@fragno-dev/lofi 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/adapters/in-memory/adapter.d.ts.map +1 -1
  2. package/dist/adapters/in-memory/adapter.js.map +1 -1
  3. package/dist/adapters/in-memory/query.js +1 -2
  4. package/dist/adapters/in-memory/query.js.map +1 -1
  5. package/dist/adapters/in-memory/store.d.ts.map +1 -1
  6. package/dist/adapters/in-memory/store.js +1 -1
  7. package/dist/adapters/in-memory/store.js.map +1 -1
  8. package/dist/adapters/stacked/adapter.d.ts.map +1 -1
  9. package/dist/adapters/stacked/adapter.js.map +1 -1
  10. package/dist/adapters/stacked/merge.js.map +1 -1
  11. package/dist/cli/client.js +1 -1
  12. package/dist/cli/client.js.map +1 -1
  13. package/dist/cli/index.d.ts.map +1 -1
  14. package/dist/cli/index.js +2 -2
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/cli/scenario.js +1 -1
  17. package/dist/cli/scenario.js.map +1 -1
  18. package/dist/cli/server.js +1 -1
  19. package/dist/cli/server.js.map +1 -1
  20. package/dist/client/client.d.ts.map +1 -1
  21. package/dist/client/client.js.map +1 -1
  22. package/dist/indexeddb/adapter.d.ts.map +1 -1
  23. package/dist/indexeddb/adapter.js +1 -1
  24. package/dist/indexeddb/adapter.js.map +1 -1
  25. package/dist/optimistic/overlay-manager.d.ts.map +1 -1
  26. package/dist/optimistic/overlay-manager.js +0 -1
  27. package/dist/optimistic/overlay-manager.js.map +1 -1
  28. package/dist/outbox/decode.d.ts.map +1 -1
  29. package/dist/outbox/decode.js.map +1 -1
  30. package/dist/outbox/uow.d.ts.map +1 -1
  31. package/dist/outbox/uow.js.map +1 -1
  32. package/dist/query/conditions.d.ts +2 -1
  33. package/dist/query/conditions.d.ts.map +1 -1
  34. package/dist/query/conditions.js +2 -1
  35. package/dist/query/conditions.js.map +1 -1
  36. package/dist/query/engine.d.ts.map +1 -1
  37. package/dist/query/engine.js +1 -1
  38. package/dist/query/engine.js.map +1 -1
  39. package/dist/scenario.d.ts.map +1 -1
  40. package/dist/scenario.js.map +1 -1
  41. package/dist/submit/client.d.ts.map +1 -1
  42. package/dist/submit/client.js.map +1 -1
  43. package/dist/submit/local-handler-tx.d.ts.map +1 -1
  44. package/dist/submit/local-handler-tx.js.map +1 -1
  45. package/dist/submit/queue.js.map +1 -1
  46. package/dist/submit/rebase.d.ts.map +1 -1
  47. package/dist/submit/rebase.js.map +1 -1
  48. package/dist/types.d.ts.map +1 -1
  49. package/package.json +23 -26
