import {
  expandTildePath
} from "./chunk-IXEJRKCZ.js";

// src/connectors/live/state-store.ts
import { promises as fs } from "fs";
import path from "path";

// src/connectors/live/framework.ts
var CONNECTOR_ID_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
function isValidConnectorId(id) {
  return typeof id === "string" && CONNECTOR_ID_PATTERN.test(id);
}

// src/connectors/live/state-store.ts
var STATE_DIR_NAME = "state";
var CONNECTORS_DIR_NAME = "connectors";
var MAX_ERROR_LENGTH = 1024;
var VALID_SYNC_STATUSES = /* @__PURE__ */ new Set([
  "success",
  "error",
  "never"
]);
var ConnectorStateCorruptionError = class extends Error {
  constructor(message) {
    super(message);
    this.name = "ConnectorStateCorruptionError";
  }
};
function resolveConnectorsDir(memoryDir) {
  if (typeof memoryDir !== "string" || memoryDir.length === 0) {
    throw new TypeError("memoryDir must be a non-empty string");
  }
  return path.join(expandTildePath(memoryDir), STATE_DIR_NAME, CONNECTORS_DIR_NAME);
}
function resolveConnectorStatePath(memoryDir, id) {
  if (!isValidConnectorId(id)) {
    throw new TypeError(
      `invalid connector id ${JSON.stringify(id)} \u2014 must match /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/`
    );
  }
  return path.join(resolveConnectorsDir(memoryDir), `${id}.json`);
}
function isConnectorStateShape(value) {
  if (typeof value !== "object" || value === null) return false;
  const v = value;
  if (typeof v.id !== "string") return false;
  if (typeof v.lastSyncStatus !== "string") return false;
  if (!["success", "error", "never"].includes(v.lastSyncStatus)) return false;
  if (typeof v.totalDocsImported !== "number" || !Number.isInteger(v.totalDocsImported)) return false;
  if (v.totalDocsImported < 0) return false;
  if (typeof v.updatedAt !== "string") return false;
  if (v.lastSyncAt !== null && typeof v.lastSyncAt !== "string") return false;
  if (v.cursor !== null) {
    if (typeof v.cursor !== "object" || v.cursor === null) return false;
    const c = v.cursor;
    if (typeof c.kind !== "string" || typeof c.value !== "string" || typeof c.updatedAt !== "string") {
      return false;
    }
  }
  if (v.lastSyncError !== void 0 && typeof v.lastSyncError !== "string") return false;
  return true;
}
async function assertNoSymlinkOnPath(memoryDir, filePath) {
  const expandedRoot = expandTildePath(memoryDir);
  const root = path.resolve(expandedRoot);
  const target = path.resolve(filePath);
  const rel = path.relative(root, target);
  if (rel.startsWith("..") || path.isAbsolute(rel)) {
    throw new Error(
      `connector state path ${target} escapes memory root ${root}`
    );
  }
  const segments = rel.length === 0 ? [] : rel.split(path.sep);
  let current = root;
  const componentsToCheck = [current];
  for (const seg of segments) {
    current = path.join(current, seg);
    componentsToCheck.push(current);
  }
  for (const component of componentsToCheck) {
    let stat;
    try {
      stat = await fs.lstat(component);
    } catch (err) {
      if (err.code === "ENOENT") {
        continue;
      }
      throw err;
    }
    if (stat.isSymbolicLink()) {
      throw new Error(
        `connector state path component ${component} is a symlink; refusing to follow`
      );
    }
  }
}
async function readConnectorState(memoryDir, id) {
  const filePath = resolveConnectorStatePath(memoryDir, id);
  await assertNoSymlinkOnPath(memoryDir, filePath);
  let raw;
  try {
    raw = await fs.readFile(filePath, "utf-8");
  } catch (err) {
    if (err.code === "ENOENT") return null;
    throw err;
  }
  let parsed;
  try {
    parsed = JSON.parse(raw);
  } catch (err) {
    throw new ConnectorStateCorruptionError(
      `connector state at ${filePath} is not valid JSON: ${err.message}`
    );
  }
  if (!isConnectorStateShape(parsed)) {
    throw new ConnectorStateCorruptionError(
      `connector state at ${filePath} does not match ConnectorState shape`
    );
  }
  if (parsed.id !== id) {
    throw new ConnectorStateCorruptionError(
      `connector state at ${filePath} has mismatched id ${JSON.stringify(parsed.id)}; expected ${JSON.stringify(id)}`
    );
  }
  return parsed;
}
async function writeConnectorState(memoryDir, id, state) {
  if (!isValidConnectorId(id)) {
    throw new TypeError(
      `invalid connector id ${JSON.stringify(id)} \u2014 must match /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/`
    );
  }
  if (state.id !== id) {
    throw new Error(
      `writeConnectorState(): state.id ${JSON.stringify(state.id)} does not match id argument ${JSON.stringify(id)}`
    );
  }
  if (!VALID_SYNC_STATUSES.has(state.lastSyncStatus)) {
    throw new Error(
      `writeConnectorState(): lastSyncStatus must be one of ${[...VALID_SYNC_STATUSES].join(", ")}, got ${JSON.stringify(state.lastSyncStatus)}`
    );
  }
  if (state.lastSyncAt !== null && typeof state.lastSyncAt !== "string") {
    throw new Error(
      `writeConnectorState(): lastSyncAt must be a string or null, got ${typeof state.lastSyncAt}`
    );
  }
  if (state.cursor !== null) {
    if (typeof state.cursor !== "object") {
      throw new Error(`writeConnectorState(): cursor must be an object or null`);
    }
    if (typeof state.cursor.kind !== "string" || typeof state.cursor.value !== "string" || typeof state.cursor.updatedAt !== "string") {
      throw new Error(
        `writeConnectorState(): cursor must have string kind, value, and updatedAt`
      );
    }
  }
  if (typeof state.totalDocsImported !== "number" || !Number.isInteger(state.totalDocsImported) || state.totalDocsImported < 0) {
    throw new Error(
      `writeConnectorState(): totalDocsImported must be a non-negative integer`
    );
  }
  if (state.lastSyncError !== void 0 && typeof state.lastSyncError !== "string") {
    throw new Error(`writeConnectorState(): lastSyncError must be a string when provided`);
  }
  const truncatedError = state.lastSyncError !== void 0 && state.lastSyncError.length > MAX_ERROR_LENGTH ? state.lastSyncError.slice(0, MAX_ERROR_LENGTH) : state.lastSyncError;
  const finalState = {
    id: state.id,
    cursor: state.cursor,
    lastSyncAt: state.lastSyncAt,
    lastSyncStatus: state.lastSyncStatus,
    ...truncatedError !== void 0 ? { lastSyncError: truncatedError } : {},
    totalDocsImported: state.totalDocsImported,
    updatedAt: (/* @__PURE__ */ new Date()).toISOString()
  };
  const dir = resolveConnectorsDir(memoryDir);
  const targetPath = path.join(dir, `${id}.json`);
  await assertNoSymlinkOnPath(memoryDir, targetPath);
  await fs.mkdir(dir, { recursive: true });
  const tmpPath = `${targetPath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  const body = `${JSON.stringify(finalState, null, 2)}
`;
  try {
    await fs.writeFile(tmpPath, body, { encoding: "utf-8", mode: 384 });
    await fs.rename(tmpPath, targetPath);
  } catch (err) {
    try {
      await fs.unlink(tmpPath);
    } catch {
    }
    throw err;
  }
  return finalState;
}
async function listConnectorStates(memoryDir) {
  const dir = resolveConnectorsDir(memoryDir);
  await assertNoSymlinkOnPath(memoryDir, dir);
  let entries;
  try {
    entries = await fs.readdir(dir);
  } catch (err) {
    if (err.code === "ENOENT") return [];
    throw err;
  }
  const out = [];
  for (const entry of entries) {
    if (!entry.endsWith(".json")) continue;
    const id = entry.slice(0, -".json".length);
    if (!isValidConnectorId(id)) continue;
    try {
      const state = await readConnectorState(memoryDir, id);
      if (state !== null) out.push(state);
    } catch (err) {
      if (err instanceof ConnectorStateCorruptionError) {
        continue;
      }
      throw err;
    }
  }
  out.sort((a, b) => a.id.localeCompare(b.id));
  return out;
}
function _connectorStatePathForTest(memoryDir, id) {
  return resolveConnectorStatePath(memoryDir, id);
}

export {
  CONNECTOR_ID_PATTERN,
  isValidConnectorId,
  readConnectorState,
  writeConnectorState,
  listConnectorStates,
  _connectorStatePathForTest
};
//# sourceMappingURL=chunk-6TBWYBJ3.js.map