@@ -1 +1 @@
1
- {"version":3,"file":"merge.js","names":["candidates: JoinCandidates","exhaustiveCheck: never","matches","output: Record<string, unknown>","merged: Record<string, unknown>","next: Record<string, unknown>[]","matches: Record<string, unknown>[]","stripped: Record<string, unknown>","merged: Record<string, unknown>[]","baseRows: Record<string, unknown>[]","mergedRows: Record<string, unknown>[]","cursor: Cursor | undefined","nextAfter: Cursor | string | undefined","nextBefore: Cursor | string | undefined","nextCursor: Cursor | undefined"],"sources":["../../../src/adapters/stacked/merge.ts"],"sourcesContent":["import type { AnyColumn, AnyRelation, AnySchema, AnyTable } from \"@fragno-dev/db/schema\";\nimport { FragnoId, FragnoReference } from \"@fragno-dev/db/schema\";\nimport type { CursorResult } from \"@fragno-dev/db/cursor\";\nimport { Cursor, createCursorFromRecord, decodeCursor } from \"@fragno-dev/db/cursor\";\nimport { FindBuilder } from \"@fragno-dev/db/unit-of-work\";\nimport type { LofiQueryInterface, LofiQueryableAdapter } from \"../../types\";\nimport type { Condition } from \"../../query/conditions\";\nimport { normalizeValue } from \"../../query/normalize\";\nimport { compareNormalizedValues } from \"../in-memory/value-comparison\";\nimport type { InMemoryLofiAdapter } from \"../in-memory/adapter\";\nimport type { InMemoryLofiRow } from \"../in-memory/store\";\n\ntype CompiledJoin = {\n relation: AnyRelation;\n options:\n | {\n select: unknown;\n where?: unknown;\n orderBy?: [AnyColumn, \"asc\" | \"desc\"][];\n join?: CompiledJoin[];\n limit?: number;\n }\n | false;\n};\n\ntype BuiltFindOptions = {\n useIndex: string;\n select?: unknown;\n where?: unknown;\n orderByIndex?: {\n indexName: string;\n direction: \"asc\" | \"desc\";\n };\n after?: Cursor | string;\n before?: Cursor | string;\n pageSize?: number;\n joins?: CompiledJoin[];\n};\n\nconst buildFindBuilder = <TTable extends AnyTable>(tableName: string, table: TTable) =>\n new FindBuilder<TTable>(tableName, table);\n\nconst buildOrderColumns = (table: AnyTable, indexName: string): AnyColumn[] => {\n if (indexName === \"_primary\") {\n return [table.getIdColumn()];\n }\n\n const index = table.indexes[indexName];\n const columns = index ? [...index.columns] : [table.getIdColumn()];\n const idColumn = table.getIdColumn();\n if (!columns.some((col) => col.name === idColumn.name)) {\n columns.push(idColumn);\n }\n return columns;\n};\n\nconst coerceLocalInternalId = (value: unknown): number | null => {\n if (value == null) {\n return null;\n }\n if (typeof value === \"number\") {\n return Number.isSafeInteger(value) ? value : null;\n }\n if (typeof value === \"bigint\") {\n const asNumber = Number(value);\n return Number.isSafeInteger(asNumber) ? asNumber : null;\n }\n return null;\n};\n\nconst resolveOrderValue = (value: unknown, column: AnyColumn): unknown => {\n if (column.role === \"external-id\") {\n if (\n value &&\n typeof value === \"object\" &&\n \"externalId\" in value &&\n typeof (value as { externalId?: unknown }).externalId === \"string\"\n ) {\n return (value as { externalId: string }).externalId;\n }\n return value;\n }\n\n if (column.role === \"internal-id\" || column.role === \"reference\") {\n if (value instanceof FragnoReference) {\n return coerceLocalInternalId(value.internalId);\n }\n if (value instanceof FragnoId) {\n return coerceLocalInternalId(value.internalId);\n }\n return coerceLocalInternalId(value);\n }\n\n return normalizeValue(value, column);\n};\n\nconst compareRows = (\n left: Record<string, unknown>,\n right: Record<string, unknown>,\n orderColumns: AnyColumn[],\n direction: \"asc\" | \"desc\",\n): number => {\n for (const column of orderColumns) {\n const leftValue = resolveOrderValue(left[column.name], column);\n const rightValue = resolveOrderValue(right[column.name], column);\n const comparison = compareNormalizedValues(leftValue, rightValue);\n if (comparison !== 0) {\n return direction === \"asc\" ? comparison : -comparison;\n }\n }\n return 0;\n};\n\nconst isNullish = (value: unknown): value is null | undefined =>\n value === null || value === undefined;\n\nconst toByteArray = (value: unknown): Uint8Array | null => {\n if (value instanceof Uint8Array) {\n return value;\n }\n if (value instanceof ArrayBuffer) {\n return new Uint8Array(value);\n }\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n }\n return null;\n};\n\nconst bytesToHex = (bytes: Uint8Array): string => {\n let hex = \"\";\n for (const byte of bytes) {\n hex += byte.toString(16).padStart(2, \"0\");\n }\n return hex;\n};\n\ntype JoinCandidates = {\n internal: unknown[];\n external: unknown[];\n normalized: unknown[];\n};\n\nconst collectJoinCandidates = (value: unknown, column: AnyColumn): JoinCandidates => {\n const candidates: JoinCandidates = { internal: [], external: [], normalized: [] };\n if (isNullish(value)) {\n return candidates;\n }\n\n const pushInternal = (input: unknown) => {\n const coerced = coerceLocalInternalId(input);\n if (coerced !== null) {\n candidates.internal.push(coerced);\n }\n };\n\n const pushExternal = (input: unknown) => {\n if (input !== undefined) {\n candidates.external.push(input);\n }\n };\n\n if (value instanceof FragnoId) {\n pushInternal(value.internalId);\n pushExternal(value.externalId);\n return candidates;\n }\n\n if (value instanceof FragnoReference) {\n pushInternal(value.internalId);\n return candidates;\n }\n\n if (typeof value === \"object\") {\n if (\"internalId\" in value) {\n pushInternal((value as { internalId?: unknown }).internalId);\n }\n if (\n \"externalId\" in value &&\n typeof (value as { externalId?: unknown }).externalId === \"string\"\n ) {\n pushExternal((value as { externalId: string }).externalId);\n }\n }\n\n if (column.role === \"external-id\") {\n if (typeof value === \"string\") {\n pushExternal(value);\n return candidates;\n }\n if (typeof value === \"number\" || typeof value === \"bigint\") {\n pushInternal(value);\n return candidates;\n }\n }\n\n if (column.role === \"reference\" || column.role === \"internal-id\") {\n if (typeof value === \"string\") {\n pushExternal(value);\n return candidates;\n }\n if (typeof value === \"number\" || typeof value === \"bigint\") {\n pushInternal(value);\n return candidates;\n }\n }\n\n candidates.normalized.push(normalizeValue(value, column));\n return candidates;\n};\n\nconst hasCandidateMatch = (left: unknown[], right: unknown[]): boolean => {\n for (const leftValue of left) {\n for (const rightValue of right) {\n if (compareNormalizedValues(leftValue, rightValue) === 0) {\n return true;\n }\n }\n }\n return false;\n};\n\nconst matchesJoinCandidates = (left: JoinCandidates, right: JoinCandidates): boolean => {\n if (left.internal.length > 0 && right.internal.length > 0) {\n return hasCandidateMatch(left.internal, right.internal);\n }\n if (left.external.length > 0 && right.external.length > 0) {\n return hasCandidateMatch(left.external, right.external);\n }\n if (left.normalized.length > 0 && right.normalized.length > 0) {\n return hasCandidateMatch(left.normalized, right.normalized);\n }\n return false;\n};\n\nconst getRowValueWithOverlay = (\n row: Record<string, unknown>,\n columnName: string,\n overlayData: Record<string, unknown> | undefined,\n): unknown => {\n if (overlayData && columnName in overlayData) {\n return overlayData[columnName];\n }\n return row[columnName];\n};\n\nconst isConditionColumn = (value: unknown): value is AnyColumn =>\n !!value && typeof value === \"object\" && \"name\" in value && \"role\" in value && \"type\" in value;\n\nconst normalizeConditionValue = (value: unknown, column: AnyColumn): unknown => {\n if (isNullish(value)) {\n return value;\n }\n\n if (column.role === \"external-id\") {\n if (value instanceof FragnoId) {\n return value.externalId;\n }\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"externalId\" in value &&\n typeof (value as { externalId?: unknown }).externalId === \"string\"\n ) {\n return (value as { externalId: string }).externalId;\n }\n return value;\n }\n\n if (column.role === \"reference\" || column.role === \"internal-id\") {\n if (value instanceof FragnoReference) {\n return coerceLocalInternalId(value.internalId);\n }\n if (value instanceof FragnoId) {\n return coerceLocalInternalId(value.internalId);\n }\n if (typeof value === \"object\" && value !== null && \"internalId\" in value) {\n return coerceLocalInternalId((value as { internalId?: unknown }).internalId);\n }\n if (typeof value === \"number\" || typeof value === \"bigint\") {\n return coerceLocalInternalId(value);\n }\n }\n\n return normalizeValue(value, column);\n};\n\nconst normalizeLikeValue = (value: unknown, column: AnyColumn): string | null => {\n const normalized =\n column.role === \"reference\" || column.role === \"internal-id\"\n ? coerceLocalInternalId(value)\n : normalizeConditionValue(value, column);\n if (normalized === null || normalized === undefined) {\n return null;\n }\n const bytes = toByteArray(normalized);\n if (bytes) {\n return bytesToHex(bytes);\n }\n return String(normalized);\n};\n\nconst resolveConditionValue = (options: {\n value: unknown;\n column: AnyColumn;\n row: Record<string, unknown>;\n}): { value: unknown; column: AnyColumn } => {\n const { value, column, row } = options;\n\n if (isConditionColumn(value)) {\n return { value: row[value.name], column: value };\n }\n\n return { value, column };\n};\n\nconst evaluateCondition = (\n condition: Condition | boolean,\n row: Record<string, unknown>,\n): boolean => {\n if (typeof condition === \"boolean\") {\n return condition;\n }\n\n switch (condition.type) {\n case \"and\":\n return condition.items.every((item) => evaluateCondition(item, row));\n case \"or\":\n return condition.items.some((item) => evaluateCondition(item, row));\n case \"not\":\n return !evaluateCondition(condition.item, row);\n case \"compare\":\n break;\n default: {\n const exhaustiveCheck: never = condition;\n throw new Error(`Unsupported condition type: ${JSON.stringify(exhaustiveCheck)}`);\n }\n }\n\n const leftColumn = condition.a;\n const leftValue = normalizeConditionValue(row[leftColumn.name], leftColumn);\n const right = resolveConditionValue({ value: condition.b, column: leftColumn, row });\n const rightValue = right.value;\n const op = condition.operator;\n\n if (op === \"is\" || op === \"is not\") {\n if (isNullish(rightValue)) {\n const matches = isNullish(leftValue);\n return op === \"is\" ? matches : !matches;\n }\n\n if (isNullish(leftValue)) {\n return op === \"is not\";\n }\n\n const rightNormalized = normalizeConditionValue(rightValue, right.column);\n const matches = compareNormalizedValues(leftValue, rightNormalized) === 0;\n return op === \"is\" ? matches : !matches;\n }\n\n if (op === \"in\" || op === \"not in\") {\n const values = Array.isArray(rightValue) ? rightValue : [];\n let hasNull = false;\n let hasMatch = false;\n\n for (const entry of values) {\n if (isNullish(entry)) {\n hasNull = true;\n continue;\n }\n const normalized = normalizeConditionValue(entry, leftColumn);\n if (compareNormalizedValues(leftValue, normalized) === 0) {\n hasMatch = true;\n break;\n }\n }\n\n if (hasMatch) {\n return op === \"in\";\n }\n\n if (hasNull) {\n return false;\n }\n\n return op === \"not in\";\n }\n\n if (\n op === \"contains\" ||\n op === \"starts with\" ||\n op === \"ends with\" ||\n op === \"not contains\" ||\n op === \"not starts with\" ||\n op === \"not ends with\"\n ) {\n const leftLike = normalizeLikeValue(leftValue, leftColumn);\n const rightLike = normalizeLikeValue(rightValue, right.column);\n\n if (leftLike === null || rightLike === null) {\n return false;\n }\n\n const leftText = leftLike.toLowerCase();\n const rightText = rightLike.toLowerCase();\n let matches = false;\n\n if (op.includes(\"contains\")) {\n matches = leftText.includes(rightText);\n } else if (op.includes(\"starts with\")) {\n matches = leftText.startsWith(rightText);\n } else {\n matches = leftText.endsWith(rightText);\n }\n\n if (op.startsWith(\"not \")) {\n return !matches;\n }\n return matches;\n }\n\n const rightNormalized = normalizeConditionValue(rightValue, right.column);\n const comparison = compareNormalizedValues(leftValue, rightNormalized);\n\n switch (op) {\n case \"=\":\n return comparison === 0;\n case \"!=\":\n return comparison !== 0;\n case \">\":\n return comparison > 0;\n case \">=\":\n return comparison >= 0;\n case \"<\":\n return comparison < 0;\n case \"<=\":\n return comparison <= 0;\n default:\n throw new Error(`Unsupported operator \"${op}\".`);\n }\n};\n\nconst orderJoinRows = (\n rows: Record<string, unknown>[],\n orderBy: [AnyColumn, \"asc\" | \"desc\"][] | undefined,\n): Record<string, unknown>[] => {\n if (!orderBy || orderBy.length === 0) {\n return rows;\n }\n\n return rows.slice().sort((left, right) => {\n for (const [column, direction] of orderBy) {\n const leftValue = resolveOrderValue(left[column.name], column);\n const rightValue = resolveOrderValue(right[column.name], column);\n const comparison = compareNormalizedValues(leftValue, rightValue);\n if (comparison !== 0) {\n return direction === \"asc\" ? comparison : -comparison;\n }\n }\n return 0;\n });\n};\n\nconst matchesJoinOn = (options: {\n parentRow: Record<string, unknown>;\n targetRow: Record<string, unknown>;\n join: CompiledJoin;\n overlay: InMemoryLofiAdapter;\n schemaName: string;\n}): boolean => {\n const { parentRow, targetRow, join, overlay, schemaName } = options;\n const parentTable = join.relation.referencer;\n const targetTable = join.relation.table;\n const parentExternalId = getExternalIdFromRow(parentRow, parentTable);\n const overlayParent = parentExternalId\n ? overlay.store.getRow(schemaName, parentTable.name, parentExternalId)\n : undefined;\n const overlayData = overlayParent?.data;\n\n for (const [left, right] of join.relation.on) {\n const leftColumn = parentTable.columns[left];\n if (!leftColumn) {\n throw new Error(`Column \"${left}\" not found on table \"${parentTable.name}\".`);\n }\n\n const rightColumn = targetTable.columns[right];\n if (!rightColumn) {\n throw new Error(`Column \"${right}\" not found on table \"${targetTable.name}\".`);\n }\n\n const leftValue = getRowValueWithOverlay(parentRow, left, overlayData);\n const rightValue = targetRow[right];\n\n const leftCandidates = collectJoinCandidates(leftValue, leftColumn);\n const rightCandidates = collectJoinCandidates(rightValue, rightColumn);\n\n if (!matchesJoinCandidates(leftCandidates, rightCandidates)) {\n return false;\n }\n }\n\n return true;\n};\n\nconst buildCursorValues = (\n cursor: Cursor | string | undefined,\n columns: AnyColumn[],\n): readonly unknown[] | undefined => {\n if (!cursor) {\n return undefined;\n }\n\n const cursorObj = typeof cursor === \"string\" ? decodeCursor(cursor) : cursor;\n return columns.map((column) => resolveOrderValue(cursorObj.indexValues[column.name], column));\n};\n\nconst compareRowToCursor = (\n row: Record<string, unknown>,\n orderColumns: AnyColumn[],\n cursorValues: readonly unknown[],\n): number => {\n for (let i = 0; i < orderColumns.length; i += 1) {\n const column = orderColumns[i]!;\n const rowValue = resolveOrderValue(row[column.name], column);\n const cursorValue = cursorValues[i];\n const comparison = compareNormalizedValues(rowValue, cursorValue);\n if (comparison !== 0) {\n return comparison;\n }\n }\n return 0;\n};\n\nconst applyCursorFilters = (options: {\n rows: Record<string, unknown>[];\n orderColumns: AnyColumn[];\n direction: \"asc\" | \"desc\";\n after?: Cursor | string;\n before?: Cursor | string;\n}): Record<string, unknown>[] => {\n const { rows, orderColumns, direction, after, before } = options;\n const afterValues = buildCursorValues(after, orderColumns);\n const beforeValues = buildCursorValues(before, orderColumns);\n\n return rows.filter((row) => {\n if (afterValues) {\n const comparison = compareRowToCursor(row, orderColumns, afterValues);\n if (direction === \"asc\" ? comparison <= 0 : comparison >= 0) {\n return false;\n }\n }\n\n if (beforeValues) {\n const comparison = compareRowToCursor(row, orderColumns, beforeValues);\n if (direction === \"asc\" ? comparison >= 0 : comparison <= 0) {\n return false;\n }\n }\n\n return true;\n });\n};\n\nconst getExternalIdFromRow = (row: Record<string, unknown>, table: AnyTable): string | null => {\n const idColumn = table.getIdColumn();\n const value = row[idColumn.name];\n if (value == null) {\n return null;\n }\n if (value instanceof FragnoId) {\n return value.externalId;\n }\n if (typeof value === \"string\") {\n return value;\n }\n if (typeof value === \"object\" && \"externalId\" in value) {\n const ext = (value as { externalId?: unknown }).externalId;\n if (typeof ext === \"string\") {\n return ext;\n }\n }\n return null;\n};\n\nconst buildOutputFromLofiRow = (\n row: InMemoryLofiRow,\n table: AnyTable,\n select: undefined | true | readonly string[],\n): Record<string, unknown> => {\n const output: Record<string, unknown> = {};\n const columnNames = select && select !== true ? select : (Object.keys(table.columns) as string[]);\n\n for (const columnName of columnNames) {\n const column = table.columns[columnName];\n if (!column || column.isHidden) {\n continue;\n }\n\n if (column.role === \"external-id\") {\n output[column.name] = new FragnoId({\n externalId: row.id,\n internalId: BigInt(row._lofi.internalId),\n version: row._lofi.version,\n });\n continue;\n }\n\n if (column.role === \"reference\") {\n const value = row._lofi.norm[column.name];\n output[column.name] =\n value === null || value === undefined\n ? null\n : FragnoReference.fromInternal(BigInt(value as number));\n continue;\n }\n\n if (column.role === \"internal-id\") {\n output[column.name] = BigInt(row._lofi.internalId);\n continue;\n }\n\n if (column.role === \"version\") {\n output[column.name] = row._lofi.version;\n continue;\n }\n\n output[column.name] = row.data[column.name];\n }\n\n return output;\n};\n\nconst mergeRowColumns = (\n baseRow: Record<string, unknown>,\n overlayRow: Record<string, unknown>,\n table: AnyTable,\n select: undefined | true | readonly string[],\n): Record<string, unknown> => {\n const merged: Record<string, unknown> = { ...baseRow };\n const columnNames = select && select !== true ? select : (Object.keys(table.columns) as string[]);\n\n for (const columnName of columnNames) {\n if (!table.columns[columnName]) {\n continue;\n }\n if (columnName in overlayRow) {\n merged[columnName] = overlayRow[columnName];\n }\n }\n\n return merged;\n};\n\nconst patchJoinRows = async <TSchema extends AnySchema>(options: {\n row: Record<string, unknown>;\n joins: CompiledJoin[] | undefined;\n overlay: InMemoryLofiAdapter;\n baseQuery: LofiQueryInterface<TSchema>;\n schemaName: string;\n baseRowsCache: Map<string, Record<string, unknown>[]>;\n}): Promise<void> => {\n const { row, joins, overlay, baseQuery, schemaName, baseRowsCache } = options;\n if (!joins || joins.length === 0) {\n return;\n }\n\n for (const join of joins) {\n if (join.options === false) {\n continue;\n }\n\n const joinOptions = join.options;\n const relationName = join.relation.name;\n let target = row[relationName];\n\n if (target !== undefined && target !== null) {\n const canCheck = (targetRow: Record<string, unknown>) =>\n join.relation.on.every(([, right]) => right in targetRow);\n\n const needsRefresh = Array.isArray(target)\n ? target.some(\n (entry) =>\n entry &&\n typeof entry === \"object\" &&\n canCheck(entry as Record<string, unknown>) &&\n !matchesJoinOn({\n parentRow: row,\n targetRow: entry as Record<string, unknown>,\n join,\n overlay,\n schemaName,\n }),\n )\n : typeof target === \"object\" &&\n canCheck(target as Record<string, unknown>) &&\n !matchesJoinOn({\n parentRow: row,\n targetRow: target as Record<string, unknown>,\n join,\n overlay,\n schemaName,\n });\n\n if (needsRefresh) {\n delete row[relationName];\n target = undefined;\n }\n }\n\n if (target === undefined || target === null || (Array.isArray(target) && target.length === 0)) {\n const baseMatches = await loadBaseJoinMatches({\n parentRow: row,\n join,\n overlay,\n baseQuery,\n schemaName,\n baseRowsCache,\n });\n if (baseMatches.length > 0) {\n target = join.relation.type === \"many\" ? baseMatches : baseMatches[0];\n row[relationName] = target;\n }\n }\n\n if (target === undefined || target === null) {\n continue;\n }\n\n const patchTarget = async (\n targetRow: Record<string, unknown>,\n ): Promise<Record<string, unknown> | null> => {\n const targetTable = join.relation.table;\n const externalId = getExternalIdFromRow(targetRow, targetTable);\n if (!externalId) {\n await patchJoinRows({\n row: targetRow,\n joins: joinOptions.join,\n overlay,\n baseQuery,\n schemaName,\n baseRowsCache,\n });\n return targetRow;\n }\n\n if (overlay.store.hasTombstone(schemaName, targetTable.name, externalId)) {\n return null;\n }\n\n let mergedTarget = targetRow;\n const overlayRow = overlay.store.getRow(schemaName, targetTable.name, externalId);\n if (overlayRow) {\n const overlayOutput = buildOutputFromLofiRow(\n overlayRow,\n targetTable,\n joinOptions.select as undefined | true | readonly string[],\n );\n mergedTarget = mergeRowColumns(\n targetRow,\n overlayOutput,\n targetTable,\n joinOptions.select as undefined | true | readonly string[],\n );\n }\n\n await patchJoinRows({\n row: mergedTarget,\n joins: joinOptions.join,\n overlay,\n baseQuery,\n schemaName,\n baseRowsCache,\n });\n\n return mergedTarget;\n };\n\n if (Array.isArray(target)) {\n const next: Record<string, unknown>[] = [];\n for (const entry of target) {\n if (entry && typeof entry === \"object\") {\n const patched = await patchTarget(entry as Record<string, unknown>);\n if (patched) {\n next.push(patched);\n }\n }\n }\n row[relationName] = next;\n continue;\n }\n\n if (typeof target === \"object\") {\n const patched = await patchTarget(target as Record<string, unknown>);\n if (!patched) {\n delete row[relationName];\n } else {\n row[relationName] = patched;\n }\n }\n }\n};\n\nasync function loadBaseJoinMatches<TSchema extends AnySchema>(options: {\n parentRow: Record<string, unknown>;\n join: CompiledJoin;\n overlay: InMemoryLofiAdapter;\n baseQuery: LofiQueryInterface<TSchema>;\n schemaName: string;\n baseRowsCache: Map<string, Record<string, unknown>[]>;\n}): Promise<Record<string, unknown>[]> {\n const { parentRow, join, overlay, baseQuery, schemaName, baseRowsCache } = options;\n if (join.options === false) {\n return [];\n }\n\n const joinOptions = join.options;\n const targetTable = join.relation.table;\n let baseRows = baseRowsCache.get(targetTable.name);\n if (!baseRows) {\n baseRows = (await baseQuery.find(targetTable.name, (b) => b.whereIndex(\"primary\"))) as Record<\n string,\n unknown\n >[];\n baseRowsCache.set(targetTable.name, baseRows);\n }\n\n const matches: Record<string, unknown>[] = [];\n for (const baseRow of baseRows) {\n if (!matchesJoinOn({ parentRow, targetRow: baseRow, join, overlay, schemaName })) {\n continue;\n }\n if (joinOptions.where && !evaluateCondition(joinOptions.where as Condition, baseRow)) {\n continue;\n }\n matches.push({ ...baseRow });\n }\n\n let ordered = orderJoinRows(matches, joinOptions.orderBy);\n if (joinOptions.limit !== undefined) {\n ordered = ordered.slice(0, Math.max(0, joinOptions.limit));\n }\n\n for (const row of ordered) {\n await patchJoinRows({\n row,\n joins: joinOptions.join,\n overlay,\n baseQuery,\n schemaName,\n baseRowsCache,\n });\n }\n\n if (joinOptions.select && joinOptions.select !== true) {\n return ordered.map((row) =>\n stripSelection({\n row,\n table: targetTable,\n select: joinOptions.select as undefined | true | readonly string[],\n joins: joinOptions.join,\n }),\n );\n }\n\n if (joinOptions.join && joinOptions.join.length > 0) {\n return ordered.map((row) =>\n stripSelection({\n row,\n table: targetTable,\n select: joinOptions.select as undefined | true | readonly string[],\n joins: joinOptions.join,\n }),\n );\n }\n\n return ordered;\n}\n\nconst stripSelection = (options: {\n row: Record<string, unknown>;\n table: AnyTable;\n select: undefined | true | readonly string[];\n joins: CompiledJoin[] | undefined;\n}): Record<string, unknown> => {\n const { row, table, select, joins } = options;\n const stripped: Record<string, unknown> = { ...row };\n\n if (select && select !== true) {\n const keep = new Set(select);\n for (const key of Object.keys(stripped)) {\n if (table.columns[key] && !keep.has(key)) {\n delete stripped[key];\n }\n }\n }\n\n if (joins) {\n for (const join of joins) {\n if (join.options === false) {\n continue;\n }\n const joinOptions = join.options;\n const relationName = join.relation.name;\n const child = stripped[relationName];\n if (!child) {\n continue;\n }\n if (Array.isArray(child)) {\n stripped[relationName] = child.map((entry) =>\n stripSelection({\n row: entry as Record<string, unknown>,\n table: join.relation.table,\n select: joinOptions.select as undefined | true | readonly string[],\n joins: joinOptions.join,\n }),\n );\n } else if (typeof child === \"object\") {\n stripped[relationName] = stripSelection({\n row: child as Record<string, unknown>,\n table: join.relation.table,\n select: joinOptions.select as undefined | true | readonly string[],\n joins: joinOptions.join,\n });\n }\n }\n }\n\n return stripped;\n};\n\nconst augmentSelect = (options: {\n table: AnyTable;\n select: undefined | true | readonly string[];\n orderColumns: AnyColumn[];\n}): undefined | true | readonly string[] => {\n const { table, select, orderColumns } = options;\n if (!select || select === true) {\n return select;\n }\n\n const augmented = new Set(select);\n augmented.add(table.getIdColumn().name);\n for (const column of orderColumns) {\n augmented.add(column.name);\n }\n\n const result = Array.from(augmented);\n return result.length === select.length ? select : result;\n};\n\nconst mergeRows = (options: {\n baseRows: Record<string, unknown>[];\n overlayRows: Record<string, unknown>[];\n overlay: InMemoryLofiAdapter;\n schemaName: string;\n table: AnyTable;\n select: undefined | true | readonly string[];\n}): Record<string, unknown>[] => {\n const { baseRows, overlayRows, overlay, schemaName, table, select } = options;\n const baseIds = new Set<string>();\n const merged: Record<string, unknown>[] = [];\n\n const overlayOutputById = new Map<string, Record<string, unknown>>();\n\n for (const baseRow of baseRows) {\n const externalId = getExternalIdFromRow(baseRow, table);\n if (!externalId) {\n continue;\n }\n baseIds.add(externalId);\n\n if (overlay.store.hasTombstone(schemaName, table.name, externalId)) {\n continue;\n }\n\n const overlayRow = overlay.store.getRow(schemaName, table.name, externalId);\n if (overlayRow) {\n let overlayOutput = overlayOutputById.get(externalId);\n if (!overlayOutput) {\n overlayOutput = buildOutputFromLofiRow(overlayRow, table, select);\n overlayOutputById.set(externalId, overlayOutput);\n }\n merged.push(mergeRowColumns(baseRow, overlayOutput, table, select));\n continue;\n }\n\n merged.push(baseRow);\n }\n\n for (const overlayRow of overlayRows) {\n const externalId = getExternalIdFromRow(overlayRow, table);\n if (!externalId || baseIds.has(externalId)) {\n continue;\n }\n if (overlay.store.hasTombstone(schemaName, table.name, externalId)) {\n continue;\n }\n merged.push(overlayRow);\n }\n\n return merged;\n};\n\nexport const createStackedQueryEngine = <T extends AnySchema>(options: {\n schema: T;\n base: LofiQueryableAdapter;\n overlay: InMemoryLofiAdapter;\n schemaName?: string;\n}): LofiQueryInterface<T> => {\n const schemaName = options.schemaName ?? options.schema.name;\n const baseQuery = options.base.createQueryEngine(options.schema, { schemaName });\n const overlayQuery = options.overlay.createQueryEngine(options.schema, { schemaName });\n\n const runFind = async (\n tableName: string,\n builderFn: ((builder: FindBuilder<AnyTable>) => unknown) | undefined,\n withCursor: boolean,\n ): Promise<Record<string, unknown>[] | CursorResult<Record<string, unknown>> | number> => {\n const tableMap = options.schema.tables as Record<string, AnyTable>;\n const table = tableMap[tableName];\n if (!table) {\n throw new Error(`Table ${tableName} not found in schema`);\n }\n\n const baseFind = baseQuery.find as unknown as (\n name: string,\n builder: (builder: FindBuilder<AnyTable>) => unknown,\n ) => Promise<Record<string, unknown>[] | number>;\n const baseFindWithCursor = baseQuery.findWithCursor as unknown as (\n name: string,\n builder: (builder: FindBuilder<AnyTable>) => unknown,\n ) => Promise<CursorResult<Record<string, unknown>>>;\n const overlayFind = overlayQuery.find as unknown as (\n name: string,\n builder: (builder: FindBuilder<AnyTable>) => unknown,\n ) => Promise<Record<string, unknown>[] | number>;\n\n const builder = buildFindBuilder(tableName, table);\n if (builderFn) {\n builderFn(builder);\n } else {\n builder.whereIndex(\"primary\");\n }\n\n const built = builder.build() as\n | { type: \"find\"; indexName: string; options: BuiltFindOptions }\n | { type: \"count\"; indexName: string; options: Pick<BuiltFindOptions, \"where\"> };\n\n if (built.type === \"count\") {\n const countBuilder = (b: FindBuilder<AnyTable>) => {\n if (!built.options.where) {\n b.whereIndex(built.indexName as \"primary\");\n return;\n }\n if (typeof built.options.where === \"function\") {\n b.whereIndex(built.indexName as \"primary\", built.options.where as () => Condition);\n return;\n }\n b.whereIndex(built.indexName as \"primary\", () => built.options.where as Condition);\n };\n\n const baseRows = (await baseFind(tableName, countBuilder)) as Record<string, unknown>[];\n const overlayRows = (await overlayFind(tableName, countBuilder)) as Record<string, unknown>[];\n\n const merged = mergeRows({\n baseRows,\n overlayRows,\n overlay: options.overlay,\n schemaName,\n table,\n select: undefined,\n });\n\n return merged.length;\n }\n\n const orderIndexName = built.options.orderByIndex?.indexName ?? built.indexName;\n const direction = built.options.orderByIndex?.direction ?? \"asc\";\n const orderColumns = buildOrderColumns(table, orderIndexName);\n const originalSelect = built.options.select as undefined | true | readonly string[];\n const select = augmentSelect({\n table,\n select: originalSelect,\n orderColumns,\n });\n\n const overlayPageSize = built.options.pageSize;\n const overlayBuilder = (b: FindBuilder<AnyTable>) => {\n if (builderFn) {\n builderFn(b);\n } else {\n b.whereIndex(\"primary\");\n }\n if (select && select !== originalSelect) {\n b.select(select as unknown as true | string[]);\n }\n if (overlayPageSize !== undefined) {\n b.pageSize(overlayPageSize);\n }\n };\n\n const overlayRows = (await overlayFind(tableName, overlayBuilder)) as Record<string, unknown>[];\n\n const baseRows: Record<string, unknown>[] = [];\n let mergedRows: Record<string, unknown>[] = [];\n let hasNextBase = false;\n let cursor: Cursor | undefined;\n\n const pageSize = built.options.pageSize;\n if (pageSize === undefined) {\n const baseBuilder = (b: FindBuilder<AnyTable>) => {\n if (builderFn) {\n builderFn(b);\n } else {\n b.whereIndex(\"primary\");\n }\n if (select && select !== originalSelect) {\n b.select(select as unknown as true | string[]);\n }\n };\n\n baseRows.push(...((await baseFind(tableName, baseBuilder)) as Record<string, unknown>[]));\n } else {\n let nextAfter: Cursor | string | undefined = built.options.after;\n let nextBefore: Cursor | string | undefined = built.options.before;\n let firstPage = true;\n let keepFetching = true;\n\n while (keepFetching) {\n const baseBuilder = (b: FindBuilder<AnyTable>) => {\n if (builderFn) {\n builderFn(b);\n } else {\n b.whereIndex(\"primary\");\n }\n if (select && select !== originalSelect) {\n b.select(select as unknown as true | string[]);\n }\n b.pageSize(pageSize);\n if (firstPage) {\n if (nextAfter) {\n b.after(nextAfter);\n }\n if (nextBefore) {\n b.before(nextBefore);\n }\n } else if (cursor) {\n b.after(cursor);\n }\n };\n\n const page = (await baseFindWithCursor(tableName, baseBuilder)) as CursorResult<\n Record<string, unknown>\n >;\n\n baseRows.push(...page.items);\n hasNextBase = page.hasNextPage;\n cursor = page.cursor;\n nextAfter = undefined;\n nextBefore = undefined;\n firstPage = false;\n\n mergedRows = mergeRows({\n baseRows,\n overlayRows,\n overlay: options.overlay,\n schemaName,\n table,\n select,\n });\n\n mergedRows = mergedRows.sort((left, right) =>\n compareRows(left, right, orderColumns, direction),\n );\n mergedRows = applyCursorFilters({\n rows: mergedRows,\n orderColumns,\n direction,\n after: built.options.after,\n before: built.options.before,\n });\n\n if (!pageSize || mergedRows.length > pageSize || !hasNextBase) {\n break;\n }\n }\n }\n\n if (pageSize === undefined || baseRows.length > 0) {\n mergedRows = mergeRows({\n baseRows,\n overlayRows,\n overlay: options.overlay,\n schemaName,\n table,\n select,\n });\n }\n\n mergedRows = mergedRows.sort((left, right) =>\n compareRows(left, right, orderColumns, direction),\n );\n mergedRows = applyCursorFilters({\n rows: mergedRows,\n orderColumns,\n direction,\n after: built.options.after,\n before: built.options.before,\n });\n\n const baseRowsCache = new Map<string, Record<string, unknown>[]>();\n for (const row of mergedRows) {\n await patchJoinRows({\n row,\n joins: built.options.joins,\n overlay: options.overlay,\n baseQuery,\n schemaName,\n baseRowsCache,\n });\n }\n\n if (!withCursor) {\n const limited =\n pageSize !== undefined ? mergedRows.slice(0, Math.max(0, pageSize)) : mergedRows;\n return limited.map((row) =>\n stripSelection({\n row,\n table,\n select: originalSelect,\n joins: built.options.joins,\n }),\n );\n }\n\n let hasNextPage = false;\n let items = mergedRows;\n\n if (pageSize && mergedRows.length > pageSize) {\n hasNextPage = true;\n items = mergedRows.slice(0, pageSize);\n }\n\n let nextCursor: Cursor | undefined;\n const lastRow = items[items.length - 1];\n if (lastRow && pageSize) {\n nextCursor = createCursorFromRecord(lastRow, orderColumns, {\n indexName: orderIndexName,\n orderDirection: direction,\n pageSize,\n });\n }\n\n return {\n items: items.map((row) =>\n stripSelection({\n row,\n table,\n select: originalSelect,\n joins: built.options.joins,\n }),\n ),\n cursor: nextCursor,\n hasNextPage,\n };\n };\n\n return {\n async find(tableName, builderFn) {\n const result = await runFind(\n tableName,\n builderFn as unknown as (builder: FindBuilder<AnyTable>) => unknown,\n false,\n );\n return result as Record<string, unknown>[] | number;\n },\n\n async findWithCursor(tableName, builderFn) {\n const result = await runFind(\n tableName,\n builderFn as unknown as (builder: FindBuilder<AnyTable>) => unknown,\n true,\n );\n return result as CursorResult<Record<string, unknown>>;\n },\n\n async findFirst(tableName, builderFn) {\n const result = await runFind(\n tableName,\n builderFn\n ? (builder: FindBuilder<AnyTable>) => {\n (builderFn as unknown as (b: FindBuilder<AnyTable>) => unknown)(builder);\n builder.pageSize(1);\n return builder;\n }\n : (builder: FindBuilder<AnyTable>) => builder.whereIndex(\"primary\").pageSize(1),\n false,\n );\n\n if (typeof result === \"number\") {\n return null;\n }\n\n return (result as Record<string, unknown>[])[0] ?? null;\n },\n } as LofiQueryInterface<T>;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"merge.js","names":["candidates: JoinCandidates","exhaustiveCheck: never","matches","output: Record<string, unknown>","merged: Record<string, unknown>","next: Record<string, unknown>[]","matches: Record<string, unknown>[]","stripped: Record<string, unknown>","merged: Record<string, unknown>[]","baseRows: Record<string, unknown>[]","mergedRows: Record<string, unknown>[]","cursor: Cursor | undefined","nextAfter: Cursor | string | undefined","nextBefore: Cursor | string | undefined","nextCursor: Cursor | undefined"],"sources":["../../../src/adapters/stacked/merge.ts"],"sourcesContent":["import type { CursorResult } from \"@fragno-dev/db/cursor\";\nimport { Cursor, createCursorFromRecord, decodeCursor } from \"@fragno-dev/db/cursor\";\nimport type { AnyColumn, AnyRelation, AnySchema, AnyTable } from \"@fragno-dev/db/schema\";\nimport { FragnoId, FragnoReference } from \"@fragno-dev/db/schema\";\nimport { FindBuilder } from \"@fragno-dev/db/unit-of-work\";\n\nimport type { Condition } from \"../../query/conditions\";\nimport { normalizeValue } from \"../../query/normalize\";\nimport type { LofiQueryInterface, LofiQueryableAdapter } from \"../../types\";\nimport type { InMemoryLofiAdapter } from \"../in-memory/adapter\";\nimport type { InMemoryLofiRow } from \"../in-memory/store\";\nimport { compareNormalizedValues } from \"../in-memory/value-comparison\";\n\ntype CompiledJoin = {\n relation: AnyRelation;\n options:\n | {\n select: unknown;\n where?: unknown;\n orderBy?: [AnyColumn, \"asc\" | \"desc\"][];\n join?: CompiledJoin[];\n limit?: number;\n }\n | false;\n};\n\ntype BuiltFindOptions = {\n useIndex: string;\n select?: unknown;\n where?: unknown;\n orderByIndex?: {\n indexName: string;\n direction: \"asc\" | \"desc\";\n };\n after?: Cursor | string;\n before?: Cursor | string;\n pageSize?: number;\n joins?: CompiledJoin[];\n};\n\nconst buildFindBuilder = <TTable extends AnyTable>(tableName: string, table: TTable) =>\n new FindBuilder<TTable>(tableName, table);\n\nconst buildOrderColumns = (table: AnyTable, indexName: string): AnyColumn[] => {\n if (indexName === \"_primary\") {\n return [table.getIdColumn()];\n }\n\n const index = table.indexes[indexName];\n const columns = index ? [...index.columns] : [table.getIdColumn()];\n const idColumn = table.getIdColumn();\n if (!columns.some((col) => col.name === idColumn.name)) {\n columns.push(idColumn);\n }\n return columns;\n};\n\nconst coerceLocalInternalId = (value: unknown): number | null => {\n if (value == null) {\n return null;\n }\n if (typeof value === \"number\") {\n return Number.isSafeInteger(value) ? value : null;\n }\n if (typeof value === \"bigint\") {\n const asNumber = Number(value);\n return Number.isSafeInteger(asNumber) ? asNumber : null;\n }\n return null;\n};\n\nconst resolveOrderValue = (value: unknown, column: AnyColumn): unknown => {\n if (column.role === \"external-id\") {\n if (\n value &&\n typeof value === \"object\" &&\n \"externalId\" in value &&\n typeof (value as { externalId?: unknown }).externalId === \"string\"\n ) {\n return (value as { externalId: string }).externalId;\n }\n return value;\n }\n\n if (column.role === \"internal-id\" || column.role === \"reference\") {\n if (value instanceof FragnoReference) {\n return coerceLocalInternalId(value.internalId);\n }\n if (value instanceof FragnoId) {\n return coerceLocalInternalId(value.internalId);\n }\n return coerceLocalInternalId(value);\n }\n\n return normalizeValue(value, column);\n};\n\nconst compareRows = (\n left: Record<string, unknown>,\n right: Record<string, unknown>,\n orderColumns: AnyColumn[],\n direction: \"asc\" | \"desc\",\n): number => {\n for (const column of orderColumns) {\n const leftValue = resolveOrderValue(left[column.name], column);\n const rightValue = resolveOrderValue(right[column.name], column);\n const comparison = compareNormalizedValues(leftValue, rightValue);\n if (comparison !== 0) {\n return direction === \"asc\" ? comparison : -comparison;\n }\n }\n return 0;\n};\n\nconst isNullish = (value: unknown): value is null | undefined =>\n value === null || value === undefined;\n\nconst toByteArray = (value: unknown): Uint8Array | null => {\n if (value instanceof Uint8Array) {\n return value;\n }\n if (value instanceof ArrayBuffer) {\n return new Uint8Array(value);\n }\n if (ArrayBuffer.isView(value)) {\n return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);\n }\n return null;\n};\n\nconst bytesToHex = (bytes: Uint8Array): string => {\n let hex = \"\";\n for (const byte of bytes) {\n hex += byte.toString(16).padStart(2, \"0\");\n }\n return hex;\n};\n\ntype JoinCandidates = {\n internal: unknown[];\n external: unknown[];\n normalized: unknown[];\n};\n\nconst collectJoinCandidates = (value: unknown, column: AnyColumn): JoinCandidates => {\n const candidates: JoinCandidates = { internal: [], external: [], normalized: [] };\n if (isNullish(value)) {\n return candidates;\n }\n\n const pushInternal = (input: unknown) => {\n const coerced = coerceLocalInternalId(input);\n if (coerced !== null) {\n candidates.internal.push(coerced);\n }\n };\n\n const pushExternal = (input: unknown) => {\n if (input !== undefined) {\n candidates.external.push(input);\n }\n };\n\n if (value instanceof FragnoId) {\n pushInternal(value.internalId);\n pushExternal(value.externalId);\n return candidates;\n }\n\n if (value instanceof FragnoReference) {\n pushInternal(value.internalId);\n return candidates;\n }\n\n if (typeof value === \"object\") {\n if (\"internalId\" in value) {\n pushInternal((value as { internalId?: unknown }).internalId);\n }\n if (\n \"externalId\" in value &&\n typeof (value as { externalId?: unknown }).externalId === \"string\"\n ) {\n pushExternal((value as { externalId: string }).externalId);\n }\n }\n\n if (column.role === \"external-id\") {\n if (typeof value === \"string\") {\n pushExternal(value);\n return candidates;\n }\n if (typeof value === \"number\" || typeof value === \"bigint\") {\n pushInternal(value);\n return candidates;\n }\n }\n\n if (column.role === \"reference\" || column.role === \"internal-id\") {\n if (typeof value === \"string\") {\n pushExternal(value);\n return candidates;\n }\n if (typeof value === \"number\" || typeof value === \"bigint\") {\n pushInternal(value);\n return candidates;\n }\n }\n\n candidates.normalized.push(normalizeValue(value, column));\n return candidates;\n};\n\nconst hasCandidateMatch = (left: unknown[], right: unknown[]): boolean => {\n for (const leftValue of left) {\n for (const rightValue of right) {\n if (compareNormalizedValues(leftValue, rightValue) === 0) {\n return true;\n }\n }\n }\n return false;\n};\n\nconst matchesJoinCandidates = (left: JoinCandidates, right: JoinCandidates): boolean => {\n if (left.internal.length > 0 && right.internal.length > 0) {\n return hasCandidateMatch(left.internal, right.internal);\n }\n if (left.external.length > 0 && right.external.length > 0) {\n return hasCandidateMatch(left.external, right.external);\n }\n if (left.normalized.length > 0 && right.normalized.length > 0) {\n return hasCandidateMatch(left.normalized, right.normalized);\n }\n return false;\n};\n\nconst getRowValueWithOverlay = (\n row: Record<string, unknown>,\n columnName: string,\n overlayData: Record<string, unknown> | undefined,\n): unknown => {\n if (overlayData && columnName in overlayData) {\n return overlayData[columnName];\n }\n return row[columnName];\n};\n\nconst isConditionColumn = (value: unknown): value is AnyColumn =>\n !!value && typeof value === \"object\" && \"name\" in value && \"role\" in value && \"type\" in value;\n\nconst normalizeConditionValue = (value: unknown, column: AnyColumn): unknown => {\n if (isNullish(value)) {\n return value;\n }\n\n if (column.role === \"external-id\") {\n if (value instanceof FragnoId) {\n return value.externalId;\n }\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"externalId\" in value &&\n typeof (value as { externalId?: unknown }).externalId === \"string\"\n ) {\n return (value as { externalId: string }).externalId;\n }\n return value;\n }\n\n if (column.role === \"reference\" || column.role === \"internal-id\") {\n if (value instanceof FragnoReference) {\n return coerceLocalInternalId(value.internalId);\n }\n if (value instanceof FragnoId) {\n return coerceLocalInternalId(value.internalId);\n }\n if (typeof value === \"object\" && value !== null && \"internalId\" in value) {\n return coerceLocalInternalId((value as { internalId?: unknown }).internalId);\n }\n if (typeof value === \"number\" || typeof value === \"bigint\") {\n return coerceLocalInternalId(value);\n }\n }\n\n return normalizeValue(value, column);\n};\n\nconst normalizeLikeValue = (value: unknown, column: AnyColumn): string | null => {\n const normalized =\n column.role === \"reference\" || column.role === \"internal-id\"\n ? coerceLocalInternalId(value)\n : normalizeConditionValue(value, column);\n if (normalized === null || normalized === undefined) {\n return null;\n }\n const bytes = toByteArray(normalized);\n if (bytes) {\n return bytesToHex(bytes);\n }\n return String(normalized);\n};\n\nconst resolveConditionValue = (options: {\n value: unknown;\n column: AnyColumn;\n row: Record<string, unknown>;\n}): { value: unknown; column: AnyColumn } => {\n const { value, column, row } = options;\n\n if (isConditionColumn(value)) {\n return { value: row[value.name], column: value };\n }\n\n return { value, column };\n};\n\nconst evaluateCondition = (\n condition: Condition | boolean,\n row: Record<string, unknown>,\n): boolean => {\n if (typeof condition === \"boolean\") {\n return condition;\n }\n\n switch (condition.type) {\n case \"and\":\n return condition.items.every((item) => evaluateCondition(item, row));\n case \"or\":\n return condition.items.some((item) => evaluateCondition(item, row));\n case \"not\":\n return !evaluateCondition(condition.item, row);\n case \"compare\":\n break;\n default: {\n const exhaustiveCheck: never = condition;\n throw new Error(`Unsupported condition type: ${JSON.stringify(exhaustiveCheck)}`);\n }\n }\n\n const leftColumn = condition.a;\n const leftValue = normalizeConditionValue(row[leftColumn.name], leftColumn);\n const right = resolveConditionValue({ value: condition.b, column: leftColumn, row });\n const rightValue = right.value;\n const op = condition.operator;\n\n if (op === \"is\" || op === \"is not\") {\n if (isNullish(rightValue)) {\n const matches = isNullish(leftValue);\n return op === \"is\" ? matches : !matches;\n }\n\n if (isNullish(leftValue)) {\n return op === \"is not\";\n }\n\n const rightNormalized = normalizeConditionValue(rightValue, right.column);\n const matches = compareNormalizedValues(leftValue, rightNormalized) === 0;\n return op === \"is\" ? matches : !matches;\n }\n\n if (op === \"in\" || op === \"not in\") {\n const values = Array.isArray(rightValue) ? rightValue : [];\n let hasNull = false;\n let hasMatch = false;\n\n for (const entry of values) {\n if (isNullish(entry)) {\n hasNull = true;\n continue;\n }\n const normalized = normalizeConditionValue(entry, leftColumn);\n if (compareNormalizedValues(leftValue, normalized) === 0) {\n hasMatch = true;\n break;\n }\n }\n\n if (hasMatch) {\n return op === \"in\";\n }\n\n if (hasNull) {\n return false;\n }\n\n return op === \"not in\";\n }\n\n if (\n op === \"contains\" ||\n op === \"starts with\" ||\n op === \"ends with\" ||\n op === \"not contains\" ||\n op === \"not starts with\" ||\n op === \"not ends with\"\n ) {\n const leftLike = normalizeLikeValue(leftValue, leftColumn);\n const rightLike = normalizeLikeValue(rightValue, right.column);\n\n if (leftLike === null || rightLike === null) {\n return false;\n }\n\n const leftText = leftLike.toLowerCase();\n const rightText = rightLike.toLowerCase();\n let matches = false;\n\n if (op.includes(\"contains\")) {\n matches = leftText.includes(rightText);\n } else if (op.includes(\"starts with\")) {\n matches = leftText.startsWith(rightText);\n } else {\n matches = leftText.endsWith(rightText);\n }\n\n if (op.startsWith(\"not \")) {\n return !matches;\n }\n return matches;\n }\n\n const rightNormalized = normalizeConditionValue(rightValue, right.column);\n const comparison = compareNormalizedValues(leftValue, rightNormalized);\n\n switch (op) {\n case \"=\":\n return comparison === 0;\n case \"!=\":\n return comparison !== 0;\n case \">\":\n return comparison > 0;\n case \">=\":\n return comparison >= 0;\n case \"<\":\n return comparison < 0;\n case \"<=\":\n return comparison <= 0;\n default:\n throw new Error(`Unsupported operator \"${op}\".`);\n }\n};\n\nconst orderJoinRows = (\n rows: Record<string, unknown>[],\n orderBy: [AnyColumn, \"asc\" | \"desc\"][] | undefined,\n): Record<string, unknown>[] => {\n if (!orderBy || orderBy.length === 0) {\n return rows;\n }\n\n return rows.slice().sort((left, right) => {\n for (const [column, direction] of orderBy) {\n const leftValue = resolveOrderValue(left[column.name], column);\n const rightValue = resolveOrderValue(right[column.name], column);\n const comparison = compareNormalizedValues(leftValue, rightValue);\n if (comparison !== 0) {\n return direction === \"asc\" ? comparison : -comparison;\n }\n }\n return 0;\n });\n};\n\nconst matchesJoinOn = (options: {\n parentRow: Record<string, unknown>;\n targetRow: Record<string, unknown>;\n join: CompiledJoin;\n overlay: InMemoryLofiAdapter;\n schemaName: string;\n}): boolean => {\n const { parentRow, targetRow, join, overlay, schemaName } = options;\n const parentTable = join.relation.referencer;\n const targetTable = join.relation.table;\n const parentExternalId = getExternalIdFromRow(parentRow, parentTable);\n const overlayParent = parentExternalId\n ? overlay.store.getRow(schemaName, parentTable.name, parentExternalId)\n : undefined;\n const overlayData = overlayParent?.data;\n\n for (const [left, right] of join.relation.on) {\n const leftColumn = parentTable.columns[left];\n if (!leftColumn) {\n throw new Error(`Column \"${left}\" not found on table \"${parentTable.name}\".`);\n }\n\n const rightColumn = targetTable.columns[right];\n if (!rightColumn) {\n throw new Error(`Column \"${right}\" not found on table \"${targetTable.name}\".`);\n }\n\n const leftValue = getRowValueWithOverlay(parentRow, left, overlayData);\n const rightValue = targetRow[right];\n\n const leftCandidates = collectJoinCandidates(leftValue, leftColumn);\n const rightCandidates = collectJoinCandidates(rightValue, rightColumn);\n\n if (!matchesJoinCandidates(leftCandidates, rightCandidates)) {\n return false;\n }\n }\n\n return true;\n};\n\nconst buildCursorValues = (\n cursor: Cursor | string | undefined,\n columns: AnyColumn[],\n): readonly unknown[] | undefined => {\n if (!cursor) {\n return undefined;\n }\n\n const cursorObj = typeof cursor === \"string\" ? decodeCursor(cursor) : cursor;\n return columns.map((column) => resolveOrderValue(cursorObj.indexValues[column.name], column));\n};\n\nconst compareRowToCursor = (\n row: Record<string, unknown>,\n orderColumns: AnyColumn[],\n cursorValues: readonly unknown[],\n): number => {\n for (let i = 0; i < orderColumns.length; i += 1) {\n const column = orderColumns[i]!;\n const rowValue = resolveOrderValue(row[column.name], column);\n const cursorValue = cursorValues[i];\n const comparison = compareNormalizedValues(rowValue, cursorValue);\n if (comparison !== 0) {\n return comparison;\n }\n }\n return 0;\n};\n\nconst applyCursorFilters = (options: {\n rows: Record<string, unknown>[];\n orderColumns: AnyColumn[];\n direction: \"asc\" | \"desc\";\n after?: Cursor | string;\n before?: Cursor | string;\n}): Record<string, unknown>[] => {\n const { rows, orderColumns, direction, after, before } = options;\n const afterValues = buildCursorValues(after, orderColumns);\n const beforeValues = buildCursorValues(before, orderColumns);\n\n return rows.filter((row) => {\n if (afterValues) {\n const comparison = compareRowToCursor(row, orderColumns, afterValues);\n if (direction === \"asc\" ? comparison <= 0 : comparison >= 0) {\n return false;\n }\n }\n\n if (beforeValues) {\n const comparison = compareRowToCursor(row, orderColumns, beforeValues);\n if (direction === \"asc\" ? comparison >= 0 : comparison <= 0) {\n return false;\n }\n }\n\n return true;\n });\n};\n\nconst getExternalIdFromRow = (row: Record<string, unknown>, table: AnyTable): string | null => {\n const idColumn = table.getIdColumn();\n const value = row[idColumn.name];\n if (value == null) {\n return null;\n }\n if (value instanceof FragnoId) {\n return value.externalId;\n }\n if (typeof value === \"string\") {\n return value;\n }\n if (typeof value === \"object\" && \"externalId\" in value) {\n const ext = (value as { externalId?: unknown }).externalId;\n if (typeof ext === \"string\") {\n return ext;\n }\n }\n return null;\n};\n\nconst buildOutputFromLofiRow = (\n row: InMemoryLofiRow,\n table: AnyTable,\n select: undefined | true | readonly string[],\n): Record<string, unknown> => {\n const output: Record<string, unknown> = {};\n const columnNames = select && select !== true ? select : (Object.keys(table.columns) as string[]);\n\n for (const columnName of columnNames) {\n const column = table.columns[columnName];\n if (!column || column.isHidden) {\n continue;\n }\n\n if (column.role === \"external-id\") {\n output[column.name] = new FragnoId({\n externalId: row.id,\n internalId: BigInt(row._lofi.internalId),\n version: row._lofi.version,\n });\n continue;\n }\n\n if (column.role === \"reference\") {\n const value = row._lofi.norm[column.name];\n output[column.name] =\n value === null || value === undefined\n ? null\n : FragnoReference.fromInternal(BigInt(value as number));\n continue;\n }\n\n if (column.role === \"internal-id\") {\n output[column.name] = BigInt(row._lofi.internalId);\n continue;\n }\n\n if (column.role === \"version\") {\n output[column.name] = row._lofi.version;\n continue;\n }\n\n output[column.name] = row.data[column.name];\n }\n\n return output;\n};\n\nconst mergeRowColumns = (\n baseRow: Record<string, unknown>,\n overlayRow: Record<string, unknown>,\n table: AnyTable,\n select: undefined | true | readonly string[],\n): Record<string, unknown> => {\n const merged: Record<string, unknown> = { ...baseRow };\n const columnNames = select && select !== true ? select : (Object.keys(table.columns) as string[]);\n\n for (const columnName of columnNames) {\n if (!table.columns[columnName]) {\n continue;\n }\n if (columnName in overlayRow) {\n merged[columnName] = overlayRow[columnName];\n }\n }\n\n return merged;\n};\n\nconst patchJoinRows = async <TSchema extends AnySchema>(options: {\n row: Record<string, unknown>;\n joins: CompiledJoin[] | undefined;\n overlay: InMemoryLofiAdapter;\n baseQuery: LofiQueryInterface<TSchema>;\n schemaName: string;\n baseRowsCache: Map<string, Record<string, unknown>[]>;\n}): Promise<void> => {\n const { row, joins, overlay, baseQuery, schemaName, baseRowsCache } = options;\n if (!joins || joins.length === 0) {\n return;\n }\n\n for (const join of joins) {\n if (join.options === false) {\n continue;\n }\n\n const joinOptions = join.options;\n const relationName = join.relation.name;\n let target = row[relationName];\n\n if (target !== undefined && target !== null) {\n const canCheck = (targetRow: Record<string, unknown>) =>\n join.relation.on.every(([, right]) => right in targetRow);\n\n const needsRefresh = Array.isArray(target)\n ? target.some(\n (entry) =>\n entry &&\n typeof entry === \"object\" &&\n canCheck(entry as Record<string, unknown>) &&\n !matchesJoinOn({\n parentRow: row,\n targetRow: entry as Record<string, unknown>,\n join,\n overlay,\n schemaName,\n }),\n )\n : typeof target === \"object\" &&\n canCheck(target as Record<string, unknown>) &&\n !matchesJoinOn({\n parentRow: row,\n targetRow: target as Record<string, unknown>,\n join,\n overlay,\n schemaName,\n });\n\n if (needsRefresh) {\n delete row[relationName];\n target = undefined;\n }\n }\n\n if (target === undefined || target === null || (Array.isArray(target) && target.length === 0)) {\n const baseMatches = await loadBaseJoinMatches({\n parentRow: row,\n join,\n overlay,\n baseQuery,\n schemaName,\n baseRowsCache,\n });\n if (baseMatches.length > 0) {\n target = join.relation.type === \"many\" ? baseMatches : baseMatches[0];\n row[relationName] = target;\n }\n }\n\n if (target === undefined || target === null) {\n continue;\n }\n\n const patchTarget = async (\n targetRow: Record<string, unknown>,\n ): Promise<Record<string, unknown> | null> => {\n const targetTable = join.relation.table;\n const externalId = getExternalIdFromRow(targetRow, targetTable);\n if (!externalId) {\n await patchJoinRows({\n row: targetRow,\n joins: joinOptions.join,\n overlay,\n baseQuery,\n schemaName,\n baseRowsCache,\n });\n return targetRow;\n }\n\n if (overlay.store.hasTombstone(schemaName, targetTable.name, externalId)) {\n return null;\n }\n\n let mergedTarget = targetRow;\n const overlayRow = overlay.store.getRow(schemaName, targetTable.name, externalId);\n if (overlayRow) {\n const overlayOutput = buildOutputFromLofiRow(\n overlayRow,\n targetTable,\n joinOptions.select as undefined | true | readonly string[],\n );\n mergedTarget = mergeRowColumns(\n targetRow,\n overlayOutput,\n targetTable,\n joinOptions.select as undefined | true | readonly string[],\n );\n }\n\n await patchJoinRows({\n row: mergedTarget,\n joins: joinOptions.join,\n overlay,\n baseQuery,\n schemaName,\n baseRowsCache,\n });\n\n return mergedTarget;\n };\n\n if (Array.isArray(target)) {\n const next: Record<string, unknown>[] = [];\n for (const entry of target) {\n if (entry && typeof entry === \"object\") {\n const patched = await patchTarget(entry as Record<string, unknown>);\n if (patched) {\n next.push(patched);\n }\n }\n }\n row[relationName] = next;\n continue;\n }\n\n if (typeof target === \"object\") {\n const patched = await patchTarget(target as Record<string, unknown>);\n if (!patched) {\n delete row[relationName];\n } else {\n row[relationName] = patched;\n }\n }\n }\n};\n\nasync function loadBaseJoinMatches<TSchema extends AnySchema>(options: {\n parentRow: Record<string, unknown>;\n join: CompiledJoin;\n overlay: InMemoryLofiAdapter;\n baseQuery: LofiQueryInterface<TSchema>;\n schemaName: string;\n baseRowsCache: Map<string, Record<string, unknown>[]>;\n}): Promise<Record<string, unknown>[]> {\n const { parentRow, join, overlay, baseQuery, schemaName, baseRowsCache } = options;\n if (join.options === false) {\n return [];\n }\n\n const joinOptions = join.options;\n const targetTable = join.relation.table;\n let baseRows = baseRowsCache.get(targetTable.name);\n if (!baseRows) {\n baseRows = (await baseQuery.find(targetTable.name, (b) => b.whereIndex(\"primary\"))) as Record<\n string,\n unknown\n >[];\n baseRowsCache.set(targetTable.name, baseRows);\n }\n\n const matches: Record<string, unknown>[] = [];\n for (const baseRow of baseRows) {\n if (!matchesJoinOn({ parentRow, targetRow: baseRow, join, overlay, schemaName })) {\n continue;\n }\n if (joinOptions.where && !evaluateCondition(joinOptions.where as Condition, baseRow)) {\n continue;\n }\n matches.push({ ...baseRow });\n }\n\n let ordered = orderJoinRows(matches, joinOptions.orderBy);\n if (joinOptions.limit !== undefined) {\n ordered = ordered.slice(0, Math.max(0, joinOptions.limit));\n }\n\n for (const row of ordered) {\n await patchJoinRows({\n row,\n joins: joinOptions.join,\n overlay,\n baseQuery,\n schemaName,\n baseRowsCache,\n });\n }\n\n if (joinOptions.select && joinOptions.select !== true) {\n return ordered.map((row) =>\n stripSelection({\n row,\n table: targetTable,\n select: joinOptions.select as undefined | true | readonly string[],\n joins: joinOptions.join,\n }),\n );\n }\n\n if (joinOptions.join && joinOptions.join.length > 0) {\n return ordered.map((row) =>\n stripSelection({\n row,\n table: targetTable,\n select: joinOptions.select as undefined | true | readonly string[],\n joins: joinOptions.join,\n }),\n );\n }\n\n return ordered;\n}\n\nconst stripSelection = (options: {\n row: Record<string, unknown>;\n table: AnyTable;\n select: undefined | true | readonly string[];\n joins: CompiledJoin[] | undefined;\n}): Record<string, unknown> => {\n const { row, table, select, joins } = options;\n const stripped: Record<string, unknown> = { ...row };\n\n if (select && select !== true) {\n const keep = new Set(select);\n for (const key of Object.keys(stripped)) {\n if (table.columns[key] && !keep.has(key)) {\n delete stripped[key];\n }\n }\n }\n\n if (joins) {\n for (const join of joins) {\n if (join.options === false) {\n continue;\n }\n const joinOptions = join.options;\n const relationName = join.relation.name;\n const child = stripped[relationName];\n if (!child) {\n continue;\n }\n if (Array.isArray(child)) {\n stripped[relationName] = child.map((entry) =>\n stripSelection({\n row: entry as Record<string, unknown>,\n table: join.relation.table,\n select: joinOptions.select as undefined | true | readonly string[],\n joins: joinOptions.join,\n }),\n );\n } else if (typeof child === \"object\") {\n stripped[relationName] = stripSelection({\n row: child as Record<string, unknown>,\n table: join.relation.table,\n select: joinOptions.select as undefined | true | readonly string[],\n joins: joinOptions.join,\n });\n }\n }\n }\n\n return stripped;\n};\n\nconst augmentSelect = (options: {\n table: AnyTable;\n select: undefined | true | readonly string[];\n orderColumns: AnyColumn[];\n}): undefined | true | readonly string[] => {\n const { table, select, orderColumns } = options;\n if (!select || select === true) {\n return select;\n }\n\n const augmented = new Set(select);\n augmented.add(table.getIdColumn().name);\n for (const column of orderColumns) {\n augmented.add(column.name);\n }\n\n const result = Array.from(augmented);\n return result.length === select.length ? select : result;\n};\n\nconst mergeRows = (options: {\n baseRows: Record<string, unknown>[];\n overlayRows: Record<string, unknown>[];\n overlay: InMemoryLofiAdapter;\n schemaName: string;\n table: AnyTable;\n select: undefined | true | readonly string[];\n}): Record<string, unknown>[] => {\n const { baseRows, overlayRows, overlay, schemaName, table, select } = options;\n const baseIds = new Set<string>();\n const merged: Record<string, unknown>[] = [];\n\n const overlayOutputById = new Map<string, Record<string, unknown>>();\n\n for (const baseRow of baseRows) {\n const externalId = getExternalIdFromRow(baseRow, table);\n if (!externalId) {\n continue;\n }\n baseIds.add(externalId);\n\n if (overlay.store.hasTombstone(schemaName, table.name, externalId)) {\n continue;\n }\n\n const overlayRow = overlay.store.getRow(schemaName, table.name, externalId);\n if (overlayRow) {\n let overlayOutput = overlayOutputById.get(externalId);\n if (!overlayOutput) {\n overlayOutput = buildOutputFromLofiRow(overlayRow, table, select);\n overlayOutputById.set(externalId, overlayOutput);\n }\n merged.push(mergeRowColumns(baseRow, overlayOutput, table, select));\n continue;\n }\n\n merged.push(baseRow);\n }\n\n for (const overlayRow of overlayRows) {\n const externalId = getExternalIdFromRow(overlayRow, table);\n if (!externalId || baseIds.has(externalId)) {\n continue;\n }\n if (overlay.store.hasTombstone(schemaName, table.name, externalId)) {\n continue;\n }\n merged.push(overlayRow);\n }\n\n return merged;\n};\n\nexport const createStackedQueryEngine = <T extends AnySchema>(options: {\n schema: T;\n base: LofiQueryableAdapter;\n overlay: InMemoryLofiAdapter;\n schemaName?: string;\n}): LofiQueryInterface<T> => {\n const schemaName = options.schemaName ?? options.schema.name;\n const baseQuery = options.base.createQueryEngine(options.schema, { schemaName });\n const overlayQuery = options.overlay.createQueryEngine(options.schema, { schemaName });\n\n const runFind = async (\n tableName: string,\n builderFn: ((builder: FindBuilder<AnyTable>) => unknown) | undefined,\n withCursor: boolean,\n ): Promise<Record<string, unknown>[] | CursorResult<Record<string, unknown>> | number> => {\n const tableMap = options.schema.tables as Record<string, AnyTable>;\n const table = tableMap[tableName];\n if (!table) {\n throw new Error(`Table ${tableName} not found in schema`);\n }\n\n const baseFind = baseQuery.find as unknown as (\n name: string,\n builder: (builder: FindBuilder<AnyTable>) => unknown,\n ) => Promise<Record<string, unknown>[] | number>;\n const baseFindWithCursor = baseQuery.findWithCursor as unknown as (\n name: string,\n builder: (builder: FindBuilder<AnyTable>) => unknown,\n ) => Promise<CursorResult<Record<string, unknown>>>;\n const overlayFind = overlayQuery.find as unknown as (\n name: string,\n builder: (builder: FindBuilder<AnyTable>) => unknown,\n ) => Promise<Record<string, unknown>[] | number>;\n\n const builder = buildFindBuilder(tableName, table);\n if (builderFn) {\n builderFn(builder);\n } else {\n builder.whereIndex(\"primary\");\n }\n\n const built = builder.build() as\n | { type: \"find\"; indexName: string; options: BuiltFindOptions }\n | { type: \"count\"; indexName: string; options: Pick<BuiltFindOptions, \"where\"> };\n\n if (built.type === \"count\") {\n const countBuilder = (b: FindBuilder<AnyTable>) => {\n if (!built.options.where) {\n b.whereIndex(built.indexName as \"primary\");\n return;\n }\n if (typeof built.options.where === \"function\") {\n b.whereIndex(built.indexName as \"primary\", built.options.where as () => Condition);\n return;\n }\n b.whereIndex(built.indexName as \"primary\", () => built.options.where as Condition);\n };\n\n const baseRows = (await baseFind(tableName, countBuilder)) as Record<string, unknown>[];\n const overlayRows = (await overlayFind(tableName, countBuilder)) as Record<string, unknown>[];\n\n const merged = mergeRows({\n baseRows,\n overlayRows,\n overlay: options.overlay,\n schemaName,\n table,\n select: undefined,\n });\n\n return merged.length;\n }\n\n const orderIndexName = built.options.orderByIndex?.indexName ?? built.indexName;\n const direction = built.options.orderByIndex?.direction ?? \"asc\";\n const orderColumns = buildOrderColumns(table, orderIndexName);\n const originalSelect = built.options.select as undefined | true | readonly string[];\n const select = augmentSelect({\n table,\n select: originalSelect,\n orderColumns,\n });\n\n const overlayPageSize = built.options.pageSize;\n const overlayBuilder = (b: FindBuilder<AnyTable>) => {\n if (builderFn) {\n builderFn(b);\n } else {\n b.whereIndex(\"primary\");\n }\n if (select && select !== originalSelect) {\n b.select(select as unknown as true | string[]);\n }\n if (overlayPageSize !== undefined) {\n b.pageSize(overlayPageSize);\n }\n };\n\n const overlayRows = (await overlayFind(tableName, overlayBuilder)) as Record<string, unknown>[];\n\n const baseRows: Record<string, unknown>[] = [];\n let mergedRows: Record<string, unknown>[] = [];\n let hasNextBase = false;\n let cursor: Cursor | undefined;\n\n const pageSize = built.options.pageSize;\n if (pageSize === undefined) {\n const baseBuilder = (b: FindBuilder<AnyTable>) => {\n if (builderFn) {\n builderFn(b);\n } else {\n b.whereIndex(\"primary\");\n }\n if (select && select !== originalSelect) {\n b.select(select as unknown as true | string[]);\n }\n };\n\n baseRows.push(...((await baseFind(tableName, baseBuilder)) as Record<string, unknown>[]));\n } else {\n let nextAfter: Cursor | string | undefined = built.options.after;\n let nextBefore: Cursor | string | undefined = built.options.before;\n let firstPage = true;\n let keepFetching = true;\n\n while (keepFetching) {\n const baseBuilder = (b: FindBuilder<AnyTable>) => {\n if (builderFn) {\n builderFn(b);\n } else {\n b.whereIndex(\"primary\");\n }\n if (select && select !== originalSelect) {\n b.select(select as unknown as true | string[]);\n }\n b.pageSize(pageSize);\n if (firstPage) {\n if (nextAfter) {\n b.after(nextAfter);\n }\n if (nextBefore) {\n b.before(nextBefore);\n }\n } else if (cursor) {\n b.after(cursor);\n }\n };\n\n const page = (await baseFindWithCursor(tableName, baseBuilder)) as CursorResult<\n Record<string, unknown>\n >;\n\n baseRows.push(...page.items);\n hasNextBase = page.hasNextPage;\n cursor = page.cursor;\n nextAfter = undefined;\n nextBefore = undefined;\n firstPage = false;\n\n mergedRows = mergeRows({\n baseRows,\n overlayRows,\n overlay: options.overlay,\n schemaName,\n table,\n select,\n });\n\n mergedRows = mergedRows.sort((left, right) =>\n compareRows(left, right, orderColumns, direction),\n );\n mergedRows = applyCursorFilters({\n rows: mergedRows,\n orderColumns,\n direction,\n after: built.options.after,\n before: built.options.before,\n });\n\n if (!pageSize || mergedRows.length > pageSize || !hasNextBase) {\n break;\n }\n }\n }\n\n if (pageSize === undefined || baseRows.length > 0) {\n mergedRows = mergeRows({\n baseRows,\n overlayRows,\n overlay: options.overlay,\n schemaName,\n table,\n select,\n });\n }\n\n mergedRows = mergedRows.sort((left, right) =>\n compareRows(left, right, orderColumns, direction),\n );\n mergedRows = applyCursorFilters({\n rows: mergedRows,\n orderColumns,\n direction,\n after: built.options.after,\n before: built.options.before,\n });\n\n const baseRowsCache = new Map<string, Record<string, unknown>[]>();\n for (const row of mergedRows) {\n await patchJoinRows({\n row,\n joins: built.options.joins,\n overlay: options.overlay,\n baseQuery,\n schemaName,\n baseRowsCache,\n });\n }\n\n if (!withCursor) {\n const limited =\n pageSize !== undefined ? mergedRows.slice(0, Math.max(0, pageSize)) : mergedRows;\n return limited.map((row) =>\n stripSelection({\n row,\n table,\n select: originalSelect,\n joins: built.options.joins,\n }),\n );\n }\n\n let hasNextPage = false;\n let items = mergedRows;\n\n if (pageSize && mergedRows.length > pageSize) {\n hasNextPage = true;\n items = mergedRows.slice(0, pageSize);\n }\n\n let nextCursor: Cursor | undefined;\n const lastRow = items[items.length - 1];\n if (lastRow && pageSize) {\n nextCursor = createCursorFromRecord(lastRow, orderColumns, {\n indexName: orderIndexName,\n orderDirection: direction,\n pageSize,\n });\n }\n\n return {\n items: items.map((row) =>\n stripSelection({\n row,\n table,\n select: originalSelect,\n joins: built.options.joins,\n }),\n ),\n cursor: nextCursor,\n hasNextPage,\n };\n };\n\n return {\n async find(tableName, builderFn) {\n const result = await runFind(\n tableName,\n builderFn as unknown as (builder: FindBuilder<AnyTable>) => unknown,\n false,\n );\n return result as Record<string, unknown>[] | number;\n },\n\n async findWithCursor(tableName, builderFn) {\n const result = await runFind(\n tableName,\n builderFn as unknown as (builder: FindBuilder<AnyTable>) => unknown,\n true,\n );\n return result as CursorResult<Record<string, unknown>>;\n },\n\n async findFirst(tableName, builderFn) {\n const result = await runFind(\n tableName,\n builderFn\n ? (builder: FindBuilder<AnyTable>) => {\n (builderFn as unknown as (b: FindBuilder<AnyTable>) => unknown)(builder);\n builder.pageSize(1);\n return builder;\n }\n : (builder: FindBuilder<AnyTable>) => builder.whereIndex(\"primary\").pageSize(1),\n false,\n );\n\n if (typeof result === \"number\") {\n return null;\n }\n\n return (result as Record<string, unknown>[])[0] ?? null;\n },\n } as LofiQueryInterface<T>;\n};\n"],"mappings":"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"}
@@ -4,9 +4,9 @@ import { LofiSubmitClient } from "../submit/client.js";
4
4
  import "../mod.js";
5
5
  import { buildOutboxUrl, coerceNumber, deriveEndpointName, formatError, installIndexedDbGlobals, sleep } from "./utils.js";
6
6
  import { openDB } from "idb";
7
- import { define } from "gunshi";
8
7
  import { resolve } from "node:path";
9
8
  import { pathToFileURL } from "node:url";
9
+ import { define } from "gunshi";
10
10
 
11
11
  //#region src/cli/client.ts
12
12
  const openDb = (name) => openDB(name);
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["submitClient: LofiSubmitClient<unknown> | undefined","lastSubmit: unknown","lastResult: { appliedEntries: number; lastVersionstamp?: string } | undefined","syncError: string | undefined"],"sources":["../../src/cli/client.ts"],"sourcesContent":["import { define } from \"gunshi\";\nimport { openDB, type IDBPDatabase } from \"idb\";\nimport { resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport { IndexedDbAdapter, LofiClient, LofiSubmitClient } from \"../mod.js\";\nimport type { LofiSchemaRegistration, LofiSubmitCommandDefinition } from \"../types.js\";\nimport { buildOutboxUrl, coerceNumber, deriveEndpointName, formatError, sleep } from \"./utils.js\";\nimport { installIndexedDbGlobals } from \"./utils.js\";\n\ntype ClientModule = {\n schemas?: LofiSchemaRegistration[] | Array<{ schema: unknown }> | unknown[];\n schema?: unknown;\n commands?: LofiSubmitCommandDefinition[];\n createCommandContext?: (command: LofiSubmitCommandDefinition<unknown, unknown>) => unknown;\n endpointName?: string;\n};\n\ntype LofiRow = {\n endpoint: string;\n schema: string;\n table: string;\n id: string;\n data: Record<string, unknown>;\n _lofi: {\n versionstamp: string;\n norm: Record<string, unknown>;\n internalId: number;\n version: number;\n };\n};\n\nconst openDb = (name: string): Promise<IDBPDatabase> => openDB(name);\n\nconst getAllRows = async (db: IDBPDatabase): Promise<LofiRow[]> => {\n const tx = db.transaction(\"lofi_rows\", \"readonly\");\n const store = tx.objectStore(\"lofi_rows\");\n const rows = (await store.getAll()) as LofiRow[];\n await tx.done;\n return rows;\n};\n\nconst normalizeSchemas = (value: unknown) => {\n if (!value) {\n return undefined;\n }\n if (Array.isArray(value)) {\n return value.map((entry) => {\n if (entry && typeof entry === \"object\" && \"schema\" in entry) {\n return entry as LofiSchemaRegistration;\n }\n return { schema: entry } as LofiSchemaRegistration;\n });\n }\n if (value && typeof value === \"object\" && \"schema\" in value) {\n return [value as LofiSchemaRegistration];\n }\n return [{ schema: value } as LofiSchemaRegistration];\n};\n\nconst resolveModule = async (modulePath: string): Promise<ClientModule> => {\n const url = pathToFileURL(resolve(process.cwd(), modulePath)).href;\n const mod = (await import(url)) as ClientModule & {\n default?: ClientModule;\n };\n return (mod.default ?? mod) as ClientModule;\n};\n\nconst parseJsonInput = (raw: string | undefined) => {\n if (!raw) {\n return undefined;\n }\n try {\n return JSON.parse(raw);\n } catch (error) {\n throw new Error(`Failed to parse JSON input: ${String(error)}`);\n }\n};\n\nexport const clientCommand = define({\n name: \"client\",\n description: \"Run a Lofi client to sync and submit commands\",\n args: {\n endpoint: {\n type: \"string\" as const,\n short: \"e\" as const,\n description: \"Fragment base URL or outbox URL\",\n required: true as const,\n },\n timeout: {\n type: \"number\" as const,\n short: \"t\" as const,\n description: \"Seconds to run (default: 5)\",\n },\n pollInterval: {\n type: \"number\" as const,\n short: \"i\" as const,\n description: \"Poll interval in ms (default: 1000)\",\n },\n limit: {\n type: \"number\" as const,\n description: \"Outbox page size (default: 500)\",\n },\n endpointName: {\n type: \"string\" as const,\n description: \"Override local endpoint name\",\n },\n module: {\n type: \"string\" as const,\n description: \"Path to a client module that exports schemas/commands\",\n required: true as const,\n },\n command: {\n type: \"string\" as const,\n description: \"Command name to queue before syncing\",\n },\n input: {\n type: \"string\" as const,\n description: \"JSON input payload for the queued command\",\n },\n submit: {\n type: \"boolean\" as const,\n description: \"Submit queued commands before syncing\",\n },\n \"no-optimistic\": {\n type: \"boolean\" as const,\n description: \"Disable optimistic execution for queued commands\",\n },\n },\n run: async (ctx) => {\n const endpoint = ctx.values[\"endpoint\"] as string;\n const timeoutSeconds = coerceNumber(ctx.values[\"timeout\"], 5);\n const pollIntervalMs = coerceNumber(ctx.values[\"pollInterval\"], 1000);\n const limit = coerceNumber(ctx.values[\"limit\"], 500);\n const endpointOverride = ctx.values[\"endpointName\"] as string | undefined;\n const modulePath = ctx.values[\"module\"] as string | undefined;\n const commandName = ctx.values[\"command\"] as string | undefined;\n const inputRaw = ctx.values[\"input\"] as string | undefined;\n const submitQueued = Boolean(ctx.values[\"submit\"]);\n const optimistic = !ctx.values[\"no-optimistic\"];\n\n if (timeoutSeconds < 0) {\n throw new Error(\"timeout must be a positive number\");\n }\n if (pollIntervalMs < 0) {\n throw new Error(\"pollInterval must be a positive number\");\n }\n if (limit < 1) {\n throw new Error(\"limit must be >= 1\");\n }\n\n installIndexedDbGlobals();\n\n const outboxUrl = buildOutboxUrl(endpoint);\n if (!modulePath) {\n throw new Error(\"Missing --module. Provide a client module that exports schemas.\");\n }\n\n const moduleConfig = await resolveModule(modulePath);\n const schemas = normalizeSchemas(moduleConfig.schemas ?? moduleConfig.schema);\n if (!schemas || schemas.length === 0) {\n throw new Error(\"Client module must export `schema` or `schemas`.\");\n }\n\n const endpointName =\n endpointOverride ?? moduleConfig?.endpointName ?? deriveEndpointName(outboxUrl);\n\n const dbName = `fragno_lofi_${endpointName}`;\n const adapter = new IndexedDbAdapter({\n dbName,\n endpointName,\n schemas,\n ignoreUnknownSchemas: true,\n });\n\n const client = new LofiClient({\n outboxUrl,\n endpointName,\n adapter,\n pollIntervalMs,\n limit,\n });\n\n let submitClient: LofiSubmitClient<unknown> | undefined;\n const commands = moduleConfig?.commands ?? [];\n if (commands.length > 0) {\n submitClient = new LofiSubmitClient({\n endpointName,\n submitUrl: outboxUrl.replace(/\\/_internal\\/outbox$/, \"/_internal/sync\"),\n internalUrl: outboxUrl.replace(/\\/_internal\\/outbox$/, \"/_internal\"),\n adapter,\n schemas: schemas.map((entry) => entry.schema) as AnySchema[],\n commands,\n ...(moduleConfig?.createCommandContext\n ? { createCommandContext: moduleConfig.createCommandContext }\n : {}),\n });\n }\n\n const parsedInput = parseJsonInput(inputRaw);\n let lastSubmit: unknown;\n\n if (commandName) {\n if (!submitClient) {\n throw new Error(\"Missing client module with commands for --command.\");\n }\n const command = commands.find((entry) => entry.name === commandName);\n if (!command) {\n throw new Error(`Unknown command: ${commandName}`);\n }\n\n await submitClient.queueCommand({\n name: command.name,\n target: command.target,\n input: parsedInput ?? {},\n optimistic,\n });\n }\n\n if (submitQueued) {\n if (!submitClient) {\n throw new Error(\"Missing client module with commands for --submit.\");\n }\n lastSubmit = await submitClient.submitOnce();\n }\n\n const deadline = Date.now() + timeoutSeconds * 1000;\n let lastResult: { appliedEntries: number; lastVersionstamp?: string } | undefined;\n let syncError: string | undefined;\n\n while (Date.now() < deadline) {\n try {\n lastResult = await client.syncOnce();\n } catch (error) {\n syncError = formatError(error);\n break;\n }\n const remaining = deadline - Date.now();\n if (remaining <= 0) {\n break;\n }\n if (pollIntervalMs > 0) {\n await sleep(Math.min(pollIntervalMs, remaining));\n }\n }\n\n const db = await openDb(dbName);\n const rows = await getAllRows(db);\n\n const output = {\n endpoint: outboxUrl,\n endpointName,\n appliedEntries: lastResult?.appliedEntries ?? 0,\n lastVersionstamp: lastResult?.lastVersionstamp,\n error: syncError,\n submit: lastSubmit,\n rows: rows.map((row) => ({\n endpoint: row.endpoint,\n schema: row.schema,\n table: row.table,\n id: row.id,\n data: row.data,\n meta: row._lofi,\n })),\n };\n\n console.log(JSON.stringify(output, null, 2));\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["submitClient: LofiSubmitClient<unknown> | undefined","lastSubmit: unknown","lastResult: { appliedEntries: number; lastVersionstamp?: string } | undefined","syncError: string | undefined"],"sources":["../../src/cli/client.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport { define } from \"gunshi\";\nimport { openDB, type IDBPDatabase } from \"idb\";\n\nimport { IndexedDbAdapter, LofiClient, LofiSubmitClient } from \"../mod.js\";\nimport type { LofiSchemaRegistration, LofiSubmitCommandDefinition } from \"../types.js\";\nimport { buildOutboxUrl, coerceNumber, deriveEndpointName, formatError, sleep } from \"./utils.js\";\nimport { installIndexedDbGlobals } from \"./utils.js\";\n\ntype ClientModule = {\n schemas?: LofiSchemaRegistration[] | Array<{ schema: unknown }> | unknown[];\n schema?: unknown;\n commands?: LofiSubmitCommandDefinition[];\n createCommandContext?: (command: LofiSubmitCommandDefinition<unknown, unknown>) => unknown;\n endpointName?: string;\n};\n\ntype LofiRow = {\n endpoint: string;\n schema: string;\n table: string;\n id: string;\n data: Record<string, unknown>;\n _lofi: {\n versionstamp: string;\n norm: Record<string, unknown>;\n internalId: number;\n version: number;\n };\n};\n\nconst openDb = (name: string): Promise<IDBPDatabase> => openDB(name);\n\nconst getAllRows = async (db: IDBPDatabase): Promise<LofiRow[]> => {\n const tx = db.transaction(\"lofi_rows\", \"readonly\");\n const store = tx.objectStore(\"lofi_rows\");\n const rows = (await store.getAll()) as LofiRow[];\n await tx.done;\n return rows;\n};\n\nconst normalizeSchemas = (value: unknown) => {\n if (!value) {\n return undefined;\n }\n if (Array.isArray(value)) {\n return value.map((entry) => {\n if (entry && typeof entry === \"object\" && \"schema\" in entry) {\n return entry as LofiSchemaRegistration;\n }\n return { schema: entry } as LofiSchemaRegistration;\n });\n }\n if (value && typeof value === \"object\" && \"schema\" in value) {\n return [value as LofiSchemaRegistration];\n }\n return [{ schema: value } as LofiSchemaRegistration];\n};\n\nconst resolveModule = async (modulePath: string): Promise<ClientModule> => {\n const url = pathToFileURL(resolve(process.cwd(), modulePath)).href;\n const mod = (await import(url)) as ClientModule & {\n default?: ClientModule;\n };\n return (mod.default ?? mod) as ClientModule;\n};\n\nconst parseJsonInput = (raw: string | undefined) => {\n if (!raw) {\n return undefined;\n }\n try {\n return JSON.parse(raw);\n } catch (error) {\n throw new Error(`Failed to parse JSON input: ${String(error)}`);\n }\n};\n\nexport const clientCommand = define({\n name: \"client\",\n description: \"Run a Lofi client to sync and submit commands\",\n args: {\n endpoint: {\n type: \"string\" as const,\n short: \"e\" as const,\n description: \"Fragment base URL or outbox URL\",\n required: true as const,\n },\n timeout: {\n type: \"number\" as const,\n short: \"t\" as const,\n description: \"Seconds to run (default: 5)\",\n },\n pollInterval: {\n type: \"number\" as const,\n short: \"i\" as const,\n description: \"Poll interval in ms (default: 1000)\",\n },\n limit: {\n type: \"number\" as const,\n description: \"Outbox page size (default: 500)\",\n },\n endpointName: {\n type: \"string\" as const,\n description: \"Override local endpoint name\",\n },\n module: {\n type: \"string\" as const,\n description: \"Path to a client module that exports schemas/commands\",\n required: true as const,\n },\n command: {\n type: \"string\" as const,\n description: \"Command name to queue before syncing\",\n },\n input: {\n type: \"string\" as const,\n description: \"JSON input payload for the queued command\",\n },\n submit: {\n type: \"boolean\" as const,\n description: \"Submit queued commands before syncing\",\n },\n \"no-optimistic\": {\n type: \"boolean\" as const,\n description: \"Disable optimistic execution for queued commands\",\n },\n },\n run: async (ctx) => {\n const endpoint = ctx.values[\"endpoint\"] as string;\n const timeoutSeconds = coerceNumber(ctx.values[\"timeout\"], 5);\n const pollIntervalMs = coerceNumber(ctx.values[\"pollInterval\"], 1000);\n const limit = coerceNumber(ctx.values[\"limit\"], 500);\n const endpointOverride = ctx.values[\"endpointName\"] as string | undefined;\n const modulePath = ctx.values[\"module\"] as string | undefined;\n const commandName = ctx.values[\"command\"] as string | undefined;\n const inputRaw = ctx.values[\"input\"] as string | undefined;\n const submitQueued = Boolean(ctx.values[\"submit\"]);\n const optimistic = !ctx.values[\"no-optimistic\"];\n\n if (timeoutSeconds < 0) {\n throw new Error(\"timeout must be a positive number\");\n }\n if (pollIntervalMs < 0) {\n throw new Error(\"pollInterval must be a positive number\");\n }\n if (limit < 1) {\n throw new Error(\"limit must be >= 1\");\n }\n\n installIndexedDbGlobals();\n\n const outboxUrl = buildOutboxUrl(endpoint);\n if (!modulePath) {\n throw new Error(\"Missing --module. Provide a client module that exports schemas.\");\n }\n\n const moduleConfig = await resolveModule(modulePath);\n const schemas = normalizeSchemas(moduleConfig.schemas ?? moduleConfig.schema);\n if (!schemas || schemas.length === 0) {\n throw new Error(\"Client module must export `schema` or `schemas`.\");\n }\n\n const endpointName =\n endpointOverride ?? moduleConfig?.endpointName ?? deriveEndpointName(outboxUrl);\n\n const dbName = `fragno_lofi_${endpointName}`;\n const adapter = new IndexedDbAdapter({\n dbName,\n endpointName,\n schemas,\n ignoreUnknownSchemas: true,\n });\n\n const client = new LofiClient({\n outboxUrl,\n endpointName,\n adapter,\n pollIntervalMs,\n limit,\n });\n\n let submitClient: LofiSubmitClient<unknown> | undefined;\n const commands = moduleConfig?.commands ?? [];\n if (commands.length > 0) {\n submitClient = new LofiSubmitClient({\n endpointName,\n submitUrl: outboxUrl.replace(/\\/_internal\\/outbox$/, \"/_internal/sync\"),\n internalUrl: outboxUrl.replace(/\\/_internal\\/outbox$/, \"/_internal\"),\n adapter,\n schemas: schemas.map((entry) => entry.schema) as AnySchema[],\n commands,\n ...(moduleConfig?.createCommandContext\n ? { createCommandContext: moduleConfig.createCommandContext }\n : {}),\n });\n }\n\n const parsedInput = parseJsonInput(inputRaw);\n let lastSubmit: unknown;\n\n if (commandName) {\n if (!submitClient) {\n throw new Error(\"Missing client module with commands for --command.\");\n }\n const command = commands.find((entry) => entry.name === commandName);\n if (!command) {\n throw new Error(`Unknown command: ${commandName}`);\n }\n\n await submitClient.queueCommand({\n name: command.name,\n target: command.target,\n input: parsedInput ?? {},\n optimistic,\n });\n }\n\n if (submitQueued) {\n if (!submitClient) {\n throw new Error(\"Missing client module with commands for --submit.\");\n }\n lastSubmit = await submitClient.submitOnce();\n }\n\n const deadline = Date.now() + timeoutSeconds * 1000;\n let lastResult: { appliedEntries: number; lastVersionstamp?: string } | undefined;\n let syncError: string | undefined;\n\n while (Date.now() < deadline) {\n try {\n lastResult = await client.syncOnce();\n } catch (error) {\n syncError = formatError(error);\n break;\n }\n const remaining = deadline - Date.now();\n if (remaining <= 0) {\n break;\n }\n if (pollIntervalMs > 0) {\n await sleep(Math.min(pollIntervalMs, remaining));\n }\n }\n\n const db = await openDb(dbName);\n const rows = await getAllRows(db);\n\n const output = {\n endpoint: outboxUrl,\n endpointName,\n appliedEntries: lastResult?.appliedEntries ?? 0,\n lastVersionstamp: lastResult?.lastVersionstamp,\n error: syncError,\n submit: lastSubmit,\n rows: rows.map((row) => ({\n endpoint: row.endpoint,\n schema: row.schema,\n table: row.table,\n id: row.id,\n data: row.data,\n meta: row._lofi,\n })),\n };\n\n console.log(JSON.stringify(output, null, 2));\n },\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":[],"mappings":";;iBAgGsB,GAAA,CAAA,GAAG"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":[],"mappings":";;iBAkGsB,GAAA,CAAA,GAAG"}
package/dist/cli/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { clientCommand } from "./client.js";
3
- import { serveCommand } from "./server.js";
4
3
  import { scenarioCommand } from "./scenario.js";
5
- import { cli } from "gunshi";
4
+ import { serveCommand } from "./server.js";
6
5
  import { readFileSync } from "node:fs";
7
6
  import { dirname, join } from "node:path";
8
7
  import { fileURLToPath } from "node:url";
8
+ import { cli } from "gunshi";
9
9
 
10
10
  //#region src/cli/index.ts
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["subCommands: Map<string, Command<Args>>"],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { cli } from \"gunshi\";\nimport type { Args, Command } from \"gunshi\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { clientCommand } from \"./client.js\";\nimport { serveCommand } from \"./server.js\";\nimport { scenarioCommand } from \"./scenario.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJson = JSON.parse(readFileSync(join(__dirname, \"../../package.json\"), \"utf-8\"));\nconst version = packageJson.version as string;\n\nconst subCommands: Map<string, Command<Args>> = new Map();\nsubCommands.set(\"client\", clientCommand as Command<Args>);\nsubCommands.set(\"serve\", serveCommand as Command<Args>);\nsubCommands.set(\"scenario\", scenarioCommand as Command<Args>);\n\nconst printMainHelp = () => {\n console.log(\"Fragno Lofi CLI\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-lofi <COMMAND>\");\n console.log(\" fragno-lofi --endpoint <url> --module <path> [options]\");\n console.log(\"\");\n console.log(\"COMMANDS:\");\n console.log(\" serve Start a local server\");\n console.log(\" client Run a client (sync + submit)\");\n console.log(\" scenario Run a multi-client scenario\");\n console.log(\"\");\n console.log(\"GLOBAL OPTIONS:\");\n console.log(\" -v, --version Print version\");\n console.log(\" -h, --help Show help\");\n console.log(\"\");\n console.log(\"EXAMPLES:\");\n console.log(\" fragno-lofi client --endpoint http://localhost:4100 --module ./client.ts\");\n console.log(\" fragno-lofi serve --module ./server.ts --port 4100\");\n console.log(\" fragno-lofi scenario --file ./scenario.ts\");\n console.log(\n \" fragno-lofi --endpoint http://localhost:3000/api/fragno-db-comment --module ./client.ts --timeout 5\",\n );\n};\n\nconst printClientHelp = () => {\n console.log(\"Lofi client\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-lofi client --endpoint <url> [options]\");\n console.log(\"\");\n console.log(\"OPTIONS:\");\n console.log(\" -e, --endpoint Fragment base URL or outbox URL (required)\");\n console.log(\" -t, --timeout Seconds to run (default: 5)\");\n console.log(\" -i, --poll-interval Poll interval in ms (default: 1000)\");\n console.log(\" --limit Outbox page size (default: 500)\");\n console.log(\" --endpoint-name Override local endpoint name\");\n console.log(\" --module Path to client module with schemas/commands (required)\");\n console.log(\" --command Queue a command by name before syncing\");\n console.log(\" --input JSON input payload for --command\");\n console.log(\" --submit Submit queued commands before syncing\");\n console.log(\" --no-optimistic Disable optimistic execution\");\n console.log(\"\");\n console.log(\"EXAMPLES:\");\n console.log(\n \" fragno-lofi client --endpoint http://localhost:3000/api/fragno-db-comment --module ./client.ts --timeout 5\",\n );\n};\n\nconst printServeHelp = () => {\n console.log(\"Lofi server\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-lofi serve --module <path> [options]\");\n console.log(\"\");\n console.log(\"OPTIONS:\");\n console.log(\" -m, --module Path to a server module exporting a fragment\");\n console.log(\" -p, --port Port to listen on (default: 4100)\");\n console.log(\"\");\n console.log(\"EXAMPLES:\");\n console.log(\" fragno-lofi serve --module ./server.ts --port 4100\");\n};\n\nconst printScenarioHelp = () => {\n console.log(\"Lofi scenario runner\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-lofi scenario --file <path>\");\n console.log(\"\");\n console.log(\"OPTIONS:\");\n console.log(\" -f, --file Path to a scenario module\");\n console.log(\"\");\n console.log(\"EXAMPLES:\");\n console.log(\" fragno-lofi scenario --file ./scenario.ts\");\n};\n\nexport async function run() {\n const args = process.argv.slice(2);\n\n if (!args.length || args[0] === \"--help\" || args[0] === \"-h\") {\n printMainHelp();\n return;\n }\n\n if (args[0] === \"--version\" || args[0] === \"-v\") {\n console.log(version);\n return;\n }\n\n const subCommandName = args[0];\n const subCommand = subCommands.get(subCommandName);\n\n if (subCommand) {\n const next = args[1];\n if (!next || next === \"--help\" || next === \"-h\") {\n if (subCommandName === \"client\") {\n printClientHelp();\n } else if (subCommandName === \"serve\") {\n printServeHelp();\n } else if (subCommandName === \"scenario\") {\n printScenarioHelp();\n }\n return;\n }\n if (next === \"--version\" || next === \"-v\") {\n console.log(version);\n return;\n }\n\n await cli(args.slice(1), subCommand, {\n name: `fragno-lofi ${subCommandName}`,\n version,\n });\n return;\n }\n\n if (subCommandName.startsWith(\"-\")) {\n await cli(args, clientCommand as Command<Args>, {\n name: \"fragno-lofi\",\n version,\n });\n return;\n }\n\n console.error(`Unknown command: ${subCommandName}`);\n console.log(\"\");\n console.log(\"Run 'fragno-lofi --help' for available commands.\");\n process.exit(1);\n}\n\nif (import.meta.main) {\n await run();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["subCommands: Map<string, Command<Args>>"],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { cli } from \"gunshi\";\nimport type { Args, Command } from \"gunshi\";\n\nimport { clientCommand } from \"./client.js\";\nimport { scenarioCommand } from \"./scenario.js\";\nimport { serveCommand } from \"./server.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageJson = JSON.parse(readFileSync(join(__dirname, \"../../package.json\"), \"utf-8\"));\nconst version = packageJson.version as string;\n\nconst subCommands: Map<string, Command<Args>> = new Map();\nsubCommands.set(\"client\", clientCommand as Command<Args>);\nsubCommands.set(\"serve\", serveCommand as Command<Args>);\nsubCommands.set(\"scenario\", scenarioCommand as Command<Args>);\n\nconst printMainHelp = () => {\n console.log(\"Fragno Lofi CLI\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-lofi <COMMAND>\");\n console.log(\" fragno-lofi --endpoint <url> --module <path> [options]\");\n console.log(\"\");\n console.log(\"COMMANDS:\");\n console.log(\" serve Start a local server\");\n console.log(\" client Run a client (sync + submit)\");\n console.log(\" scenario Run a multi-client scenario\");\n console.log(\"\");\n console.log(\"GLOBAL OPTIONS:\");\n console.log(\" -v, --version Print version\");\n console.log(\" -h, --help Show help\");\n console.log(\"\");\n console.log(\"EXAMPLES:\");\n console.log(\" fragno-lofi client --endpoint http://localhost:4100 --module ./client.ts\");\n console.log(\" fragno-lofi serve --module ./server.ts --port 4100\");\n console.log(\" fragno-lofi scenario --file ./scenario.ts\");\n console.log(\n \" fragno-lofi --endpoint http://localhost:3000/api/fragno-db-comment --module ./client.ts --timeout 5\",\n );\n};\n\nconst printClientHelp = () => {\n console.log(\"Lofi client\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-lofi client --endpoint <url> [options]\");\n console.log(\"\");\n console.log(\"OPTIONS:\");\n console.log(\" -e, --endpoint Fragment base URL or outbox URL (required)\");\n console.log(\" -t, --timeout Seconds to run (default: 5)\");\n console.log(\" -i, --poll-interval Poll interval in ms (default: 1000)\");\n console.log(\" --limit Outbox page size (default: 500)\");\n console.log(\" --endpoint-name Override local endpoint name\");\n console.log(\" --module Path to client module with schemas/commands (required)\");\n console.log(\" --command Queue a command by name before syncing\");\n console.log(\" --input JSON input payload for --command\");\n console.log(\" --submit Submit queued commands before syncing\");\n console.log(\" --no-optimistic Disable optimistic execution\");\n console.log(\"\");\n console.log(\"EXAMPLES:\");\n console.log(\n \" fragno-lofi client --endpoint http://localhost:3000/api/fragno-db-comment --module ./client.ts --timeout 5\",\n );\n};\n\nconst printServeHelp = () => {\n console.log(\"Lofi server\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-lofi serve --module <path> [options]\");\n console.log(\"\");\n console.log(\"OPTIONS:\");\n console.log(\" -m, --module Path to a server module exporting a fragment\");\n console.log(\" -p, --port Port to listen on (default: 4100)\");\n console.log(\"\");\n console.log(\"EXAMPLES:\");\n console.log(\" fragno-lofi serve --module ./server.ts --port 4100\");\n};\n\nconst printScenarioHelp = () => {\n console.log(\"Lofi scenario runner\");\n console.log(\"\");\n console.log(\"USAGE:\");\n console.log(\" fragno-lofi scenario --file <path>\");\n console.log(\"\");\n console.log(\"OPTIONS:\");\n console.log(\" -f, --file Path to a scenario module\");\n console.log(\"\");\n console.log(\"EXAMPLES:\");\n console.log(\" fragno-lofi scenario --file ./scenario.ts\");\n};\n\nexport async function run() {\n const args = process.argv.slice(2);\n\n if (!args.length || args[0] === \"--help\" || args[0] === \"-h\") {\n printMainHelp();\n return;\n }\n\n if (args[0] === \"--version\" || args[0] === \"-v\") {\n console.log(version);\n return;\n }\n\n const subCommandName = args[0];\n const subCommand = subCommands.get(subCommandName);\n\n if (subCommand) {\n const next = args[1];\n if (!next || next === \"--help\" || next === \"-h\") {\n if (subCommandName === \"client\") {\n printClientHelp();\n } else if (subCommandName === \"serve\") {\n printServeHelp();\n } else if (subCommandName === \"scenario\") {\n printScenarioHelp();\n }\n return;\n }\n if (next === \"--version\" || next === \"-v\") {\n console.log(version);\n return;\n }\n\n await cli(args.slice(1), subCommand, {\n name: `fragno-lofi ${subCommandName}`,\n version,\n });\n return;\n }\n\n if (subCommandName.startsWith(\"-\")) {\n await cli(args, clientCommand as Command<Args>, {\n name: \"fragno-lofi\",\n version,\n });\n return;\n }\n\n console.error(`Unknown command: ${subCommandName}`);\n console.log(\"\");\n console.log(\"Run 'fragno-lofi --help' for available commands.\");\n process.exit(1);\n}\n\nif (import.meta.main) {\n await run();\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { runScenario } from "../scenario.js";
2
2
  import { installIndexedDbGlobals } from "./utils.js";
3
- import { define } from "gunshi";
4
3
  import { resolve } from "node:path";
5
4
  import { pathToFileURL } from "node:url";
5
+ import { define } from "gunshi";
6
6
 
7
7
  //#region src/cli/scenario.ts
8
8
  const resolveScenarioModule = async (modulePath) => {
@@ -1 +1 @@
1
- {"version":3,"file":"scenario.js","names":[],"sources":["../../src/cli/scenario.ts"],"sourcesContent":["import { define } from \"gunshi\";\nimport { resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { runScenario, type ScenarioDefinition } from \"../scenario.js\";\nimport { installIndexedDbGlobals } from \"./utils.js\";\n\ntype ScenarioModule = {\n default?: ScenarioDefinition | (() => ScenarioDefinition | Promise<ScenarioDefinition>);\n scenario?: ScenarioDefinition | (() => ScenarioDefinition | Promise<ScenarioDefinition>);\n};\n\nconst resolveScenarioModule = async (modulePath: string): Promise<ScenarioDefinition> => {\n const url = pathToFileURL(resolve(process.cwd(), modulePath)).href;\n const mod = (await import(url)) as ScenarioModule;\n const candidate = mod.default ?? mod.scenario;\n\n if (!candidate) {\n throw new Error(\"Scenario module must export a default scenario.\");\n }\n\n if (typeof candidate === \"function\") {\n return await candidate();\n }\n\n return candidate;\n};\n\nexport const scenarioCommand = define({\n name: \"scenario\",\n description: \"Run a multi-client Lofi scenario\",\n args: {\n file: {\n type: \"string\" as const,\n short: \"f\" as const,\n description: \"Path to a scenario module\",\n required: true as const,\n },\n },\n run: async (ctx) => {\n const file = ctx.values[\"file\"] as string;\n installIndexedDbGlobals();\n\n const scenario = await resolveScenarioModule(file);\n const context = await runScenario(scenario);\n\n try {\n const output = {\n name: context.name,\n baseUrl: context.server.baseUrl,\n vars: context.vars,\n lastSubmit: context.lastSubmit,\n lastSync: context.lastSync,\n };\n console.log(JSON.stringify(output, null, 2));\n } finally {\n await context.cleanup();\n }\n },\n});\n"],"mappings":";;;;;;;AAWA,MAAM,wBAAwB,OAAO,eAAoD;CAEvF,MAAM,MAAO,MAAM,OADP,cAAc,QAAQ,QAAQ,KAAK,EAAE,WAAW,CAAC,CAAC;CAE9D,MAAM,YAAY,IAAI,WAAW,IAAI;AAErC,KAAI,CAAC,UACH,OAAM,IAAI,MAAM,kDAAkD;AAGpE,KAAI,OAAO,cAAc,WACvB,QAAO,MAAM,WAAW;AAG1B,QAAO;;AAGT,MAAa,kBAAkB,OAAO;CACpC,MAAM;CACN,aAAa;CACb,MAAM,EACJ,MAAM;EACJ,MAAM;EACN,OAAO;EACP,aAAa;EACb,UAAU;EACX,EACF;CACD,KAAK,OAAO,QAAQ;EAClB,MAAM,OAAO,IAAI,OAAO;AACxB,2BAAyB;EAGzB,MAAM,UAAU,MAAM,YADL,MAAM,sBAAsB,KAAK,CACP;AAE3C,MAAI;GACF,MAAM,SAAS;IACb,MAAM,QAAQ;IACd,SAAS,QAAQ,OAAO;IACxB,MAAM,QAAQ;IACd,YAAY,QAAQ;IACpB,UAAU,QAAQ;IACnB;AACD,WAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;YACpC;AACR,SAAM,QAAQ,SAAS;;;CAG5B,CAAC"}
1
+ {"version":3,"file":"scenario.js","names":[],"sources":["../../src/cli/scenario.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { define } from \"gunshi\";\n\nimport { runScenario, type ScenarioDefinition } from \"../scenario.js\";\nimport { installIndexedDbGlobals } from \"./utils.js\";\n\ntype ScenarioModule = {\n default?: ScenarioDefinition | (() => ScenarioDefinition | Promise<ScenarioDefinition>);\n scenario?: ScenarioDefinition | (() => ScenarioDefinition | Promise<ScenarioDefinition>);\n};\n\nconst resolveScenarioModule = async (modulePath: string): Promise<ScenarioDefinition> => {\n const url = pathToFileURL(resolve(process.cwd(), modulePath)).href;\n const mod = (await import(url)) as ScenarioModule;\n const candidate = mod.default ?? mod.scenario;\n\n if (!candidate) {\n throw new Error(\"Scenario module must export a default scenario.\");\n }\n\n if (typeof candidate === \"function\") {\n return await candidate();\n }\n\n return candidate;\n};\n\nexport const scenarioCommand = define({\n name: \"scenario\",\n description: \"Run a multi-client Lofi scenario\",\n args: {\n file: {\n type: \"string\" as const,\n short: \"f\" as const,\n description: \"Path to a scenario module\",\n required: true as const,\n },\n },\n run: async (ctx) => {\n const file = ctx.values[\"file\"] as string;\n installIndexedDbGlobals();\n\n const scenario = await resolveScenarioModule(file);\n const context = await runScenario(scenario);\n\n try {\n const output = {\n name: context.name,\n baseUrl: context.server.baseUrl,\n vars: context.vars,\n lastSubmit: context.lastSubmit,\n lastSync: context.lastSync,\n };\n console.log(JSON.stringify(output, null, 2));\n } finally {\n await context.cleanup();\n }\n },\n});\n"],"mappings":";;;;;;;AAaA,MAAM,wBAAwB,OAAO,eAAoD;CAEvF,MAAM,MAAO,MAAM,OADP,cAAc,QAAQ,QAAQ,KAAK,EAAE,WAAW,CAAC,CAAC;CAE9D,MAAM,YAAY,IAAI,WAAW,IAAI;AAErC,KAAI,CAAC,UACH,OAAM,IAAI,MAAM,kDAAkD;AAGpE,KAAI,OAAO,cAAc,WACvB,QAAO,MAAM,WAAW;AAG1B,QAAO;;AAGT,MAAa,kBAAkB,OAAO;CACpC,MAAM;CACN,aAAa;CACb,MAAM,EACJ,MAAM;EACJ,MAAM;EACN,OAAO;EACP,aAAa;EACb,UAAU;EACX,EACF;CACD,KAAK,OAAO,QAAQ;EAClB,MAAM,OAAO,IAAI,OAAO;AACxB,2BAAyB;EAGzB,MAAM,UAAU,MAAM,YADL,MAAM,sBAAsB,KAAK,CACP;AAE3C,MAAI;GACF,MAAM,SAAS;IACb,MAAM,QAAQ;IACd,SAAS,QAAQ,OAAO;IACxB,MAAM,QAAQ;IACd,YAAY,QAAQ;IACpB,UAAU,QAAQ;IACnB;AACD,WAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;YACpC;AACR,SAAM,QAAQ,SAAS;;;CAG5B,CAAC"}
@@ -2,9 +2,9 @@ import { InMemoryAdapter, withDatabase } from "@fragno-dev/db";
2
2
  import { createServer } from "node:http";
3
3
  import { defineFragment, instantiate } from "@fragno-dev/core";
4
4
  import { toNodeHandler } from "@fragno-dev/node";
5
- import { define } from "gunshi";
6
5
  import { resolve } from "node:path";
7
6
  import { pathToFileURL } from "node:url";
7
+ import { define } from "gunshi";
8
8
 
9
9
  //#region src/cli/server.ts
10
10
  const isDefinitionConfig = (value) => Boolean(value && typeof value === "object" && "fragmentName" in value && "schema" in value && "syncCommands" in value);
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","names":["resolve"],"sources":["../../src/cli/server.ts"],"sourcesContent":["import { define } from \"gunshi\";\nimport { createServer } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport { resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport {\n defineFragment,\n instantiate,\n type AnyFragnoInstantiatedFragment,\n type AnyRouteOrFactory,\n} from \"@fragno-dev/core\";\nimport { toNodeHandler } from \"@fragno-dev/node\";\nimport { InMemoryAdapter, withDatabase, type SyncCommandRegistry } from \"@fragno-dev/db\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\n\ntype ServerDefinitionConfig = {\n fragmentName: string;\n schema: AnySchema;\n syncCommands: SyncCommandRegistry;\n routes?: AnyRouteOrFactory[];\n};\n\ntype ServerModule = {\n default?: unknown;\n fragment?: AnyFragnoInstantiatedFragment;\n createFragment?: (options: {\n databaseAdapter: InMemoryAdapter;\n outbox: { enabled: boolean };\n }) => AnyFragnoInstantiatedFragment | Promise<AnyFragnoInstantiatedFragment>;\n server?: ServerDefinitionConfig;\n};\n\nconst isDefinitionConfig = (value: unknown): value is ServerDefinitionConfig =>\n Boolean(\n value &&\n typeof value === \"object\" &&\n \"fragmentName\" in value &&\n \"schema\" in value &&\n \"syncCommands\" in value,\n );\n\nconst isFragmentInstance = (value: unknown): value is AnyFragnoInstantiatedFragment =>\n Boolean(value && typeof value === \"object\" && \"handler\" in value && \"callRouteRaw\" in value);\n\nconst resolveServerModule = async (modulePath: string): Promise<ServerModule> => {\n const url = pathToFileURL(resolve(process.cwd(), modulePath)).href;\n const mod = (await import(url)) as ServerModule;\n return mod;\n};\n\nconst resolveFragment = async (\n modulePath: string,\n): Promise<{ fragment: AnyFragnoInstantiatedFragment; adapter?: InMemoryAdapter }> => {\n const mod = await resolveServerModule(modulePath);\n const candidates = [mod.default, mod.server, mod.fragment, mod.createFragment, mod] as unknown[];\n\n for (const candidate of candidates) {\n if (!candidate) {\n continue;\n }\n if (isDefinitionConfig(candidate)) {\n const adapter = new InMemoryAdapter({ idSeed: `lofi-cli-${candidate.fragmentName}` });\n const fragmentDef = defineFragment(candidate.fragmentName)\n .extend(withDatabase(candidate.schema))\n .withSyncCommands(candidate.syncCommands)\n .build();\n\n const fragment = instantiate(fragmentDef)\n .withConfig({})\n .withRoutes(candidate.routes ?? [])\n .withOptions({ databaseAdapter: adapter, outbox: { enabled: true } })\n .build();\n\n return { fragment, adapter };\n }\n\n if (isFragmentInstance(candidate)) {\n return { fragment: candidate };\n }\n\n if (typeof candidate === \"function\") {\n const adapter = new InMemoryAdapter({ idSeed: \"lofi-cli\" });\n const fragment = await candidate({ databaseAdapter: adapter, outbox: { enabled: true } });\n if (isFragmentInstance(fragment)) {\n return { fragment, adapter };\n }\n }\n }\n\n throw new Error(\"Unable to resolve server fragment from module.\");\n};\n\nexport const serveCommand = define({\n name: \"serve\",\n description: \"Start a local Fragno server for Lofi testing\",\n args: {\n module: {\n type: \"string\" as const,\n short: \"m\" as const,\n description: \"Path to a server module exporting a fragment\",\n required: true as const,\n },\n port: {\n type: \"number\" as const,\n short: \"p\" as const,\n description: \"Port to listen on (default: 4100)\",\n },\n },\n run: async (ctx) => {\n const modulePath = ctx.values[\"module\"] as string;\n const port = (ctx.values[\"port\"] as number | undefined) ?? 4100;\n\n const { fragment, adapter } = await resolveFragment(modulePath);\n const handler = toNodeHandler(fragment.handler.bind(fragment));\n const server = createServer(handler);\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, \"127.0.0.1\", () => resolve());\n });\n\n const address = server.address() as AddressInfo;\n const mountRoute =\n \"mountRoute\" in fragment && typeof fragment.mountRoute === \"string\"\n ? fragment.mountRoute\n : \"\";\n const baseUrl = `http://127.0.0.1:${address.port}${mountRoute}`;\n\n const cleanup = async () => {\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n if (adapter) {\n await adapter.close();\n }\n };\n\n let stopping = false;\n const stop = async () => {\n if (stopping) {\n return;\n }\n stopping = true;\n try {\n await cleanup();\n } catch (err) {\n console.error(\"Cleanup error:\", err);\n }\n process.exit(0);\n };\n\n process.on(\"SIGINT\", stop);\n process.on(\"SIGTERM\", stop);\n\n console.log(`Lofi server listening on ${baseUrl}`);\n },\n});\n"],"mappings":";;;;;;;;;AAgCA,MAAM,sBAAsB,UAC1B,QACE,SACE,OAAO,UAAU,YACjB,kBAAkB,SAClB,YAAY,SACZ,kBAAkB,MACrB;AAEH,MAAM,sBAAsB,UAC1B,QAAQ,SAAS,OAAO,UAAU,YAAY,aAAa,SAAS,kBAAkB,MAAM;AAE9F,MAAM,sBAAsB,OAAO,eAA8C;AAG/E,QADa,MAAM,OADP,cAAc,QAAQ,QAAQ,KAAK,EAAE,WAAW,CAAC,CAAC;;AAKhE,MAAM,kBAAkB,OACtB,eACoF;CACpF,MAAM,MAAM,MAAM,oBAAoB,WAAW;CACjD,MAAM,aAAa;EAAC,IAAI;EAAS,IAAI;EAAQ,IAAI;EAAU,IAAI;EAAgB;EAAI;AAEnF,MAAK,MAAM,aAAa,YAAY;AAClC,MAAI,CAAC,UACH;AAEF,MAAI,mBAAmB,UAAU,EAAE;GACjC,MAAM,UAAU,IAAI,gBAAgB,EAAE,QAAQ,YAAY,UAAU,gBAAgB,CAAC;AAYrF,UAAO;IAAE,UANQ,YALG,eAAe,UAAU,aAAa,CACvD,OAAO,aAAa,UAAU,OAAO,CAAC,CACtC,iBAAiB,UAAU,aAAa,CACxC,OAAO,CAE+B,CACtC,WAAW,EAAE,CAAC,CACd,WAAW,UAAU,UAAU,EAAE,CAAC,CAClC,YAAY;KAAE,iBAAiB;KAAS,QAAQ,EAAE,SAAS,MAAM;KAAE,CAAC,CACpE,OAAO;IAES;IAAS;;AAG9B,MAAI,mBAAmB,UAAU,CAC/B,QAAO,EAAE,UAAU,WAAW;AAGhC,MAAI,OAAO,cAAc,YAAY;GACnC,MAAM,UAAU,IAAI,gBAAgB,EAAE,QAAQ,YAAY,CAAC;GAC3D,MAAM,WAAW,MAAM,UAAU;IAAE,iBAAiB;IAAS,QAAQ,EAAE,SAAS,MAAM;IAAE,CAAC;AACzF,OAAI,mBAAmB,SAAS,CAC9B,QAAO;IAAE;IAAU;IAAS;;;AAKlC,OAAM,IAAI,MAAM,iDAAiD;;AAGnE,MAAa,eAAe,OAAO;CACjC,MAAM;CACN,aAAa;CACb,MAAM;EACJ,QAAQ;GACN,MAAM;GACN,OAAO;GACP,aAAa;GACb,UAAU;GACX;EACD,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,KAAK,OAAO,QAAQ;EAClB,MAAM,aAAa,IAAI,OAAO;EAC9B,MAAM,OAAQ,IAAI,OAAO,WAAkC;EAE3D,MAAM,EAAE,UAAU,YAAY,MAAM,gBAAgB,WAAW;EAE/D,MAAM,SAAS,aADC,cAAc,SAAS,QAAQ,KAAK,SAAS,CAAC,CAC1B;AAEpC,QAAM,IAAI,SAAe,WAAS,WAAW;AAC3C,UAAO,KAAK,SAAS,OAAO;AAC5B,UAAO,OAAO,MAAM,mBAAmBA,WAAS,CAAC;IACjD;EAEF,MAAM,UAAU,OAAO,SAAS;EAChC,MAAM,aACJ,gBAAgB,YAAY,OAAO,SAAS,eAAe,WACvD,SAAS,aACT;EACN,MAAM,UAAU,oBAAoB,QAAQ,OAAO;EAEnD,MAAM,UAAU,YAAY;AAC1B,SAAM,IAAI,SAAe,WAAS,WAAW;AAC3C,WAAO,OAAO,QAAQ;AACpB,SAAI,KAAK;AACP,aAAO,IAAI;AACX;;AAEF,gBAAS;MACT;KACF;AACF,OAAI,QACF,OAAM,QAAQ,OAAO;;EAIzB,IAAI,WAAW;EACf,MAAM,OAAO,YAAY;AACvB,OAAI,SACF;AAEF,cAAW;AACX,OAAI;AACF,UAAM,SAAS;YACR,KAAK;AACZ,YAAQ,MAAM,kBAAkB,IAAI;;AAEtC,WAAQ,KAAK,EAAE;;AAGjB,UAAQ,GAAG,UAAU,KAAK;AAC1B,UAAQ,GAAG,WAAW,KAAK;AAE3B,UAAQ,IAAI,4BAA4B,UAAU;;CAErD,CAAC"}
1
+ {"version":3,"file":"server.js","names":["resolve"],"sources":["../../src/cli/server.ts"],"sourcesContent":["import { createServer } from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport { resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport { define } from \"gunshi\";\n\nimport {\n defineFragment,\n instantiate,\n type AnyFragnoInstantiatedFragment,\n type AnyRouteOrFactory,\n} from \"@fragno-dev/core\";\nimport { InMemoryAdapter, withDatabase, type SyncCommandRegistry } from \"@fragno-dev/db\";\nimport { toNodeHandler } from \"@fragno-dev/node\";\n\ntype ServerDefinitionConfig = {\n fragmentName: string;\n schema: AnySchema;\n syncCommands: SyncCommandRegistry;\n routes?: AnyRouteOrFactory[];\n};\n\ntype ServerModule = {\n default?: unknown;\n fragment?: AnyFragnoInstantiatedFragment;\n createFragment?: (options: {\n databaseAdapter: InMemoryAdapter;\n outbox: { enabled: boolean };\n }) => AnyFragnoInstantiatedFragment | Promise<AnyFragnoInstantiatedFragment>;\n server?: ServerDefinitionConfig;\n};\n\nconst isDefinitionConfig = (value: unknown): value is ServerDefinitionConfig =>\n Boolean(\n value &&\n typeof value === \"object\" &&\n \"fragmentName\" in value &&\n \"schema\" in value &&\n \"syncCommands\" in value,\n );\n\nconst isFragmentInstance = (value: unknown): value is AnyFragnoInstantiatedFragment =>\n Boolean(value && typeof value === \"object\" && \"handler\" in value && \"callRouteRaw\" in value);\n\nconst resolveServerModule = async (modulePath: string): Promise<ServerModule> => {\n const url = pathToFileURL(resolve(process.cwd(), modulePath)).href;\n const mod = (await import(url)) as ServerModule;\n return mod;\n};\n\nconst resolveFragment = async (\n modulePath: string,\n): Promise<{ fragment: AnyFragnoInstantiatedFragment; adapter?: InMemoryAdapter }> => {\n const mod = await resolveServerModule(modulePath);\n const candidates = [mod.default, mod.server, mod.fragment, mod.createFragment, mod] as unknown[];\n\n for (const candidate of candidates) {\n if (!candidate) {\n continue;\n }\n if (isDefinitionConfig(candidate)) {\n const adapter = new InMemoryAdapter({ idSeed: `lofi-cli-${candidate.fragmentName}` });\n const fragmentDef = defineFragment(candidate.fragmentName)\n .extend(withDatabase(candidate.schema))\n .withSyncCommands(candidate.syncCommands)\n .build();\n\n const fragment = instantiate(fragmentDef)\n .withConfig({})\n .withRoutes(candidate.routes ?? [])\n .withOptions({ databaseAdapter: adapter, outbox: { enabled: true } })\n .build();\n\n return { fragment, adapter };\n }\n\n if (isFragmentInstance(candidate)) {\n return { fragment: candidate };\n }\n\n if (typeof candidate === \"function\") {\n const adapter = new InMemoryAdapter({ idSeed: \"lofi-cli\" });\n const fragment = await candidate({ databaseAdapter: adapter, outbox: { enabled: true } });\n if (isFragmentInstance(fragment)) {\n return { fragment, adapter };\n }\n }\n }\n\n throw new Error(\"Unable to resolve server fragment from module.\");\n};\n\nexport const serveCommand = define({\n name: \"serve\",\n description: \"Start a local Fragno server for Lofi testing\",\n args: {\n module: {\n type: \"string\" as const,\n short: \"m\" as const,\n description: \"Path to a server module exporting a fragment\",\n required: true as const,\n },\n port: {\n type: \"number\" as const,\n short: \"p\" as const,\n description: \"Port to listen on (default: 4100)\",\n },\n },\n run: async (ctx) => {\n const modulePath = ctx.values[\"module\"] as string;\n const port = (ctx.values[\"port\"] as number | undefined) ?? 4100;\n\n const { fragment, adapter } = await resolveFragment(modulePath);\n const handler = toNodeHandler(fragment.handler.bind(fragment));\n const server = createServer(handler);\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, \"127.0.0.1\", () => resolve());\n });\n\n const address = server.address() as AddressInfo;\n const mountRoute =\n \"mountRoute\" in fragment && typeof fragment.mountRoute === \"string\"\n ? fragment.mountRoute\n : \"\";\n const baseUrl = `http://127.0.0.1:${address.port}${mountRoute}`;\n\n const cleanup = async () => {\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n if (adapter) {\n await adapter.close();\n }\n };\n\n let stopping = false;\n const stop = async () => {\n if (stopping) {\n return;\n }\n stopping = true;\n try {\n await cleanup();\n } catch (err) {\n console.error(\"Cleanup error:\", err);\n }\n process.exit(0);\n };\n\n process.on(\"SIGINT\", stop);\n process.on(\"SIGTERM\", stop);\n\n console.log(`Lofi server listening on ${baseUrl}`);\n },\n});\n"],"mappings":";;;;;;;;;AAkCA,MAAM,sBAAsB,UAC1B,QACE,SACA,OAAO,UAAU,YACjB,kBAAkB,SAClB,YAAY,SACZ,kBAAkB,MACnB;AAEH,MAAM,sBAAsB,UAC1B,QAAQ,SAAS,OAAO,UAAU,YAAY,aAAa,SAAS,kBAAkB,MAAM;AAE9F,MAAM,sBAAsB,OAAO,eAA8C;AAG/E,QADa,MAAM,OADP,cAAc,QAAQ,QAAQ,KAAK,EAAE,WAAW,CAAC,CAAC;;AAKhE,MAAM,kBAAkB,OACtB,eACoF;CACpF,MAAM,MAAM,MAAM,oBAAoB,WAAW;CACjD,MAAM,aAAa;EAAC,IAAI;EAAS,IAAI;EAAQ,IAAI;EAAU,IAAI;EAAgB;EAAI;AAEnF,MAAK,MAAM,aAAa,YAAY;AAClC,MAAI,CAAC,UACH;AAEF,MAAI,mBAAmB,UAAU,EAAE;GACjC,MAAM,UAAU,IAAI,gBAAgB,EAAE,QAAQ,YAAY,UAAU,gBAAgB,CAAC;AAYrF,UAAO;IAAE,UANQ,YALG,eAAe,UAAU,aAAa,CACvD,OAAO,aAAa,UAAU,OAAO,CAAC,CACtC,iBAAiB,UAAU,aAAa,CACxC,OAAO,CAE+B,CACtC,WAAW,EAAE,CAAC,CACd,WAAW,UAAU,UAAU,EAAE,CAAC,CAClC,YAAY;KAAE,iBAAiB;KAAS,QAAQ,EAAE,SAAS,MAAM;KAAE,CAAC,CACpE,OAAO;IAES;IAAS;;AAG9B,MAAI,mBAAmB,UAAU,CAC/B,QAAO,EAAE,UAAU,WAAW;AAGhC,MAAI,OAAO,cAAc,YAAY;GACnC,MAAM,UAAU,IAAI,gBAAgB,EAAE,QAAQ,YAAY,CAAC;GAC3D,MAAM,WAAW,MAAM,UAAU;IAAE,iBAAiB;IAAS,QAAQ,EAAE,SAAS,MAAM;IAAE,CAAC;AACzF,OAAI,mBAAmB,SAAS,CAC9B,QAAO;IAAE;IAAU;IAAS;;;AAKlC,OAAM,IAAI,MAAM,iDAAiD;;AAGnE,MAAa,eAAe,OAAO;CACjC,MAAM;CACN,aAAa;CACb,MAAM;EACJ,QAAQ;GACN,MAAM;GACN,OAAO;GACP,aAAa;GACb,UAAU;GACX;EACD,MAAM;GACJ,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,KAAK,OAAO,QAAQ;EAClB,MAAM,aAAa,IAAI,OAAO;EAC9B,MAAM,OAAQ,IAAI,OAAO,WAAkC;EAE3D,MAAM,EAAE,UAAU,YAAY,MAAM,gBAAgB,WAAW;EAE/D,MAAM,SAAS,aADC,cAAc,SAAS,QAAQ,KAAK,SAAS,CAAC,CAC1B;AAEpC,QAAM,IAAI,SAAe,WAAS,WAAW;AAC3C,UAAO,KAAK,SAAS,OAAO;AAC5B,UAAO,OAAO,MAAM,mBAAmBA,WAAS,CAAC;IACjD;EAEF,MAAM,UAAU,OAAO,SAAS;EAChC,MAAM,aACJ,gBAAgB,YAAY,OAAO,SAAS,eAAe,WACvD,SAAS,aACT;EACN,MAAM,UAAU,oBAAoB,QAAQ,OAAO;EAEnD,MAAM,UAAU,YAAY;AAC1B,SAAM,IAAI,SAAe,WAAS,WAAW;AAC3C,WAAO,OAAO,QAAQ;AACpB,SAAI,KAAK;AACP,aAAO,IAAI;AACX;;AAEF,gBAAS;MACT;KACF;AACF,OAAI,QACF,OAAM,QAAQ,OAAO;;EAIzB,IAAI,WAAW;EACf,MAAM,OAAO,YAAY;AACvB,OAAI,SACF;AAEF,cAAW;AACX,OAAI;AACF,UAAM,SAAS;YACR,KAAK;AACZ,YAAQ,MAAM,kBAAkB,IAAI;;AAEtC,WAAQ,KAAK,EAAE;;AAGjB,UAAQ,GAAG,UAAU,KAAK;AAC1B,UAAQ,GAAG,WAAW,KAAK;AAE3B,UAAQ,IAAI,4BAA4B,UAAU;;CAErD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","names":[],"sources":["../../src/client/client.ts"],"sourcesContent":[],"mappings":";;;KAIK,eAAA;WAA6B;AAF8C,CAAA;AAkBnE,cAAA,UAAA,CAAU;EAkBA,iBAAA,OAAA;EAcM,iBAAA,SAAA;EAuDF,iBAAA,YAAA;EAA0B,iBAAA,OAAA;EAAR,iBAAA,cAAA;EAAO,iBAAA,KAAA;;;;;;;;;;uBArE7B;;aAcM;;;qBAuDF,kBAAkB,QAAQ"}
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../../src/client/client.ts"],"sourcesContent":[],"mappings":";;;KAKK,eAAA;WAA6B;AAF8C,CAAA;AAkBnE,cAAA,UAAA,CAAU;EAkBA,iBAAA,OAAA;EAcM,iBAAA,SAAA;EAuDF,iBAAA,YAAA;EAA0B,iBAAA,OAAA;EAAR,iBAAA,cAAA;EAAO,iBAAA,KAAA;;;;;;;;;;uBArE7B;;aAcM;;;qBAuDF,kBAAkB,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["lastVersionstamp: string | undefined","result"],"sources":["../../src/client/client.ts"],"sourcesContent":["import type { OutboxEntry, OutboxPayload } from \"@fragno-dev/db\";\nimport { decodeOutboxPayload, resolveOutboxRefs } from \"../outbox\";\nimport type { LofiClientOptions, LofiMutation, LofiSyncResult } from \"../types\";\n\ntype LofiSyncOptions = { signal?: AbortSignal };\n\ntype OutboxPageResult = {\n entries: OutboxEntry[];\n lastVersionstamp?: string;\n};\n\ntype OutboxFetchOptions = {\n cursor?: string;\n limit: number;\n signal: AbortSignal;\n};\n\nconst DEFAULT_POLL_INTERVAL_MS = 1000;\nconst DEFAULT_LIMIT = 500;\n\nexport class LofiClient {\n private readonly adapter: LofiClientOptions[\"adapter\"];\n private readonly outboxUrl: string;\n private readonly endpointName: string;\n private readonly fetcher: typeof fetch;\n private readonly pollIntervalMs: number;\n private readonly limit: number;\n private readonly cursorKey?: string;\n private readonly onSyncApplied?: (result: LofiSyncResult) => void | Promise<void>;\n private readonly onError?: (error: unknown) => void;\n private readonly defaultSignal?: AbortSignal;\n\n private intervalId: ReturnType<typeof setInterval> | undefined;\n private inFlight?: Promise<LofiSyncResult>;\n private inFlightController?: AbortController;\n private loopSignal?: AbortSignal;\n private loopSignalListener?: () => void;\n\n constructor(options: LofiClientOptions) {\n this.adapter = options.adapter;\n this.outboxUrl = options.outboxUrl;\n this.endpointName = options.endpointName;\n const defaultFetch = fetch.bind(globalThis);\n this.fetcher = options.fetch ?? defaultFetch;\n this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n this.limit = options.limit ?? DEFAULT_LIMIT;\n this.cursorKey = options.cursorKey;\n this.onSyncApplied = options.onSyncApplied;\n this.onError = options.onError;\n this.defaultSignal = options.signal;\n }\n\n start(options?: { signal?: AbortSignal }): void {\n if (this.intervalId) {\n return;\n }\n\n this.loopSignal = options?.signal ?? this.defaultSignal;\n\n if (this.loopSignal) {\n const abortListener = () => this.stop();\n this.loopSignal.addEventListener(\"abort\", abortListener, { once: true });\n this.loopSignalListener = () => this.loopSignal?.removeEventListener(\"abort\", abortListener);\n }\n\n void this.syncOnce({ signal: this.loopSignal }).catch((error) => {\n if (isAbortError(error)) {\n return;\n }\n\n this.handleLoopError(error);\n });\n\n this.intervalId = setInterval(() => {\n if (!this.intervalId) {\n return;\n }\n\n if (this.inFlight) {\n return;\n }\n\n void this.syncOnce({ signal: this.loopSignal }).catch((error) => {\n if (isAbortError(error)) {\n return;\n }\n\n this.handleLoopError(error);\n });\n }, this.pollIntervalMs);\n }\n\n stop(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = undefined;\n }\n\n if (this.loopSignalListener) {\n this.loopSignalListener();\n this.loopSignalListener = undefined;\n }\n\n this.loopSignal = undefined;\n this.inFlightController?.abort();\n }\n\n async syncOnce(options?: LofiSyncOptions): Promise<LofiSyncResult> {\n if (this.inFlight) {\n return this.inFlight;\n }\n\n const signal = options?.signal ?? this.defaultSignal;\n if (signal?.aborted) {\n return { appliedEntries: 0 };\n }\n\n const controller = new AbortController();\n this.inFlightController = controller;\n const cleanup = attachAbortSignal(controller, signal);\n\n const promise = this.runSyncOnce(controller.signal).finally(() => {\n cleanup();\n if (this.inFlight === promise) {\n this.inFlight = undefined;\n }\n if (this.inFlightController === controller) {\n this.inFlightController = undefined;\n }\n });\n\n this.inFlight = promise;\n return promise;\n }\n\n private async runSyncOnce(signal: AbortSignal): Promise<LofiSyncResult> {\n if (signal.aborted) {\n return { appliedEntries: 0 };\n }\n\n const cursorKey = this.cursorKey ?? `${this.endpointName}::outbox`;\n const sourceKey = cursorKey;\n let cursor = await this.adapter.getMeta(cursorKey);\n\n let appliedEntries = 0;\n let lastVersionstamp: string | undefined;\n\n try {\n for await (const page of this.fetchPages({ cursor, limit: this.limit, signal })) {\n if (page.entries.length === 0) {\n break;\n }\n\n for (const entry of page.entries) {\n if (signal.aborted) {\n return { appliedEntries, lastVersionstamp };\n }\n\n const mutations = decodeOutboxEntry(entry);\n const resolvedMutations = entry.refMap\n ? mutations.map((mutation) => resolveOutboxRefs(mutation, entry.refMap ?? {}))\n : mutations;\n\n const result = await this.adapter.applyOutboxEntry({\n sourceKey,\n versionstamp: entry.versionstamp,\n uowId: entry.uowId,\n mutations: resolvedMutations,\n });\n\n lastVersionstamp = entry.versionstamp;\n await this.adapter.setMeta(cursorKey, entry.versionstamp);\n cursor = entry.versionstamp;\n\n if (result.applied) {\n appliedEntries += 1;\n }\n\n if (signal.aborted) {\n return { appliedEntries, lastVersionstamp };\n }\n }\n\n if (page.entries.length < this.limit) {\n break;\n }\n }\n } catch (error) {\n if (isAbortError(error)) {\n return { appliedEntries, lastVersionstamp };\n }\n throw error;\n }\n\n const result = { appliedEntries, lastVersionstamp };\n\n if (appliedEntries > 0) {\n await this.onSyncApplied?.(result);\n }\n\n return result;\n }\n\n private async *fetchPages(options: OutboxFetchOptions): AsyncGenerator<OutboxPageResult> {\n let cursor = options.cursor;\n\n while (true) {\n if (options.signal.aborted) {\n return;\n }\n\n const url = buildOutboxUrl(this.outboxUrl, cursor, options.limit);\n const response = await this.fetcher(url, { signal: options.signal });\n\n if (!response.ok) {\n throw new Error(`Outbox request failed: ${response.status} ${response.statusText}`);\n }\n\n const data = (await response.json()) as OutboxEntry[];\n if (!Array.isArray(data)) {\n throw new Error(\"Invalid outbox response payload\");\n }\n\n const lastVersionstamp = data.at(-1)?.versionstamp;\n yield { entries: data, lastVersionstamp };\n\n if (data.length < options.limit || !lastVersionstamp) {\n return;\n }\n\n cursor = lastVersionstamp;\n }\n }\n\n private handleLoopError(error: unknown): void {\n this.stop();\n this.onError?.(error);\n }\n}\n\nfunction decodeOutboxEntry(entry: OutboxEntry): LofiMutation[] {\n const payload = decodeOutboxPayload(entry.payload);\n return payload.mutations.map((mutation) => toLofiMutation(mutation));\n}\n\nfunction toLofiMutation(mutation: OutboxPayload[\"mutations\"][number]): LofiMutation {\n if (mutation.op === \"create\") {\n return {\n op: \"create\",\n schema: mutation.schema,\n table: mutation.table,\n externalId: mutation.externalId,\n values: mutation.values,\n versionstamp: mutation.versionstamp,\n };\n }\n\n if (mutation.op === \"update\") {\n return {\n op: \"update\",\n schema: mutation.schema,\n table: mutation.table,\n externalId: mutation.externalId,\n set: mutation.set,\n versionstamp: mutation.versionstamp,\n };\n }\n\n return {\n op: \"delete\",\n schema: mutation.schema,\n table: mutation.table,\n externalId: mutation.externalId,\n versionstamp: mutation.versionstamp,\n };\n}\n\nfunction buildOutboxUrl(outboxUrl: string, afterVersionstamp: string | undefined, limit: number) {\n const url = new URL(outboxUrl);\n const params = new URLSearchParams(url.search);\n\n if (afterVersionstamp) {\n params.set(\"afterVersionstamp\", afterVersionstamp);\n } else {\n params.delete(\"afterVersionstamp\");\n }\n\n params.set(\"limit\", String(limit));\n url.search = params.toString();\n\n return url.toString();\n}\n\nfunction attachAbortSignal(controller: AbortController, signal?: AbortSignal) {\n if (!signal) {\n return () => undefined;\n }\n\n if (signal.aborted) {\n controller.abort(signal.reason);\n return () => undefined;\n }\n\n const abort = () => controller.abort(signal.reason);\n signal.addEventListener(\"abort\", abort, { once: true });\n return () => signal.removeEventListener(\"abort\", abort);\n}\n\nfunction isAbortError(error: unknown): boolean {\n if (!error || typeof error !== \"object\") {\n return false;\n }\n\n return \"name\" in error && error.name === \"AbortError\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["lastVersionstamp: string | undefined","result"],"sources":["../../src/client/client.ts"],"sourcesContent":["import type { OutboxEntry, OutboxPayload } from \"@fragno-dev/db\";\n\nimport { decodeOutboxPayload, resolveOutboxRefs } from \"../outbox\";\nimport type { LofiClientOptions, LofiMutation, LofiSyncResult } from \"../types\";\n\ntype LofiSyncOptions = { signal?: AbortSignal };\n\ntype OutboxPageResult = {\n entries: OutboxEntry[];\n lastVersionstamp?: string;\n};\n\ntype OutboxFetchOptions = {\n cursor?: string;\n limit: number;\n signal: AbortSignal;\n};\n\nconst DEFAULT_POLL_INTERVAL_MS = 1000;\nconst DEFAULT_LIMIT = 500;\n\nexport class LofiClient {\n private readonly adapter: LofiClientOptions[\"adapter\"];\n private readonly outboxUrl: string;\n private readonly endpointName: string;\n private readonly fetcher: typeof fetch;\n private readonly pollIntervalMs: number;\n private readonly limit: number;\n private readonly cursorKey?: string;\n private readonly onSyncApplied?: (result: LofiSyncResult) => void | Promise<void>;\n private readonly onError?: (error: unknown) => void;\n private readonly defaultSignal?: AbortSignal;\n\n private intervalId: ReturnType<typeof setInterval> | undefined;\n private inFlight?: Promise<LofiSyncResult>;\n private inFlightController?: AbortController;\n private loopSignal?: AbortSignal;\n private loopSignalListener?: () => void;\n\n constructor(options: LofiClientOptions) {\n this.adapter = options.adapter;\n this.outboxUrl = options.outboxUrl;\n this.endpointName = options.endpointName;\n const defaultFetch = fetch.bind(globalThis);\n this.fetcher = options.fetch ?? defaultFetch;\n this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n this.limit = options.limit ?? DEFAULT_LIMIT;\n this.cursorKey = options.cursorKey;\n this.onSyncApplied = options.onSyncApplied;\n this.onError = options.onError;\n this.defaultSignal = options.signal;\n }\n\n start(options?: { signal?: AbortSignal }): void {\n if (this.intervalId) {\n return;\n }\n\n this.loopSignal = options?.signal ?? this.defaultSignal;\n\n if (this.loopSignal) {\n const abortListener = () => this.stop();\n this.loopSignal.addEventListener(\"abort\", abortListener, { once: true });\n this.loopSignalListener = () => this.loopSignal?.removeEventListener(\"abort\", abortListener);\n }\n\n void this.syncOnce({ signal: this.loopSignal }).catch((error) => {\n if (isAbortError(error)) {\n return;\n }\n\n this.handleLoopError(error);\n });\n\n this.intervalId = setInterval(() => {\n if (!this.intervalId) {\n return;\n }\n\n if (this.inFlight) {\n return;\n }\n\n void this.syncOnce({ signal: this.loopSignal }).catch((error) => {\n if (isAbortError(error)) {\n return;\n }\n\n this.handleLoopError(error);\n });\n }, this.pollIntervalMs);\n }\n\n stop(): void {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = undefined;\n }\n\n if (this.loopSignalListener) {\n this.loopSignalListener();\n this.loopSignalListener = undefined;\n }\n\n this.loopSignal = undefined;\n this.inFlightController?.abort();\n }\n\n async syncOnce(options?: LofiSyncOptions): Promise<LofiSyncResult> {\n if (this.inFlight) {\n return this.inFlight;\n }\n\n const signal = options?.signal ?? this.defaultSignal;\n if (signal?.aborted) {\n return { appliedEntries: 0 };\n }\n\n const controller = new AbortController();\n this.inFlightController = controller;\n const cleanup = attachAbortSignal(controller, signal);\n\n const promise = this.runSyncOnce(controller.signal).finally(() => {\n cleanup();\n if (this.inFlight === promise) {\n this.inFlight = undefined;\n }\n if (this.inFlightController === controller) {\n this.inFlightController = undefined;\n }\n });\n\n this.inFlight = promise;\n return promise;\n }\n\n private async runSyncOnce(signal: AbortSignal): Promise<LofiSyncResult> {\n if (signal.aborted) {\n return { appliedEntries: 0 };\n }\n\n const cursorKey = this.cursorKey ?? `${this.endpointName}::outbox`;\n const sourceKey = cursorKey;\n let cursor = await this.adapter.getMeta(cursorKey);\n\n let appliedEntries = 0;\n let lastVersionstamp: string | undefined;\n\n try {\n for await (const page of this.fetchPages({ cursor, limit: this.limit, signal })) {\n if (page.entries.length === 0) {\n break;\n }\n\n for (const entry of page.entries) {\n if (signal.aborted) {\n return { appliedEntries, lastVersionstamp };\n }\n\n const mutations = decodeOutboxEntry(entry);\n const resolvedMutations = entry.refMap\n ? mutations.map((mutation) => resolveOutboxRefs(mutation, entry.refMap ?? {}))\n : mutations;\n\n const result = await this.adapter.applyOutboxEntry({\n sourceKey,\n versionstamp: entry.versionstamp,\n uowId: entry.uowId,\n mutations: resolvedMutations,\n });\n\n lastVersionstamp = entry.versionstamp;\n await this.adapter.setMeta(cursorKey, entry.versionstamp);\n cursor = entry.versionstamp;\n\n if (result.applied) {\n appliedEntries += 1;\n }\n\n if (signal.aborted) {\n return { appliedEntries, lastVersionstamp };\n }\n }\n\n if (page.entries.length < this.limit) {\n break;\n }\n }\n } catch (error) {\n if (isAbortError(error)) {\n return { appliedEntries, lastVersionstamp };\n }\n throw error;\n }\n\n const result = { appliedEntries, lastVersionstamp };\n\n if (appliedEntries > 0) {\n await this.onSyncApplied?.(result);\n }\n\n return result;\n }\n\n private async *fetchPages(options: OutboxFetchOptions): AsyncGenerator<OutboxPageResult> {\n let cursor = options.cursor;\n\n while (true) {\n if (options.signal.aborted) {\n return;\n }\n\n const url = buildOutboxUrl(this.outboxUrl, cursor, options.limit);\n const response = await this.fetcher(url, { signal: options.signal });\n\n if (!response.ok) {\n throw new Error(`Outbox request failed: ${response.status} ${response.statusText}`);\n }\n\n const data = (await response.json()) as OutboxEntry[];\n if (!Array.isArray(data)) {\n throw new Error(\"Invalid outbox response payload\");\n }\n\n const lastVersionstamp = data.at(-1)?.versionstamp;\n yield { entries: data, lastVersionstamp };\n\n if (data.length < options.limit || !lastVersionstamp) {\n return;\n }\n\n cursor = lastVersionstamp;\n }\n }\n\n private handleLoopError(error: unknown): void {\n this.stop();\n this.onError?.(error);\n }\n}\n\nfunction decodeOutboxEntry(entry: OutboxEntry): LofiMutation[] {\n const payload = decodeOutboxPayload(entry.payload);\n return payload.mutations.map((mutation) => toLofiMutation(mutation));\n}\n\nfunction toLofiMutation(mutation: OutboxPayload[\"mutations\"][number]): LofiMutation {\n if (mutation.op === \"create\") {\n return {\n op: \"create\",\n schema: mutation.schema,\n table: mutation.table,\n externalId: mutation.externalId,\n values: mutation.values,\n versionstamp: mutation.versionstamp,\n };\n }\n\n if (mutation.op === \"update\") {\n return {\n op: \"update\",\n schema: mutation.schema,\n table: mutation.table,\n externalId: mutation.externalId,\n set: mutation.set,\n versionstamp: mutation.versionstamp,\n };\n }\n\n return {\n op: \"delete\",\n schema: mutation.schema,\n table: mutation.table,\n externalId: mutation.externalId,\n versionstamp: mutation.versionstamp,\n };\n}\n\nfunction buildOutboxUrl(outboxUrl: string, afterVersionstamp: string | undefined, limit: number) {\n const url = new URL(outboxUrl);\n const params = new URLSearchParams(url.search);\n\n if (afterVersionstamp) {\n params.set(\"afterVersionstamp\", afterVersionstamp);\n } else {\n params.delete(\"afterVersionstamp\");\n }\n\n params.set(\"limit\", String(limit));\n url.search = params.toString();\n\n return url.toString();\n}\n\nfunction attachAbortSignal(controller: AbortController, signal?: AbortSignal) {\n if (!signal) {\n return () => undefined;\n }\n\n if (signal.aborted) {\n controller.abort(signal.reason);\n return () => undefined;\n }\n\n const abort = () => controller.abort(signal.reason);\n signal.addEventListener(\"abort\", abort, { once: true });\n return () => signal.removeEventListener(\"abort\", abort);\n}\n\nfunction isAbortError(error: unknown): boolean {\n if (!error || typeof error !== \"object\") {\n return false;\n }\n\n return \"name\" in error && error.name === \"AbortError\";\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","names":[],"sources":["../../src/indexeddb/adapter.ts"],"sourcesContent":[],"mappings":";;;;;cA4Ea,gBAAA,YAA4B,aAAa;;EAAzC,iBAAA,YAAiB;EAaP,iBAAA,OAAA;EAiDR,iBAAA,SAAA;EACT,iBAAA,QAAA;EA6E4B,iBAAA,gBAAA;EAAiB,iBAAA,iBAAA;EA4DrB,iBAAA,gBAAA;EASe,iBAAA,oBAAA;EAQT,QAAA,SAAA;EACxB,WAAA,CAAA,OAAA,EA7MW,uBA6MX;EACE,gBAAA,CAAA,OAAA,EAAA;IACU,SAAA,EAAA,MAAA;IAAnB,YAAA,EAAA,MAAA;IAUqC,KAAA,EAAA,MAAA;IAtOD,SAAA,EA8D1B,YA9D0B,EAAA;EAAa,CAAA,CAAA,EA+DhD,OA/DgD,CAAA;IAAoB,OAAA,EAAA,OAAA;;4BA4IxC,iBAAiB;wBA4DrB;uCASe;oCAQT,mBACxB,aACE,yBACT,mBAAmB;0CAUkB"}
1
+ {"version":3,"file":"adapter.d.ts","names":[],"sources":["../../src/indexeddb/adapter.ts"],"sourcesContent":[],"mappings":";;;;;cA6Ea,gBAAA,YAA4B,aAAa;;EAAzC,iBAAA,YAAiB;EAaP,iBAAA,OAAA;EAiDR,iBAAA,SAAA;EACT,iBAAA,QAAA;EA6E4B,iBAAA,gBAAA;EAAiB,iBAAA,iBAAA;EA4DrB,iBAAA,gBAAA;EASe,iBAAA,oBAAA;EAQT,QAAA,SAAA;EACxB,WAAA,CAAA,OAAA,EA7MW,uBA6MX;EACE,gBAAA,CAAA,OAAA,EAAA;IACU,SAAA,EAAA,MAAA;IAAnB,YAAA,EAAA,MAAA;IAUqC,KAAA,EAAA,MAAA;IAtOD,SAAA,EA8D1B,YA9D0B,EAAA;EAAa,CAAA,CAAA,EA+DhD,OA/DgD,CAAA;IAAoB,OAAA,EAAA,OAAA;;4BA4IxC,iBAAiB;wBA4DrB;uCASe;oCAQT,mBACxB,aACE,yBACT,mBAAmB;0CAUkB"}
@@ -1,7 +1,7 @@
1
1
  import { normalizeValue } from "../query/normalize.js";
2
2
  import { createIndexedDbQueryEngine } from "../query/engine.js";
3
- import { FragnoId, FragnoReference } from "@fragno-dev/db/schema";
4
3
  import { generateMigrationFromSchema } from "@fragno-dev/db/client";
4
+ import { FragnoId, FragnoReference } from "@fragno-dev/db/schema";
5
5
  import { openDB } from "idb";
6
6
 
7
7
  //#region src/indexeddb/adapter.ts