@korajs/core 1.0.0-beta.11 → 1.0.0-beta.13

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/internal.ts","../src/operations/content-hash.ts","../src/events/event-emitter.ts","../src/errors/error-fixes.ts","../src/errors/errors.ts","../src/clock/hlc.ts","../src/operations/operation.ts","../src/version-vector/topological-sort.ts"],"sourcesContent":["// Internal exports — shared within @kora packages but NOT part of the public API.\n// Other @kora packages can import from '@korajs/core/internal' if needed.\n\nexport { canonicalize, computeOperationId } from './operations/content-hash'\nexport { SimpleEventEmitter } from './events/event-emitter'\nexport { validateOperationParams } from './operations/operation'\nexport { topologicalSort } from './version-vector/topological-sort'\n","import type { OperationInput } from '../types'\n\n/**\n * Compute the content-addressed ID for an operation using SHA-256.\n * The same operation content always produces the same hash, ensuring deduplication.\n *\n * @param input - The operation input (without id/timestamp, which are assigned separately)\n * @param timestamp - The HLC timestamp serialized as a string\n * @returns A hex-encoded SHA-256 hash\n */\nexport async function computeOperationId(\n\tinput: OperationInput,\n\ttimestamp: string,\n): Promise<string> {\n\t// Only include atomicOps when present — ensures backward compatibility\n\t// (existing operations without atomicOps produce identical hashes).\n\tconst hashInput: Record<string, unknown> = {\n\t\ttype: input.type,\n\t\tcollection: input.collection,\n\t\trecordId: input.recordId,\n\t\tdata: input.data,\n\t\ttimestamp,\n\t\tnodeId: input.nodeId,\n\t}\n\tif (input.atomicOps !== undefined && Object.keys(input.atomicOps).length > 0) {\n\t\thashInput.atomicOps = input.atomicOps\n\t}\n\tconst canonical = canonicalize(hashInput)\n\tconst encoded = new TextEncoder().encode(canonical)\n\tconst hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', encoded)\n\treturn bufferToHex(hashBuffer)\n}\n\n/**\n * Deterministic JSON serialization with sorted keys.\n * Ensures identical objects always produce identical strings regardless of property insertion order.\n *\n * @param obj - The value to serialize\n * @returns A deterministic JSON string\n */\nexport function canonicalize(obj: unknown): string {\n\tif (obj === null) {\n\t\treturn 'null'\n\t}\n\n\tif (obj === undefined) {\n\t\treturn 'null'\n\t}\n\n\tif (typeof obj !== 'object') {\n\t\treturn JSON.stringify(obj)\n\t}\n\n\tif (Array.isArray(obj)) {\n\t\tconst items = obj.map((item) => canonicalize(item))\n\t\treturn `[${items.join(',')}]`\n\t}\n\n\tconst keys = Object.keys(obj as Record<string, unknown>).sort()\n\tconst pairs = keys.map((key) => {\n\t\tconst value = (obj as Record<string, unknown>)[key]\n\t\t// Serialize undefined values as null for deterministic output\n\t\treturn `${JSON.stringify(key)}:${canonicalize(value === undefined ? null : value)}`\n\t})\n\treturn `{${pairs.join(',')}}`\n}\n\nfunction bufferToHex(buffer: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(buffer)\n\treturn Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n","import type { KoraEventByType, KoraEventEmitter, KoraEventListener, KoraEventType } from './events'\n\ntype AnyListener = (event: never) => void\n\n/**\n * Concrete implementation of KoraEventEmitter.\n * Simple, synchronous event emitter for internal use across @kora packages.\n *\n * @example\n * ```typescript\n * const emitter = new SimpleEventEmitter()\n * const unsub = emitter.on('operation:created', (event) => {\n * console.log(event.operation.id)\n * })\n * emitter.emit({ type: 'operation:created', operation: someOp })\n * unsub() // unsubscribe\n * ```\n */\nexport class SimpleEventEmitter implements KoraEventEmitter {\n\tprivate listeners = new Map<string, Set<AnyListener>>()\n\n\ton<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): () => void {\n\t\tlet set = this.listeners.get(type)\n\t\tif (!set) {\n\t\t\tset = new Set()\n\t\t\tthis.listeners.set(type, set)\n\t\t}\n\t\tset.add(listener as AnyListener)\n\n\t\treturn () => {\n\t\t\tthis.off(type, listener)\n\t\t}\n\t}\n\n\toff<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): void {\n\t\tconst set = this.listeners.get(type)\n\t\tif (set) {\n\t\t\tset.delete(listener as AnyListener)\n\t\t\tif (set.size === 0) {\n\t\t\t\tthis.listeners.delete(type)\n\t\t\t}\n\t\t}\n\t}\n\n\temit<T extends KoraEventType>(event: KoraEventByType<T>): void {\n\t\tconst set = this.listeners.get(event.type)\n\t\tif (!set) return\n\t\tfor (const listener of set) {\n\t\t\t;(listener as (event: KoraEventByType<T>) => void)(event)\n\t\t}\n\t}\n\n\t/** Remove all listeners for all event types. */\n\tclear(): void {\n\t\tthis.listeners.clear()\n\t}\n\n\t/** Get the number of listeners for a specific event type. */\n\tlistenerCount(type: KoraEventType): number {\n\t\treturn this.listeners.get(type)?.size ?? 0\n\t}\n}\n","/**\n * Human-readable remediation hints keyed by {@link KoraError} `code`.\n */\nexport const KORA_ERROR_FIX_SUGGESTIONS: Record<string, string> = {\n\tSCHEMA_VALIDATION:\n\t\t'Review your defineSchema() definition: every collection needs at least one field and a positive version.',\n\tOPERATION_ERROR:\n\t\t'Check the operation payload matches your schema field types and required fields.',\n\tMERGE_CONFLICT:\n\t\t'Add a custom resolver for the conflicting field in defineSchema(), or adjust constraint onConflict rules.',\n\tSYNC_ERROR:\n\t\t'Verify the sync server URL, auth token, and that the server accepts your schema version.',\n\tSTORAGE_ERROR:\n\t\t'Confirm the storage adapter is supported in this environment and the database path is writable.',\n\tCLOCK_DRIFT:\n\t\t'Sync device wall-clock time (NTP). Kora refuses new timestamps when drift exceeds five minutes.',\n\tINVALID_TIMESTAMP_FIELDS:\n\t\t'HLC timestamps need non-negative integer wallTime (< 10^15) and logical (<= 99999). Generate timestamps through HybridLogicalClock instead of building them by hand.',\n\tPERSISTENCE_ERROR:\n\t\t'IndexedDB persistence failed; check browser storage settings and available disk quota.',\n\tMISSING_WORKER_URL:\n\t\t'Pass store.workerUrl pointing to sqlite-wasm-worker.js (see create-kora-app templates).',\n\tINVALID_SYNC_URL:\n\t\t'Use ws:// or wss:// for WebSocket transport, or http(s):// for HTTP long-polling.',\n\tAPP_NOT_READY:\n\t\t'Await app.ready before querying or mutating collections, or wrap the tree in <KoraProvider app={app}>.',\n}\n\n/**\n * Returns a suggested fix for a Kora error code, if one is known.\n */\nexport function getKoraErrorFix(code: string): string | undefined {\n\treturn KORA_ERROR_FIX_SUGGESTIONS[code]\n}\n","import { getKoraErrorFix } from './error-fixes'\n\n/**\n * Base error class for all Kora errors.\n * Every error includes a machine-readable code and optional context for debugging.\n */\nexport class KoraError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly code: string,\n\t\tpublic readonly context?: Record<string, unknown>,\n\t) {\n\t\tsuper(message)\n\t\tthis.name = 'KoraError'\n\t}\n\n\t/**\n\t * Actionable hint for resolving this error (from registry or error context).\n\t */\n\tget fix(): string | undefined {\n\t\tconst fromContext = this.context?.fix\n\t\tif (typeof fromContext === 'string' && fromContext.length > 0) {\n\t\t\treturn fromContext\n\t\t}\n\t\treturn getKoraErrorFix(this.code)\n\t}\n}\n\n/**\n * Thrown when schema validation fails during defineSchema() or at app initialization.\n */\nexport class SchemaValidationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'SCHEMA_VALIDATION', context)\n\t\tthis.name = 'SchemaValidationError'\n\t}\n}\n\n/**\n * Thrown when an operation is invalid or cannot be created.\n */\nexport class OperationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'OPERATION_ERROR', context)\n\t\tthis.name = 'OperationError'\n\t}\n}\n\n/**\n * Thrown when a merge conflict cannot be automatically resolved.\n */\nexport class MergeConflictError extends KoraError {\n\tconstructor(\n\t\tpublic readonly operationA: { id: string; collection: string },\n\t\tpublic readonly operationB: { id: string; collection: string },\n\t\tpublic readonly field: string,\n\t) {\n\t\tsuper(\n\t\t\t`Merge conflict on field \"${field}\" in collection \"${operationA.collection}\"`,\n\t\t\t'MERGE_CONFLICT',\n\t\t\t{ operationA: operationA.id, operationB: operationB.id, field },\n\t\t)\n\t\tthis.name = 'MergeConflictError'\n\t}\n}\n\n/**\n * Thrown when a sync error occurs.\n */\nexport class SyncError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'SYNC_ERROR', context)\n\t\tthis.name = 'SyncError'\n\t}\n}\n\n/**\n * Thrown when a storage operation fails.\n */\nexport class StorageError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'STORAGE_ERROR', context)\n\t\tthis.name = 'StorageError'\n\t}\n}\n\n/**\n * Thrown when collection/query APIs are used before {@link KoraApp.ready} resolves.\n */\nexport class AppNotReadyError extends KoraError {\n\tconstructor(detail: string) {\n\t\tsuper(detail, 'APP_NOT_READY', {\n\t\t\tfix: 'Await app.ready, or wrap your UI in <KoraProvider app={app}> before calling useQuery().',\n\t\t})\n\t\tthis.name = 'AppNotReadyError'\n\t}\n}\n\n/**\n * Thrown when the HLC detects excessive clock drift.\n * Drift > 60s: warning. Drift > 5min: this error is thrown, refusing to generate timestamps.\n */\nexport class RemoteClockDriftError extends KoraError {\n\tconstructor(\n\t\tpublic readonly remoteWallTime: number,\n\t\tpublic readonly localReferenceTime: number,\n\t) {\n\t\tconst aheadSeconds = Math.round((remoteWallTime - localReferenceTime) / 1000)\n\t\tsuper(\n\t\t\t`Rejected remote timestamp ${aheadSeconds}s ahead of local reference time. The sending device's clock is set too far in the future.`,\n\t\t\t'REMOTE_CLOCK_DRIFT',\n\t\t\t{ remoteWallTime, localReferenceTime, aheadSeconds },\n\t\t)\n\t\tthis.name = 'RemoteClockDriftError'\n\t}\n}\n\n/**\n * Thrown when an HLC timestamp has structurally invalid fields: non-integer or\n * negative wallTime/logical, or a logical counter beyond the serializable cap.\n * Rejected BEFORE any clock state changes, so a malformed remote timestamp can\n * never corrupt a replica's clock or break the lexicographic ordering of the\n * serialized form.\n */\nexport class InvalidTimestampError extends KoraError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly wallTime: number,\n\t\tpublic readonly logical: number,\n\t) {\n\t\tsuper(message, 'INVALID_TIMESTAMP_FIELDS', { wallTime, logical })\n\t\tthis.name = 'InvalidTimestampError'\n\t}\n}\n\nexport class ClockDriftError extends KoraError {\n\tconstructor(\n\t\tpublic readonly currentHlcTime: number,\n\t\tpublic readonly physicalTime: number,\n\t) {\n\t\tconst driftSeconds = Math.round((currentHlcTime - physicalTime) / 1000)\n\t\tsuper(\n\t\t\t`Clock drift of ${driftSeconds}s detected. Physical time is behind HLC by more than 5 minutes. This indicates a severe clock issue.`,\n\t\t\t'CLOCK_DRIFT',\n\t\t\t{ currentHlcTime, physicalTime, driftSeconds },\n\t\t)\n\t\tthis.name = 'ClockDriftError'\n\t}\n}\n","import { ClockDriftError, InvalidTimestampError, RemoteClockDriftError } from '../errors/errors'\nimport type { HLCTimestamp, TimeSource } from '../types'\n\n/** Default time source using the system clock */\nconst systemTimeSource: TimeSource = { now: () => Date.now() }\n\n/**\n * Highest logical counter value that fits the 5-digit zero-padded slot in the\n * serialized timestamp form. The serialized string must sort lexicographically\n * in exactly the same order as {@link HybridLogicalClock.compare}; a 6-digit\n * logical value would shift the padding and silently break that invariant for\n * every stored `_version` column and operation timestamp. When the counter\n * would exceed this cap, the clock carries into wallTime instead\n * (wallTime + 1, logical = 0), which preserves both monotonicity and the\n * serialized ordering.\n */\nexport const MAX_LOGICAL = 99_999\n\n/** Number of distinct logical values; the modulus used when carrying into wallTime. */\nconst LOGICAL_MODULUS = MAX_LOGICAL + 1\n\n/**\n * Exclusive upper bound on serializable wallTime: the serialized form\n * zero-pads wallTime to 15 digits, so a 16-digit value would break the\n * lexicographic ordering. 10^15 ms is roughly the year 33658 — unreachable by\n * honest clocks, only by hand-built timestamps.\n */\nconst WALL_TIME_LIMIT = 10 ** 15\n\n/** Maximum allowed drift before warning (60 seconds) */\nconst DRIFT_WARN_MS = 60_000\n\n/** Drift beyond which the error-severity callback fires (5 minutes) */\nconst DRIFT_ERROR_MS = 5 * 60_000\n\n/** Maximum future skew accepted from a remote timestamp before it is rejected (5 minutes) */\nconst MAX_REMOTE_FUTURE_MS = 5 * 60_000\n\n/**\n * Hybrid Logical Clock implementation based on Kulkarni et al.\n *\n * Provides a total order that respects causality without requiring synchronized clocks.\n * Each call to now() returns a timestamp strictly greater than the previous one.\n *\n * @example\n * ```typescript\n * const clock = new HybridLogicalClock('node-1')\n * const ts1 = clock.now()\n * const ts2 = clock.now()\n * // HybridLogicalClock.compare(ts1, ts2) < 0 (ts1 is earlier)\n * ```\n */\nexport class HybridLogicalClock {\n\tprivate wallTime = 0\n\tprivate logical = 0\n\tprivate referenceOffsetMs: number | null = null\n\n\tconstructor(\n\t\tprivate readonly nodeId: string,\n\t\tprivate readonly timeSource: TimeSource = systemTimeSource,\n\t\tprivate readonly onDriftWarning?: (driftMs: number) => void,\n\t\tprivate readonly onDriftError?: (driftMs: number) => void,\n\t) {}\n\n\t/**\n\t * Records the known offset between an external time reference (usually the\n\t * sync server, learned at handshake) and this device's physical clock:\n\t * `referenceTime - localPhysicalTime`. Once set, drift evaluation and remote\n\t * timestamp validation are performed against reference-corrected time, so a\n\t * device with a wrong local clock still validates remote timestamps correctly.\n\t */\n\tsetReferenceOffset(offsetMs: number): void {\n\t\tthis.referenceOffsetMs = offsetMs\n\t}\n\n\tgetReferenceOffset(): number | null {\n\t\treturn this.referenceOffsetMs\n\t}\n\n\t/** Physical time corrected by the known reference offset, when available. */\n\tprivate effectiveTime(physicalTime: number): number {\n\t\treturn physicalTime + (this.referenceOffsetMs ?? 0)\n\t}\n\n\t/**\n\t * Generate a new timestamp for a local event.\n\t * Guarantees monotonicity: each call returns a timestamp strictly greater than the previous.\n\t *\n\t * Never throws and never blocks a local write: if the physical clock has\n\t * fallen behind the HLC (e.g. the user corrected a fast clock), the HLC\n\t * freezes wallTime and advances the logical counter, and drift is reported\n\t * through the onDriftWarning / onDriftError callbacks instead.\n\t */\n\tnow(): HLCTimestamp {\n\t\tconst physicalTime = this.timeSource.now()\n\t\tthis.checkDrift(physicalTime)\n\n\t\tif (physicalTime > this.wallTime) {\n\t\t\tthis.wallTime = physicalTime\n\t\t\tthis.logical = 0\n\t\t} else {\n\t\t\tthis.logical++\n\t\t}\n\t\tthis.carryLogicalOverflow()\n\n\t\treturn { wallTime: this.wallTime, logical: this.logical, nodeId: this.nodeId }\n\t}\n\n\t/**\n\t * Update clock on receiving a remote timestamp.\n\t * Merges the remote clock state with the local state to maintain causal ordering.\n\t *\n\t * @throws {InvalidTimestampError} If the remote timestamp has non-integer or\n\t * negative wallTime/logical, or logical beyond {@link MAX_LOGICAL}. Checked\n\t * BEFORE any state change, so malformed input cannot corrupt this clock.\n\t * @throws {RemoteClockDriftError} If the remote timestamp is more than 5 minutes\n\t * ahead of reference-corrected local time. Validation happens BEFORE any state\n\t * is adopted, so a rejected timestamp cannot poison this clock. When no\n\t * reference offset is known and this clock is uninitialized (cold start with\n\t * a possibly-wrong local clock), validation is skipped, matching the previous\n\t * cold-start behavior but now without a corrupting failure mode afterward.\n\t */\n\treceive(remote: HLCTimestamp): HLCTimestamp {\n\t\tconst physicalTime = this.timeSource.now()\n\t\tconst wasColdStart = this.wallTime === 0\n\n\t\t// Validate before adopting anything. Malformed fields would corrupt the\n\t\t// clock's arithmetic (NaN/fractional wallTime) or overflow the serialized\n\t\t// form's 5-digit logical slot, so they are rejected outright.\n\t\tif (\n\t\t\t!Number.isInteger(remote.wallTime) ||\n\t\t\tremote.wallTime < 0 ||\n\t\t\t!Number.isInteger(remote.logical) ||\n\t\t\tremote.logical < 0 ||\n\t\t\tremote.logical > MAX_LOGICAL\n\t\t) {\n\t\t\tthrow new InvalidTimestampError(\n\t\t\t\t`Rejected remote HLC timestamp with invalid fields (wallTime=${remote.wallTime}, logical=${remote.logical}). wallTime and logical must be non-negative integers and logical must not exceed ${MAX_LOGICAL}.`,\n\t\t\t\tremote.wallTime,\n\t\t\t\tremote.logical,\n\t\t\t)\n\t\t}\n\n\t\t// A far-future remote timestamp must never become this clock's state.\n\t\tconst canValidate = this.referenceOffsetMs !== null || !wasColdStart\n\t\tif (canValidate) {\n\t\t\tconst reference = this.effectiveTime(physicalTime)\n\t\t\tif (remote.wallTime > reference + MAX_REMOTE_FUTURE_MS) {\n\t\t\t\tthrow new RemoteClockDriftError(remote.wallTime, reference)\n\t\t\t}\n\t\t}\n\n\t\tif (physicalTime > this.wallTime && physicalTime > remote.wallTime) {\n\t\t\tthis.wallTime = physicalTime\n\t\t\tthis.logical = 0\n\t\t} else if (remote.wallTime > this.wallTime) {\n\t\t\tthis.wallTime = remote.wallTime\n\t\t\tthis.logical = remote.logical + 1\n\t\t} else if (this.wallTime === remote.wallTime) {\n\t\t\tthis.logical = Math.max(this.logical, remote.logical) + 1\n\t\t} else {\n\t\t\t// this.wallTime > remote.wallTime && this.wallTime >= physicalTime\n\t\t\tthis.logical++\n\t\t}\n\t\tthis.carryLogicalOverflow()\n\n\t\t// Report (never throw) drift after merging. Cold start is exempt from\n\t\t// reporting to avoid false positives when the local clock is simply wrong.\n\t\tif (!wasColdStart) {\n\t\t\tthis.checkDrift(physicalTime)\n\t\t}\n\n\t\treturn { wallTime: this.wallTime, logical: this.logical, nodeId: this.nodeId }\n\t}\n\n\t/**\n\t * Advance this clock to at least the given timestamp.\n\t *\n\t * Used after a timestamp rebase rewrites unsynced operations: future `now()`\n\t * timestamps must sort after every rebased operation, otherwise a write issued\n\t * immediately after the rebase could interleave with (or precede) rebased ops\n\t * and break the log's total order. Never moves the clock backward — a\n\t * timestamp at or before the current state is a no-op, preserving the\n\t * monotonicity guarantee of `now()`.\n\t *\n\t * Inputs with logical > MAX_LOGICAL are normalized deterministically by\n\t * carrying the excess into wallTime, so the adopted state always leaves room\n\t * for the next increment inside the serializable range.\n\t */\n\tadvanceTo(ts: HLCTimestamp): void {\n\t\t// Deterministic normalization: adopting given values verbatim could plant\n\t\t// an unserializable logical counter inside the clock.\n\t\tconst normalized: HLCTimestamp = {\n\t\t\twallTime: ts.wallTime + Math.floor(ts.logical / LOGICAL_MODULUS),\n\t\t\tlogical: ts.logical % LOGICAL_MODULUS,\n\t\t\tnodeId: ts.nodeId,\n\t\t}\n\t\tconst current: HLCTimestamp = {\n\t\t\twallTime: this.wallTime,\n\t\t\tlogical: this.logical,\n\t\t\tnodeId: this.nodeId,\n\t\t}\n\t\tif (HybridLogicalClock.compare(normalized, current) > 0) {\n\t\t\tthis.wallTime = normalized.wallTime\n\t\t\tthis.logical = normalized.logical\n\t\t}\n\t}\n\n\t/**\n\t * Carry the logical counter into wallTime when an increment pushed it past\n\t * MAX_LOGICAL. Bumping wallTime by 1ms keeps the timestamp strictly greater\n\t * than everything issued before (monotonicity) while keeping the logical\n\t * counter inside the 5-digit slot the serialized form depends on.\n\t */\n\tprivate carryLogicalOverflow(): void {\n\t\tif (this.logical > MAX_LOGICAL) {\n\t\t\tthis.wallTime += 1\n\t\t\tthis.logical = 0\n\t\t}\n\t}\n\n\t/**\n\t * Compare two timestamps. Returns negative if a < b, positive if a > b, zero if equal.\n\t * Total order: wallTime first, then logical, then nodeId (lexicographic).\n\t */\n\tstatic compare(a: HLCTimestamp, b: HLCTimestamp): number {\n\t\tif (a.wallTime !== b.wallTime) return a.wallTime - b.wallTime\n\t\tif (a.logical !== b.logical) return a.logical - b.logical\n\t\tif (a.nodeId < b.nodeId) return -1\n\t\tif (a.nodeId > b.nodeId) return 1\n\t\treturn 0\n\t}\n\n\t/**\n\t * Serialize an HLC timestamp to a string that sorts lexicographically.\n\t * Format: zero-padded wallTime:logical:nodeId\n\t *\n\t * @throws {InvalidTimestampError} If wallTime does not fit 15 digits or\n\t * logical does not fit 5 digits (or either is negative/non-integer). Such a\n\t * value would overflow its zero-padded slot and the serialized string would\n\t * no longer sort in the same order as {@link HybridLogicalClock.compare} —\n\t * silently corrupting every LWW comparison on stored `_version` columns.\n\t * Internal clocks can no longer produce such values (the logical counter\n\t * carries into wallTime); this guards against hand-built timestamps.\n\t */\n\tstatic serialize(ts: HLCTimestamp): string {\n\t\tif (\n\t\t\t!Number.isInteger(ts.wallTime) ||\n\t\t\tts.wallTime < 0 ||\n\t\t\tts.wallTime >= WALL_TIME_LIMIT ||\n\t\t\t!Number.isInteger(ts.logical) ||\n\t\t\tts.logical < 0 ||\n\t\t\tts.logical > MAX_LOGICAL\n\t\t) {\n\t\t\tthrow new InvalidTimestampError(\n\t\t\t\t`Cannot serialize HLC timestamp (wallTime=${ts.wallTime}, logical=${ts.logical}): wallTime must be an integer in [0, 10^15) and logical an integer in [0, ${MAX_LOGICAL}]. Values outside these ranges overflow the zero-padded serialized form, which must sort lexicographically identically to HybridLogicalClock.compare.`,\n\t\t\t\tts.wallTime,\n\t\t\t\tts.logical,\n\t\t\t)\n\t\t}\n\t\tconst wall = ts.wallTime.toString().padStart(15, '0')\n\t\tconst log = ts.logical.toString().padStart(5, '0')\n\t\treturn `${wall}:${log}:${ts.nodeId}`\n\t}\n\n\t/**\n\t * Deserialize an HLC timestamp from its serialized string form.\n\t */\n\tstatic deserialize(s: string): HLCTimestamp {\n\t\tconst parts = s.split(':')\n\t\tif (parts.length < 3) {\n\t\t\tthrow new Error(`Invalid HLC timestamp string: \"${s}\"`)\n\t\t}\n\t\treturn {\n\t\t\twallTime: Number.parseInt(parts[0] ?? '0', 10),\n\t\t\tlogical: Number.parseInt(parts[1] ?? '0', 10),\n\t\t\t// nodeId may contain colons, so rejoin remaining parts\n\t\t\tnodeId: parts.slice(2).join(':'),\n\t\t}\n\t}\n\n\t/**\n\t * Reports drift between the HLC and (reference-corrected) physical time.\n\t * Reporting only: local timestamp generation is never blocked, because the\n\t * user's data always outranks the quality of its timestamps.\n\t */\n\tprivate checkDrift(physicalTime: number): void {\n\t\tconst drift = this.wallTime - this.effectiveTime(physicalTime)\n\t\tif (drift > DRIFT_ERROR_MS) {\n\t\t\tthis.onDriftError?.(drift)\n\t\t\treturn\n\t\t}\n\t\tif (drift > DRIFT_WARN_MS) {\n\t\t\tthis.onDriftWarning?.(drift)\n\t\t}\n\t}\n}\n","import { HybridLogicalClock } from '../clock/hlc'\nimport { OperationError } from '../errors/errors'\nimport type { HLCTimestamp, Operation, OperationInput } from '../types'\nimport { computeOperationId } from './content-hash'\n\n/**\n * Creates an immutable, content-addressed Operation from the given parameters.\n * The operation is deep-frozen after creation — it cannot be modified.\n *\n * @param input - The operation parameters (without id, which is computed)\n * @param clock - The HLC clock to generate the timestamp\n * @returns A frozen Operation with a content-addressed id\n *\n * @example\n * ```typescript\n * const op = await createOperation({\n * nodeId: 'device-1',\n * type: 'insert',\n * collection: 'todos',\n * recordId: 'rec-1',\n * data: { title: 'Ship it' },\n * previousData: null,\n * sequenceNumber: 1,\n * causalDeps: [],\n * schemaVersion: 1,\n * }, clock)\n * ```\n */\nexport async function createOperation(\n\tinput: OperationInput,\n\tclock: HybridLogicalClock,\n): Promise<Operation> {\n\tvalidateOperationParams(input)\n\n\tconst timestamp = clock.now()\n\tconst serializedTs = HybridLogicalClock.serialize(timestamp)\n\tconst id = await computeOperationId(input, serializedTs)\n\n\tconst operation: Operation = {\n\t\tid,\n\t\tnodeId: input.nodeId,\n\t\ttype: input.type,\n\t\tcollection: input.collection,\n\t\trecordId: input.recordId,\n\t\tdata: input.data ? { ...input.data } : null,\n\t\tpreviousData: input.previousData ? { ...input.previousData } : null,\n\t\ttimestamp,\n\t\tsequenceNumber: input.sequenceNumber,\n\t\tcausalDeps: [...input.causalDeps],\n\t\tschemaVersion: input.schemaVersion,\n\t\t...(input.atomicOps !== undefined && Object.keys(input.atomicOps).length > 0\n\t\t\t? { atomicOps: { ...input.atomicOps } }\n\t\t\t: {}),\n\t\t...(input.transactionId !== undefined ? { transactionId: input.transactionId } : {}),\n\t\t...(input.mutationName !== undefined ? { mutationName: input.mutationName } : {}),\n\t}\n\n\treturn deepFreeze(operation)\n}\n\n/**\n * Validates operation input parameters. Throws OperationError with\n * contextual information on validation failure.\n */\nexport function validateOperationParams(input: OperationInput): void {\n\tif (!input.nodeId || typeof input.nodeId !== 'string') {\n\t\tthrow new OperationError('nodeId is required and must be a non-empty string', {\n\t\t\treceived: input.nodeId,\n\t\t})\n\t}\n\n\tif (!input.type || !['insert', 'update', 'delete'].includes(input.type)) {\n\t\tthrow new OperationError('type must be \"insert\", \"update\", or \"delete\"', {\n\t\t\treceived: input.type,\n\t\t})\n\t}\n\n\tif (!input.collection || typeof input.collection !== 'string') {\n\t\tthrow new OperationError('collection is required and must be a non-empty string', {\n\t\t\treceived: input.collection,\n\t\t})\n\t}\n\n\tif (!input.recordId || typeof input.recordId !== 'string') {\n\t\tthrow new OperationError('recordId is required and must be a non-empty string', {\n\t\t\treceived: input.recordId,\n\t\t})\n\t}\n\n\tif (input.type === 'insert' && input.data === null) {\n\t\tthrow new OperationError('insert operations must include data', {\n\t\t\ttype: input.type,\n\t\t\tcollection: input.collection,\n\t\t})\n\t}\n\n\tif (input.type === 'update' && input.data === null) {\n\t\tthrow new OperationError('update operations must include data with changed fields', {\n\t\t\ttype: input.type,\n\t\t\tcollection: input.collection,\n\t\t})\n\t}\n\n\tif (input.type === 'update' && input.previousData === null) {\n\t\tthrow new OperationError(\n\t\t\t'update operations must include previousData for 3-way merge support',\n\t\t\t{\n\t\t\t\ttype: input.type,\n\t\t\t\tcollection: input.collection,\n\t\t\t},\n\t\t)\n\t}\n\n\tif (input.type === 'delete' && input.data !== null) {\n\t\tthrow new OperationError('delete operations must have null data', {\n\t\t\ttype: input.type,\n\t\t\tcollection: input.collection,\n\t\t})\n\t}\n\n\tif (typeof input.sequenceNumber !== 'number' || input.sequenceNumber < 0) {\n\t\tthrow new OperationError('sequenceNumber must be a non-negative number', {\n\t\t\treceived: input.sequenceNumber,\n\t\t})\n\t}\n\n\tif (!Array.isArray(input.causalDeps)) {\n\t\tthrow new OperationError('causalDeps must be an array of operation IDs', {\n\t\t\treceived: typeof input.causalDeps,\n\t\t})\n\t}\n\n\tif (typeof input.schemaVersion !== 'number' || input.schemaVersion < 1) {\n\t\tthrow new OperationError('schemaVersion must be a positive number', {\n\t\t\treceived: input.schemaVersion,\n\t\t})\n\t}\n}\n\n/**\n * Verify the integrity of an operation by recomputing its content hash.\n * Returns true if the id matches the recomputed hash.\n */\nexport async function verifyOperationIntegrity(op: Operation): Promise<boolean> {\n\tconst input: OperationInput = {\n\t\tnodeId: op.nodeId,\n\t\ttype: op.type,\n\t\tcollection: op.collection,\n\t\trecordId: op.recordId,\n\t\tdata: op.data,\n\t\tpreviousData: op.previousData,\n\t\tsequenceNumber: op.sequenceNumber,\n\t\tcausalDeps: op.causalDeps,\n\t\tschemaVersion: op.schemaVersion,\n\t\t...(op.atomicOps !== undefined ? { atomicOps: op.atomicOps } : {}),\n\t}\n\tconst serializedTs = HybridLogicalClock.serialize(op.timestamp)\n\tconst expectedId = await computeOperationId(input, serializedTs)\n\treturn op.id === expectedId\n}\n\n/**\n * Type guard for Operation interface.\n */\nexport function isValidOperation(value: unknown): value is Operation {\n\tif (typeof value !== 'object' || value === null) return false\n\tconst op = value as Record<string, unknown>\n\treturn (\n\t\ttypeof op.id === 'string' &&\n\t\ttypeof op.nodeId === 'string' &&\n\t\t(op.type === 'insert' || op.type === 'update' || op.type === 'delete') &&\n\t\ttypeof op.collection === 'string' &&\n\t\ttypeof op.recordId === 'string' &&\n\t\ttypeof op.sequenceNumber === 'number' &&\n\t\tArray.isArray(op.causalDeps) &&\n\t\ttypeof op.schemaVersion === 'number' &&\n\t\ttypeof op.timestamp === 'object' &&\n\t\top.timestamp !== null\n\t)\n}\n\nfunction deepFreeze<T>(obj: T): T {\n\tif (typeof obj !== 'object' || obj === null) return obj\n\t// Typed arrays (e.g. richtext Yjs blobs) cannot be frozen in JS engines.\n\tif (ArrayBuffer.isView(obj)) return obj\n\tObject.freeze(obj)\n\tfor (const value of Object.values(obj)) {\n\t\tif (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {\n\t\t\tdeepFreeze(value)\n\t\t}\n\t}\n\treturn obj\n}\n","import { HybridLogicalClock } from '../clock/hlc'\nimport { OperationError } from '../errors/errors'\nimport type { Operation } from '../types'\n\n/**\n * Topological sort of operations based on their causal dependency DAG.\n * Uses Kahn's algorithm with a binary heap for O(V log V + E) performance.\n * Deterministic tie-breaking via HLC timestamp ensures identical output\n * regardless of input order.\n *\n * @param operations - The operations to sort\n * @returns Operations in causal order (dependencies before dependents)\n * @throws {OperationError} If a cycle is detected in the dependency graph\n */\nexport function topologicalSort(operations: Operation[]): Operation[] {\n\tif (operations.length <= 1) return [...operations]\n\n\t// Build adjacency list and in-degree map\n\tconst opMap = new Map<string, Operation>()\n\tfor (const op of operations) {\n\t\topMap.set(op.id, op)\n\t}\n\n\t// Only count edges where both ends are in the operation set\n\tconst inDegree = new Map<string, number>()\n\tconst dependents = new Map<string, string[]>()\n\n\tfor (const op of operations) {\n\t\tif (!inDegree.has(op.id)) {\n\t\t\tinDegree.set(op.id, 0)\n\t\t}\n\t\tif (!dependents.has(op.id)) {\n\t\t\tdependents.set(op.id, [])\n\t\t}\n\n\t\tfor (const depId of op.causalDeps) {\n\t\t\tif (opMap.has(depId)) {\n\t\t\t\t// depId -> op.id edge (depId must come before op.id)\n\t\t\t\tinDegree.set(op.id, (inDegree.get(op.id) ?? 0) + 1)\n\t\t\t\tconst deps = dependents.get(depId)\n\t\t\t\tif (deps) {\n\t\t\t\t\tdeps.push(op.id)\n\t\t\t\t} else {\n\t\t\t\t\tdependents.set(depId, [op.id])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Initialize min-heap with nodes that have no in-set dependencies\n\tconst heap = new MinHeap(compareByTimestamp)\n\tfor (const op of operations) {\n\t\tif ((inDegree.get(op.id) ?? 0) === 0) {\n\t\t\theap.push(op)\n\t\t}\n\t}\n\n\tconst result: Operation[] = []\n\n\twhile (heap.size > 0) {\n\t\t// Extract the earliest operation (deterministic tie-breaking by HLC)\n\t\tconst current = heap.pop()\n\t\tresult.push(current)\n\n\t\tconst deps = dependents.get(current.id) ?? []\n\n\t\tfor (const depId of deps) {\n\t\t\tconst deg = (inDegree.get(depId) ?? 0) - 1\n\t\t\tinDegree.set(depId, deg)\n\t\t\tif (deg === 0) {\n\t\t\t\tconst op = opMap.get(depId)\n\t\t\t\tif (op) heap.push(op)\n\t\t\t}\n\t\t}\n\t}\n\n\tif (result.length !== operations.length) {\n\t\tthrow new OperationError(\n\t\t\t`Cycle detected in operation dependency graph. Sorted ${result.length} of ${operations.length} operations.`,\n\t\t\t{\n\t\t\t\tsortedCount: result.length,\n\t\t\t\ttotalCount: operations.length,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn result\n}\n\nfunction compareByTimestamp(a: Operation, b: Operation): number {\n\treturn HybridLogicalClock.compare(a.timestamp, b.timestamp)\n}\n\n/**\n * Binary min-heap for efficient priority queue operations.\n * push: O(log n), pop: O(log n) — replaces the O(n) sorted array approach.\n */\nclass MinHeap {\n\tprivate readonly data: Operation[] = []\n\tprivate readonly cmp: (a: Operation, b: Operation) => number\n\n\tconstructor(comparator: (a: Operation, b: Operation) => number) {\n\t\tthis.cmp = comparator\n\t}\n\n\tget size(): number {\n\t\treturn this.data.length\n\t}\n\n\tpush(item: Operation): void {\n\t\tthis.data.push(item)\n\t\tthis.bubbleUp(this.data.length - 1)\n\t}\n\n\tpop(): Operation {\n\t\tconst top = this.data[0] as Operation\n\t\tconst last = this.data.pop() as Operation\n\t\tif (this.data.length > 0) {\n\t\t\tthis.data[0] = last\n\t\t\tthis.sinkDown(0)\n\t\t}\n\t\treturn top\n\t}\n\n\tprivate bubbleUp(index: number): void {\n\t\tlet current = index\n\t\twhile (current > 0) {\n\t\t\tconst parentIndex = (current - 1) >> 1\n\t\t\tif (this.cmp(this.data[current] as Operation, this.data[parentIndex] as Operation) >= 0) break\n\t\t\tthis.swap(current, parentIndex)\n\t\t\tcurrent = parentIndex\n\t\t}\n\t}\n\n\tprivate sinkDown(index: number): void {\n\t\tconst length = this.data.length\n\t\tlet current = index\n\t\twhile (true) {\n\t\t\tlet smallest = current\n\t\t\tconst left = 2 * current + 1\n\t\t\tconst right = 2 * current + 2\n\n\t\t\tif (\n\t\t\t\tleft < length &&\n\t\t\t\tthis.cmp(this.data[left] as Operation, this.data[smallest] as Operation) < 0\n\t\t\t) {\n\t\t\t\tsmallest = left\n\t\t\t}\n\t\t\tif (\n\t\t\t\tright < length &&\n\t\t\t\tthis.cmp(this.data[right] as Operation, this.data[smallest] as Operation) < 0\n\t\t\t) {\n\t\t\t\tsmallest = right\n\t\t\t}\n\n\t\t\tif (smallest === current) break\n\t\t\tthis.swap(current, smallest)\n\t\t\tcurrent = smallest\n\t\t}\n\t}\n\n\tprivate swap(i: number, j: number): void {\n\t\tconst tmp = this.data[i] as Operation\n\t\tthis.data[i] = this.data[j] as Operation\n\t\tthis.data[j] = tmp\n\t}\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/internal.ts","../src/errors/error-fixes.ts","../src/errors/errors.ts","../src/clock/hlc.ts","../src/operations/op-data-binary.ts","../src/operations/operation.ts","../src/operations/canonical-body.ts","../src/operations/content-hash.ts","../src/events/event-emitter.ts","../src/version-vector/topological-sort.ts","../src/schema/quote-ident.ts","../src/schema/sql-gen.ts","../src/schema/constraint-relaxation.ts","../src/fold/stamp.ts","../src/fold/field-kind.ts"],"sourcesContent":["// Internal exports — shared within @kora packages but NOT part of the public API.\n// Other @kora packages can import from '@korajs/core/internal' if needed.\n\nexport { canonicalize, computeOperationId } from './operations/content-hash'\nexport { SimpleEventEmitter } from './events/event-emitter'\nexport { validateOperationParams } from './operations/operation'\nexport { topologicalSort } from './version-vector/topological-sort'\nexport {\n\tcollectionIndexName,\n\tenumCheckConstraint,\n\tlegacyCollectionIndexName,\n\tSCHEMA_CEILING_DIRECTIVE,\n\tsqlDefaultLiteral,\n\tsqlStringLiteral,\n} from './schema/sql-gen'\nexport {\n\tisKoraInternalColumn,\n\tisKoraSqliteEnumCheck,\n\tisPostgresEnumCheckDefinition,\n\tparseEnumCheckDefinition,\n\tparseSqliteCheckConstraints,\n\tplanPostgresConstraintRelaxation,\n\tplanSqliteConstraintRelaxation,\n\treadSqliteTableCatalog,\n\tsqliteCheckConstraintDefinition,\n\tsqliteCheckReferencesColumn,\n\tsqliteConstraintRelaxationStatements,\n\tsqliteTableNeedsRelaxation,\n} from './schema/constraint-relaxation'\nexport type {\n\tEnumCheckShape,\n\tSqliteCheckConstraint,\n\tSqliteColumnInfo,\n\tSqliteForeignKeyInfo,\n\tSqliteQueryFn,\n\tSqliteTableCatalog,\n} from './schema/constraint-relaxation'\nexport { compareStamps, stampOf } from './fold/stamp'\nexport { planField } from './fold/field-kind'\nexport type { FieldPlan } from './fold/field-kind'\n","/**\n * Human-readable remediation hints keyed by {@link KoraError} `code`.\n */\nexport const KORA_ERROR_FIX_SUGGESTIONS: Record<string, string> = {\n\tSCHEMA_VALIDATION:\n\t\t'Review your defineSchema() definition: every collection needs at least one field and a positive version.',\n\tOPERATION_ERROR:\n\t\t'Check the operation payload matches your schema field types and required fields.',\n\tMERGE_CONFLICT:\n\t\t'Add a custom resolver for the conflicting field in defineSchema(), or adjust constraint onConflict rules.',\n\tSYNC_ERROR:\n\t\t'Verify the sync server URL, auth token, and that the server accepts your schema version.',\n\tSTORAGE_ERROR:\n\t\t'Confirm the storage adapter is supported in this environment and the database path is writable.',\n\tCLOCK_DRIFT:\n\t\t'Sync device wall-clock time (NTP). Kora refuses new timestamps when drift exceeds five minutes.',\n\tINVALID_TIMESTAMP_FIELDS:\n\t\t'HLC timestamps need non-negative integer wallTime (< 10^15) and logical (<= 99999). Generate timestamps through HybridLogicalClock instead of building them by hand.',\n\tPERSISTENCE_ERROR:\n\t\t'IndexedDB persistence failed; check browser storage settings and available disk quota.',\n\tMISSING_WORKER_URL:\n\t\t'Pass store.workerUrl pointing to sqlite-wasm-worker.js (see create-kora-app templates).',\n\tINVALID_SYNC_URL:\n\t\t'Use ws:// or wss:// for WebSocket transport, or http(s):// for HTTP long-polling.',\n\tAPP_NOT_READY:\n\t\t'Await app.ready before querying or mutating collections, or wrap the tree in <KoraProvider app={app}>.',\n}\n\n/**\n * Returns a suggested fix for a Kora error code, if one is known.\n */\nexport function getKoraErrorFix(code: string): string | undefined {\n\treturn KORA_ERROR_FIX_SUGGESTIONS[code]\n}\n","import { getKoraErrorFix } from './error-fixes'\n\n/**\n * Base error class for all Kora errors.\n * Every error includes a machine-readable code and optional context for debugging.\n */\nexport class KoraError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly code: string,\n\t\tpublic readonly context?: Record<string, unknown>,\n\t) {\n\t\tsuper(message)\n\t\tthis.name = 'KoraError'\n\t}\n\n\t/**\n\t * Actionable hint for resolving this error (from registry or error context).\n\t */\n\tget fix(): string | undefined {\n\t\tconst fromContext = this.context?.fix\n\t\tif (typeof fromContext === 'string' && fromContext.length > 0) {\n\t\t\treturn fromContext\n\t\t}\n\t\treturn getKoraErrorFix(this.code)\n\t}\n}\n\n/**\n * Thrown when schema validation fails during defineSchema() or at app initialization.\n */\nexport class SchemaValidationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'SCHEMA_VALIDATION', context)\n\t\tthis.name = 'SchemaValidationError'\n\t}\n}\n\n/**\n * Thrown when an operation is invalid or cannot be created.\n */\nexport class OperationError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'OPERATION_ERROR', context)\n\t\tthis.name = 'OperationError'\n\t}\n}\n\n/**\n * Thrown by the local write path when an operation would exceed `maxOperationBytes`\n * (RT-86): the server refuses such an operation, so the write is refused before it is\n * accepted, with nothing written.\n */\nexport class OperationTooLargeError extends KoraError {\n\tconstructor(\n\t\tpublic readonly collection: string,\n\t\tpublic readonly recordId: string,\n\t\tpublic readonly bytes: number,\n\t\tpublic readonly maxBytes: number,\n\t) {\n\t\tsuper(\n\t\t\t`A write to ${collection}/${recordId} would produce an operation of ${bytes} bytes, more than the ${maxBytes}-byte limit (maxOperationBytes). Nothing was written. Store large content as a blob (t.blob()) or split it across records.`,\n\t\t\t'OPERATION_TOO_LARGE',\n\t\t\t{ collection, recordId, bytes, maxBytes },\n\t\t)\n\t\tthis.name = 'OperationTooLargeError'\n\t}\n}\n\n/**\n * Thrown when a merge conflict cannot be automatically resolved.\n */\nexport class MergeConflictError extends KoraError {\n\tconstructor(\n\t\tpublic readonly operationA: { id: string; collection: string },\n\t\tpublic readonly operationB: { id: string; collection: string },\n\t\tpublic readonly field: string,\n\t) {\n\t\tsuper(\n\t\t\t`Merge conflict on field \"${field}\" in collection \"${operationA.collection}\"`,\n\t\t\t'MERGE_CONFLICT',\n\t\t\t{ operationA: operationA.id, operationB: operationB.id, field },\n\t\t)\n\t\tthis.name = 'MergeConflictError'\n\t}\n}\n\n/**\n * Thrown when a sync error occurs.\n */\nexport class SyncError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'SYNC_ERROR', context)\n\t\tthis.name = 'SyncError'\n\t}\n}\n\n/**\n * Thrown when a storage operation fails.\n */\nexport class StorageError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'STORAGE_ERROR', context)\n\t\tthis.name = 'StorageError'\n\t}\n}\n\n/**\n * Thrown when collection/query APIs are used before {@link KoraApp.ready} resolves.\n */\nexport class AppNotReadyError extends KoraError {\n\tconstructor(detail: string) {\n\t\tsuper(detail, 'APP_NOT_READY', {\n\t\t\tfix: 'Await app.ready, or wrap your UI in <KoraProvider app={app}> before calling useQuery().',\n\t\t})\n\t\tthis.name = 'AppNotReadyError'\n\t}\n}\n\n/**\n * Thrown when the HLC detects excessive clock drift.\n * Drift > 60s: warning. Drift > 5min: this error is thrown, refusing to generate timestamps.\n */\nexport class RemoteClockDriftError extends KoraError {\n\tconstructor(\n\t\tpublic readonly remoteWallTime: number,\n\t\tpublic readonly localReferenceTime: number,\n\t) {\n\t\tconst aheadSeconds = Math.round((remoteWallTime - localReferenceTime) / 1000)\n\t\tsuper(\n\t\t\t`Rejected remote timestamp ${aheadSeconds}s ahead of local reference time. The sending device's clock is set too far in the future.`,\n\t\t\t'REMOTE_CLOCK_DRIFT',\n\t\t\t{ remoteWallTime, localReferenceTime, aheadSeconds },\n\t\t)\n\t\tthis.name = 'RemoteClockDriftError'\n\t}\n}\n\n/**\n * Thrown when an HLC timestamp has structurally invalid fields: non-integer or\n * negative wallTime/logical, or a logical counter beyond the serializable cap.\n * Rejected BEFORE any clock state changes, so a malformed remote timestamp can\n * never corrupt a replica's clock or break the lexicographic ordering of the\n * serialized form.\n */\nexport class InvalidTimestampError extends KoraError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly wallTime: number,\n\t\tpublic readonly logical: number,\n\t) {\n\t\tsuper(message, 'INVALID_TIMESTAMP_FIELDS', { wallTime, logical })\n\t\tthis.name = 'InvalidTimestampError'\n\t}\n}\n\nexport class ClockDriftError extends KoraError {\n\tconstructor(\n\t\tpublic readonly currentHlcTime: number,\n\t\tpublic readonly physicalTime: number,\n\t) {\n\t\tconst driftSeconds = Math.round((currentHlcTime - physicalTime) / 1000)\n\t\tsuper(\n\t\t\t`Clock drift of ${driftSeconds}s detected. Physical time is behind HLC by more than 5 minutes. This indicates a severe clock issue.`,\n\t\t\t'CLOCK_DRIFT',\n\t\t\t{ currentHlcTime, physicalTime, driftSeconds },\n\t\t)\n\t\tthis.name = 'ClockDriftError'\n\t}\n}\n","import { ClockDriftError, InvalidTimestampError, RemoteClockDriftError } from '../errors/errors'\nimport type { HLCTimestamp, TimeSource } from '../types'\n\n/** Default time source using the system clock */\nconst systemTimeSource: TimeSource = { now: () => Date.now() }\n\n/**\n * Highest logical counter value that fits the 5-digit zero-padded slot in the\n * serialized timestamp form. The serialized string must sort lexicographically\n * in exactly the same order as {@link HybridLogicalClock.compare}; a 6-digit\n * logical value would shift the padding and silently break that invariant for\n * every stored `_version` column and operation timestamp. When the counter\n * would exceed this cap, the clock carries into wallTime instead\n * (wallTime + 1, logical = 0), which preserves both monotonicity and the\n * serialized ordering.\n */\nexport const MAX_LOGICAL = 99_999\n\n/** Number of distinct logical values; the modulus used when carrying into wallTime. */\nconst LOGICAL_MODULUS = MAX_LOGICAL + 1\n\n/**\n * Exclusive upper bound on serializable wallTime: the serialized form\n * zero-pads wallTime to 15 digits, so a 16-digit value would break the\n * lexicographic ordering. 10^15 ms is roughly the year 33658 — unreachable by\n * honest clocks, only by hand-built timestamps.\n */\nconst WALL_TIME_LIMIT = 10 ** 15\n\n/** Maximum allowed drift before warning (60 seconds) */\nconst DRIFT_WARN_MS = 60_000\n\n/** Drift beyond which the error-severity callback fires (5 minutes) */\nconst DRIFT_ERROR_MS = 5 * 60_000\n\n/** Maximum future skew accepted from a remote timestamp before it is rejected (5 minutes) */\nconst MAX_REMOTE_FUTURE_MS = 5 * 60_000\n\n/**\n * Hybrid Logical Clock implementation based on Kulkarni et al.\n *\n * Provides a total order that respects causality without requiring synchronized clocks.\n * Each call to now() returns a timestamp strictly greater than the previous one.\n *\n * @example\n * ```typescript\n * const clock = new HybridLogicalClock('node-1')\n * const ts1 = clock.now()\n * const ts2 = clock.now()\n * // HybridLogicalClock.compare(ts1, ts2) < 0 (ts1 is earlier)\n * ```\n */\nexport class HybridLogicalClock {\n\tprivate wallTime = 0\n\tprivate logical = 0\n\tprivate referenceOffsetMs: number | null = null\n\n\tconstructor(\n\t\tprivate readonly nodeId: string,\n\t\tprivate readonly timeSource: TimeSource = systemTimeSource,\n\t\tprivate readonly onDriftWarning?: (driftMs: number) => void,\n\t\tprivate readonly onDriftError?: (driftMs: number) => void,\n\t) {}\n\n\t/**\n\t * Records the known offset between an external time reference (usually the\n\t * sync server, learned at handshake) and this device's physical clock:\n\t * `referenceTime - localPhysicalTime`. Once set, drift evaluation and remote\n\t * timestamp validation are performed against reference-corrected time, so a\n\t * device with a wrong local clock still validates remote timestamps correctly.\n\t */\n\tsetReferenceOffset(offsetMs: number): void {\n\t\tthis.referenceOffsetMs = offsetMs\n\t}\n\n\tgetReferenceOffset(): number | null {\n\t\treturn this.referenceOffsetMs\n\t}\n\n\t/** Physical time corrected by the known reference offset, when available. */\n\tprivate effectiveTime(physicalTime: number): number {\n\t\treturn physicalTime + (this.referenceOffsetMs ?? 0)\n\t}\n\n\t/**\n\t * Generate a new timestamp for a local event.\n\t * Guarantees monotonicity: each call returns a timestamp strictly greater than the previous.\n\t *\n\t * Never throws and never blocks a local write: if the physical clock has\n\t * fallen behind the HLC (e.g. the user corrected a fast clock), the HLC\n\t * freezes wallTime and advances the logical counter, and drift is reported\n\t * through the onDriftWarning / onDriftError callbacks instead.\n\t */\n\tnow(): HLCTimestamp {\n\t\tconst physicalTime = this.timeSource.now()\n\t\tthis.checkDrift(physicalTime)\n\n\t\tif (physicalTime > this.wallTime) {\n\t\t\tthis.wallTime = physicalTime\n\t\t\tthis.logical = 0\n\t\t} else {\n\t\t\tthis.logical++\n\t\t}\n\t\tthis.carryLogicalOverflow()\n\n\t\treturn { wallTime: this.wallTime, logical: this.logical, nodeId: this.nodeId }\n\t}\n\n\t/**\n\t * Update clock on receiving a remote timestamp.\n\t * Merges the remote clock state with the local state to maintain causal ordering.\n\t *\n\t * @throws {InvalidTimestampError} If the remote timestamp has non-integer or\n\t * negative wallTime/logical, or logical beyond {@link MAX_LOGICAL}. Checked\n\t * BEFORE any state change, so malformed input cannot corrupt this clock.\n\t * @throws {RemoteClockDriftError} If the remote timestamp is more than 5 minutes\n\t * ahead of reference-corrected local time. Validation happens BEFORE any state\n\t * is adopted, so a rejected timestamp cannot poison this clock. When no\n\t * reference offset is known and this clock is uninitialized (cold start with\n\t * a possibly-wrong local clock), validation is skipped, matching the previous\n\t * cold-start behavior but now without a corrupting failure mode afterward.\n\t */\n\treceive(remote: HLCTimestamp): HLCTimestamp {\n\t\tconst physicalTime = this.timeSource.now()\n\t\tconst wasColdStart = this.wallTime === 0\n\n\t\t// Validate before adopting anything. Malformed fields would corrupt the\n\t\t// clock's arithmetic (NaN/fractional wallTime) or overflow the serialized\n\t\t// form's 5-digit logical slot, so they are rejected outright.\n\t\tif (\n\t\t\t!Number.isInteger(remote.wallTime) ||\n\t\t\tremote.wallTime < 0 ||\n\t\t\t!Number.isInteger(remote.logical) ||\n\t\t\tremote.logical < 0 ||\n\t\t\tremote.logical > MAX_LOGICAL\n\t\t) {\n\t\t\tthrow new InvalidTimestampError(\n\t\t\t\t`Rejected remote HLC timestamp with invalid fields (wallTime=${remote.wallTime}, logical=${remote.logical}). wallTime and logical must be non-negative integers and logical must not exceed ${MAX_LOGICAL}.`,\n\t\t\t\tremote.wallTime,\n\t\t\t\tremote.logical,\n\t\t\t)\n\t\t}\n\n\t\t// A far-future remote timestamp must never become this clock's state.\n\t\tconst canValidate = this.referenceOffsetMs !== null || !wasColdStart\n\t\tif (canValidate) {\n\t\t\tconst reference = this.effectiveTime(physicalTime)\n\t\t\tif (remote.wallTime > reference + MAX_REMOTE_FUTURE_MS) {\n\t\t\t\tthrow new RemoteClockDriftError(remote.wallTime, reference)\n\t\t\t}\n\t\t}\n\n\t\tif (physicalTime > this.wallTime && physicalTime > remote.wallTime) {\n\t\t\tthis.wallTime = physicalTime\n\t\t\tthis.logical = 0\n\t\t} else if (remote.wallTime > this.wallTime) {\n\t\t\tthis.wallTime = remote.wallTime\n\t\t\tthis.logical = remote.logical + 1\n\t\t} else if (this.wallTime === remote.wallTime) {\n\t\t\tthis.logical = Math.max(this.logical, remote.logical) + 1\n\t\t} else {\n\t\t\t// this.wallTime > remote.wallTime && this.wallTime >= physicalTime\n\t\t\tthis.logical++\n\t\t}\n\t\tthis.carryLogicalOverflow()\n\n\t\t// Report (never throw) drift after merging. Cold start is exempt from\n\t\t// reporting to avoid false positives when the local clock is simply wrong.\n\t\tif (!wasColdStart) {\n\t\t\tthis.checkDrift(physicalTime)\n\t\t}\n\n\t\treturn { wallTime: this.wallTime, logical: this.logical, nodeId: this.nodeId }\n\t}\n\n\t/**\n\t * Advance this clock to at least the given timestamp.\n\t *\n\t * Used after a timestamp rebase rewrites unsynced operations: future `now()`\n\t * timestamps must sort after every rebased operation, otherwise a write issued\n\t * immediately after the rebase could interleave with (or precede) rebased ops\n\t * and break the log's total order. Never moves the clock backward — a\n\t * timestamp at or before the current state is a no-op, preserving the\n\t * monotonicity guarantee of `now()`.\n\t *\n\t * Inputs with logical > MAX_LOGICAL are normalized deterministically by\n\t * carrying the excess into wallTime, so the adopted state always leaves room\n\t * for the next increment inside the serializable range.\n\t */\n\tadvanceTo(ts: HLCTimestamp): void {\n\t\t// Deterministic normalization: adopting given values verbatim could plant\n\t\t// an unserializable logical counter inside the clock.\n\t\tconst normalized: HLCTimestamp = {\n\t\t\twallTime: ts.wallTime + Math.floor(ts.logical / LOGICAL_MODULUS),\n\t\t\tlogical: ts.logical % LOGICAL_MODULUS,\n\t\t\tnodeId: ts.nodeId,\n\t\t}\n\t\tconst current: HLCTimestamp = {\n\t\t\twallTime: this.wallTime,\n\t\t\tlogical: this.logical,\n\t\t\tnodeId: this.nodeId,\n\t\t}\n\t\tif (HybridLogicalClock.compare(normalized, current) > 0) {\n\t\t\tthis.wallTime = normalized.wallTime\n\t\t\tthis.logical = normalized.logical\n\t\t}\n\t}\n\n\t/**\n\t * Carry the logical counter into wallTime when an increment pushed it past\n\t * MAX_LOGICAL. Bumping wallTime by 1ms keeps the timestamp strictly greater\n\t * than everything issued before (monotonicity) while keeping the logical\n\t * counter inside the 5-digit slot the serialized form depends on.\n\t */\n\tprivate carryLogicalOverflow(): void {\n\t\tif (this.logical > MAX_LOGICAL) {\n\t\t\tthis.wallTime += 1\n\t\t\tthis.logical = 0\n\t\t}\n\t}\n\n\t/**\n\t * Compare two timestamps. Returns negative if a < b, positive if a > b, zero if equal.\n\t * Total order: wallTime first, then logical, then nodeId (lexicographic).\n\t */\n\tstatic compare(a: HLCTimestamp, b: HLCTimestamp): number {\n\t\tif (a.wallTime !== b.wallTime) return a.wallTime - b.wallTime\n\t\tif (a.logical !== b.logical) return a.logical - b.logical\n\t\tif (a.nodeId < b.nodeId) return -1\n\t\tif (a.nodeId > b.nodeId) return 1\n\t\treturn 0\n\t}\n\n\t/**\n\t * Serialize an HLC timestamp to a string that sorts lexicographically.\n\t * Format: zero-padded wallTime:logical:nodeId\n\t *\n\t * @throws {InvalidTimestampError} If wallTime does not fit 15 digits or\n\t * logical does not fit 5 digits (or either is negative/non-integer). Such a\n\t * value would overflow its zero-padded slot and the serialized string would\n\t * no longer sort in the same order as {@link HybridLogicalClock.compare} —\n\t * silently corrupting every LWW comparison on stored `_version` columns.\n\t * Internal clocks can no longer produce such values (the logical counter\n\t * carries into wallTime); this guards against hand-built timestamps.\n\t */\n\tstatic serialize(ts: HLCTimestamp): string {\n\t\tif (\n\t\t\t!Number.isInteger(ts.wallTime) ||\n\t\t\tts.wallTime < 0 ||\n\t\t\tts.wallTime >= WALL_TIME_LIMIT ||\n\t\t\t!Number.isInteger(ts.logical) ||\n\t\t\tts.logical < 0 ||\n\t\t\tts.logical > MAX_LOGICAL\n\t\t) {\n\t\t\tthrow new InvalidTimestampError(\n\t\t\t\t`Cannot serialize HLC timestamp (wallTime=${ts.wallTime}, logical=${ts.logical}): wallTime must be an integer in [0, 10^15) and logical an integer in [0, ${MAX_LOGICAL}]. Values outside these ranges overflow the zero-padded serialized form, which must sort lexicographically identically to HybridLogicalClock.compare.`,\n\t\t\t\tts.wallTime,\n\t\t\t\tts.logical,\n\t\t\t)\n\t\t}\n\t\tconst wall = ts.wallTime.toString().padStart(15, '0')\n\t\tconst log = ts.logical.toString().padStart(5, '0')\n\t\treturn `${wall}:${log}:${ts.nodeId}`\n\t}\n\n\t/**\n\t * Deserialize an HLC timestamp from its serialized string form.\n\t */\n\tstatic deserialize(s: string): HLCTimestamp {\n\t\tconst parts = s.split(':')\n\t\tif (parts.length < 3) {\n\t\t\tthrow new Error(`Invalid HLC timestamp string: \"${s}\"`)\n\t\t}\n\t\treturn {\n\t\t\twallTime: Number.parseInt(parts[0] ?? '0', 10),\n\t\t\tlogical: Number.parseInt(parts[1] ?? '0', 10),\n\t\t\t// nodeId may contain colons, so rejoin remaining parts\n\t\t\tnodeId: parts.slice(2).join(':'),\n\t\t}\n\t}\n\n\t/**\n\t * Reports drift between the HLC and (reference-corrected) physical time.\n\t * Reporting only: local timestamp generation is never blocked, because the\n\t * user's data always outranks the quality of its timestamps.\n\t */\n\tprivate checkDrift(physicalTime: number): void {\n\t\tconst drift = this.wallTime - this.effectiveTime(physicalTime)\n\t\tif (drift > DRIFT_ERROR_MS) {\n\t\t\tthis.onDriftError?.(drift)\n\t\t\treturn\n\t\t}\n\t\tif (drift > DRIFT_WARN_MS) {\n\t\t\tthis.onDriftWarning?.(drift)\n\t\t}\n\t}\n}\n","import { OperationError } from '../errors/errors'\n\n/**\n * Canonical JSON-safe encoding of binary bytes stored inside `op.data` /\n * `op.previousData` (currently richtext field values). A raw Uint8Array\n * JSON-serializes to a numeric-key object and an ArrayBuffer to `{}` (silent\n * data loss), which breaks content-hash stability, persistence round-trips,\n * remote application, and merge. Tagging the bytes as base64 at\n * operation-creation time makes the hashed value, the persisted JSON, the wire\n * payload, and the value the merge engine sees the identical canonical value.\n *\n * This convention lives in `@korajs/core` because `op.data` is a core concept:\n * both `@korajs/store` (persistence, creation) and `@korajs/merge` (CRDT merge)\n * must agree on it, and neither may depend on the other.\n */\nexport interface KoraBytesValue {\n\t$koraBytes: string\n}\n\n/**\n * Type guard for the tagged binary form. Requires exactly the single\n * `$koraBytes` key so arbitrary user objects that happen to contain the key\n * are never silently reinterpreted as bytes.\n */\nexport function isKoraBytesValue(value: unknown): value is KoraBytesValue {\n\tif (typeof value !== 'object' || value === null) {\n\t\treturn false\n\t}\n\tconst record = value as Record<string, unknown>\n\treturn Object.keys(record).length === 1 && typeof record.$koraBytes === 'string'\n}\n\n/**\n * Detects the numeric-key object shape a raw Uint8Array produced when\n * JSON-serialized before the tagged encoding existed ({\"0\":1,\"1\":2,...}).\n */\nexport function isLegacyNumericByteObject(value: unknown): value is Record<string, number> {\n\tif (typeof value !== 'object' || value === null || ArrayBuffer.isView(value)) {\n\t\treturn false\n\t}\n\tconst record = value as Record<string, unknown>\n\tconst keys = Object.keys(record)\n\tif (keys.length === 0) {\n\t\treturn false\n\t}\n\tfor (let i = 0; i < keys.length; i++) {\n\t\tconst byte = record[String(i)]\n\t\tif (typeof byte !== 'number' || !Number.isInteger(byte) || byte < 0 || byte > 255) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nconst BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'\n\nconst BASE64_REVERSE: ReadonlyMap<string, number> = new Map(\n\tArray.from(BASE64_ALPHABET, (char, index) => [char, index]),\n)\n\n/**\n * Dependency-free base64 encoder. btoa is unavailable for bytes in Node and\n * Buffer is unavailable in browsers, so a manual implementation is the only\n * deterministic option that works in every runtime Kora targets.\n */\nexport function bytesToBase64(bytes: Uint8Array): string {\n\tlet out = ''\n\tfor (let i = 0; i < bytes.length; i += 3) {\n\t\tconst b0 = bytes[i] ?? 0\n\t\tconst b1 = bytes[i + 1] ?? 0\n\t\tconst b2 = bytes[i + 2] ?? 0\n\t\tconst triple = (b0 << 16) | (b1 << 8) | b2\n\t\tout += BASE64_ALPHABET[(triple >> 18) & 63] ?? ''\n\t\tout += BASE64_ALPHABET[(triple >> 12) & 63] ?? ''\n\t\tout += i + 1 < bytes.length ? (BASE64_ALPHABET[(triple >> 6) & 63] ?? '') : '='\n\t\tout += i + 2 < bytes.length ? (BASE64_ALPHABET[triple & 63] ?? '') : '='\n\t}\n\treturn out\n}\n\n/**\n * Inverse of {@link bytesToBase64}.\n */\nexport function base64ToBytes(base64: string): Uint8Array {\n\tconst cleaned = base64.replace(/=+$/, '')\n\tconst out = new Uint8Array(Math.floor((cleaned.length * 6) / 8))\n\tlet buffer = 0\n\tlet bits = 0\n\tlet index = 0\n\tfor (const char of cleaned) {\n\t\tconst value = BASE64_REVERSE.get(char)\n\t\tif (value === undefined) {\n\t\t\tthrow new OperationError(`Invalid base64 character \"${char}\" in tagged binary value.`, {\n\t\t\t\tchar,\n\t\t\t})\n\t\t}\n\t\tbuffer = (buffer << 6) | value\n\t\tbits += 6\n\t\tif (bits >= 8) {\n\t\t\tbits -= 8\n\t\t\tout[index] = (buffer >> bits) & 0xff\n\t\t\tindex += 1\n\t\t}\n\t}\n\treturn out\n}\n\n/**\n * Normalize a validated binary-or-string value into the canonical form stored\n * in `op.data`: strings pass through unchanged (backward compatible with every\n * existing operation), binary values become the tagged base64 form.\n */\nexport function encodeBytesForOpData(\n\tvalue: string | Uint8Array | ArrayBuffer,\n): string | KoraBytesValue {\n\tif (typeof value === 'string') {\n\t\treturn value\n\t}\n\tconst bytes = value instanceof Uint8Array ? value : new Uint8Array(value)\n\treturn { $koraBytes: bytesToBase64(bytes) }\n}\n\n/**\n * Reverse of {@link encodeBytesForOpData}, tolerant of every shape a binary\n * op-data value has ever taken: canonical strings and tagged bytes, in-memory\n * Uint8Array/ArrayBuffer (ops that never round-tripped through JSON), and\n * pre-fix numeric-key objects.\n */\nexport function decodeBytesFromOpData(value: unknown): string | Uint8Array {\n\tif (typeof value === 'string') {\n\t\treturn value\n\t}\n\tif (value instanceof Uint8Array) {\n\t\treturn value\n\t}\n\tif (value instanceof ArrayBuffer) {\n\t\treturn new Uint8Array(value)\n\t}\n\tif (isKoraBytesValue(value)) {\n\t\treturn base64ToBytes(value.$koraBytes)\n\t}\n\t// Pre-fix era: a raw Uint8Array in op.data JSON-serialized to a numeric-key\n\t// object. Nothing shipped relies on that shape, but dev databases may still\n\t// contain such operations — reconstructing the bytes here is cheap and\n\t// prevents those databases from crashing on replay/apply/merge. No migration\n\t// is written for this; new operations always use the tagged form.\n\tif (isLegacyNumericByteObject(value)) {\n\t\tconst keys = Object.keys(value)\n\t\tconst bytes = new Uint8Array(keys.length)\n\t\tfor (let i = 0; i < keys.length; i++) {\n\t\t\tbytes[i] = value[String(i)] ?? 0\n\t\t}\n\t\treturn bytes\n\t}\n\tthrow new OperationError(\n\t\t'Binary op-data value must be a string, Uint8Array, ArrayBuffer, tagged { $koraBytes } object, or legacy numeric-key byte object.',\n\t\t{ receivedType: typeof value },\n\t)\n}\n","import { HybridLogicalClock } from '../clock/hlc'\nimport { OperationError } from '../errors/errors'\nimport type { HLCTimestamp, Operation, OperationInput } from '../types'\nimport { canonicalizeOperationBody } from './canonical-body'\nimport {\n\tDEFAULT_OPERATION_HASH_VERSION,\n\ttype OperationHashVersion,\n\tcomputeOperationId,\n} from './content-hash'\n\n/** Options for {@link createOperation}. */\nexport interface CreateOperationOptions {\n\t/**\n\t * Content-hash version of the new operation's id. Defaults to 2 (protocol v2),\n\t * which commits the id to every semantic field (CORE-1); 1 is the legacy hash.\n\t */\n\thashVersion?: OperationHashVersion\n}\n\n/**\n * Creates an immutable, content-addressed Operation from the given parameters.\n * The operation is deep-frozen after creation — it cannot be modified.\n *\n * @param input - The operation parameters (without id, which is computed)\n * @param clock - The HLC clock to generate the timestamp\n * @param options - Optional hash version (default 2)\n * @returns A frozen Operation with a content-addressed id\n *\n * @example\n * ```typescript\n * const op = await createOperation({\n * nodeId: 'device-1',\n * type: 'insert',\n * collection: 'todos',\n * recordId: 'rec-1',\n * data: { title: 'Ship it' },\n * previousData: null,\n * sequenceNumber: 1,\n * causalDeps: [],\n * schemaVersion: 1,\n * }, clock)\n * ```\n */\nexport async function createOperation(\n\trawInput: OperationInput,\n\tclock: HybridLogicalClock,\n\toptions: CreateOperationOptions = {},\n): Promise<Operation> {\n\tvalidateOperationParams(rawInput)\n\t// One canonical body (canonical-body.ts), applied once, here: the id is computed over\n\t// it and it is what the operation carries, so the hashed content is exactly what the\n\t// op log, the wire and the fold see (RT-72, RT-79, RT-80).\n\tconst input = canonicalizeOperationBody(rawInput)\n\tconst hashVersion = options.hashVersion ?? DEFAULT_OPERATION_HASH_VERSION\n\n\tconst timestamp = clock.now()\n\tconst id =\n\t\thashVersion === 1\n\t\t\t? await computeOperationId(input, HybridLogicalClock.serialize(timestamp))\n\t\t\t: await computeOperationId({ ...input, timestamp }, hashVersion)\n\n\tconst operation: Operation = {\n\t\tid,\n\t\tnodeId: input.nodeId,\n\t\ttype: input.type,\n\t\tcollection: input.collection,\n\t\trecordId: input.recordId,\n\t\tdata: input.data ? { ...input.data } : null,\n\t\tpreviousData: input.previousData ? { ...input.previousData } : null,\n\t\ttimestamp,\n\t\tsequenceNumber: input.sequenceNumber,\n\t\tcausalDeps: [...input.causalDeps],\n\t\tschemaVersion: input.schemaVersion,\n\t\t...(input.atomicOps !== undefined && Object.keys(input.atomicOps).length > 0\n\t\t\t? { atomicOps: { ...input.atomicOps } }\n\t\t\t: {}),\n\t\t...(input.transactionId !== undefined ? { transactionId: input.transactionId } : {}),\n\t\t...(input.mutationName !== undefined ? { mutationName: input.mutationName } : {}),\n\t\t...(hashVersion !== 1 ? { hashVersion } : {}),\n\t}\n\n\treturn deepFreeze(operation)\n}\n\n/**\n * Validates operation input parameters. Throws OperationError with\n * contextual information on validation failure.\n */\nexport function validateOperationParams(input: OperationInput): void {\n\tif (!input.nodeId || typeof input.nodeId !== 'string') {\n\t\tthrow new OperationError('nodeId is required and must be a non-empty string', {\n\t\t\treceived: input.nodeId,\n\t\t})\n\t}\n\n\tif (!input.type || !['insert', 'update', 'delete'].includes(input.type)) {\n\t\tthrow new OperationError('type must be \"insert\", \"update\", or \"delete\"', {\n\t\t\treceived: input.type,\n\t\t})\n\t}\n\n\tif (!input.collection || typeof input.collection !== 'string') {\n\t\tthrow new OperationError('collection is required and must be a non-empty string', {\n\t\t\treceived: input.collection,\n\t\t})\n\t}\n\n\tif (!input.recordId || typeof input.recordId !== 'string') {\n\t\tthrow new OperationError('recordId is required and must be a non-empty string', {\n\t\t\treceived: input.recordId,\n\t\t})\n\t}\n\n\tif (input.type === 'insert' && input.data === null) {\n\t\tthrow new OperationError('insert operations must include data', {\n\t\t\ttype: input.type,\n\t\t\tcollection: input.collection,\n\t\t})\n\t}\n\n\tif (input.type === 'update' && input.data === null) {\n\t\tthrow new OperationError('update operations must include data with changed fields', {\n\t\t\ttype: input.type,\n\t\t\tcollection: input.collection,\n\t\t})\n\t}\n\n\tif (input.type === 'update' && input.previousData === null) {\n\t\tthrow new OperationError(\n\t\t\t'update operations must include previousData for 3-way merge support',\n\t\t\t{\n\t\t\t\ttype: input.type,\n\t\t\t\tcollection: input.collection,\n\t\t\t},\n\t\t)\n\t}\n\n\tif (input.type === 'delete' && input.data !== null) {\n\t\tthrow new OperationError('delete operations must have null data', {\n\t\t\ttype: input.type,\n\t\t\tcollection: input.collection,\n\t\t})\n\t}\n\n\tif (typeof input.sequenceNumber !== 'number' || input.sequenceNumber < 0) {\n\t\tthrow new OperationError('sequenceNumber must be a non-negative number', {\n\t\t\treceived: input.sequenceNumber,\n\t\t})\n\t}\n\n\tif (!Array.isArray(input.causalDeps)) {\n\t\tthrow new OperationError('causalDeps must be an array of operation IDs', {\n\t\t\treceived: typeof input.causalDeps,\n\t\t})\n\t}\n\n\tif (typeof input.schemaVersion !== 'number' || input.schemaVersion < 1) {\n\t\tthrow new OperationError('schemaVersion must be a positive number', {\n\t\t\treceived: input.schemaVersion,\n\t\t})\n\t}\n}\n\n/**\n * Verify an operation's id against its content, using the hash version the\n * operation declares (`hashVersion`, absent = 1). A version-1 id does not cover\n * previousData, sequenceNumber, causalDeps or schemaVersion (CORE-1), so only a\n * version-2 operation is protected against rewriting those.\n *\n * Returns false for an unknown hash version.\n *\n * @param op - The operation to verify\n * @returns true when `op.id` is the content hash of `op`\n */\nexport async function verifyOperationId(op: Operation): Promise<boolean> {\n\tconst version = op.hashVersion ?? 1\n\tif (version !== 1 && version !== 2) return false\n\tlet expectedId: string\n\ttry {\n\t\texpectedId = await computeOperationId(\n\t\t\t{\n\t\t\t\tnodeId: op.nodeId,\n\t\t\t\ttype: op.type,\n\t\t\t\tcollection: op.collection,\n\t\t\t\trecordId: op.recordId,\n\t\t\t\tdata: op.data,\n\t\t\t\tpreviousData: op.previousData,\n\t\t\t\ttimestamp: op.timestamp,\n\t\t\t\tsequenceNumber: op.sequenceNumber,\n\t\t\t\tcausalDeps: op.causalDeps,\n\t\t\t\tschemaVersion: op.schemaVersion,\n\t\t\t\t...(op.atomicOps !== undefined ? { atomicOps: op.atomicOps } : {}),\n\t\t\t},\n\t\t\tversion,\n\t\t)\n\t} catch {\n\t\t// A body with no canonical form (never produced by createOperation, never\n\t\t// decoded from JSON) cannot be the content of any id.\n\t\treturn false\n\t}\n\treturn op.id === expectedId\n}\n\n/**\n * Verify the integrity of an operation by recomputing its content hash.\n * Returns true if the id matches the recomputed hash. Same as\n * {@link verifyOperationId}.\n */\nexport async function verifyOperationIntegrity(op: Operation): Promise<boolean> {\n\treturn verifyOperationId(op)\n}\n\n/**\n * Type guard for Operation interface.\n */\nexport function isValidOperation(value: unknown): value is Operation {\n\tif (typeof value !== 'object' || value === null) return false\n\tconst op = value as Record<string, unknown>\n\treturn (\n\t\ttypeof op.id === 'string' &&\n\t\ttypeof op.nodeId === 'string' &&\n\t\t(op.type === 'insert' || op.type === 'update' || op.type === 'delete') &&\n\t\ttypeof op.collection === 'string' &&\n\t\ttypeof op.recordId === 'string' &&\n\t\ttypeof op.sequenceNumber === 'number' &&\n\t\tArray.isArray(op.causalDeps) &&\n\t\ttypeof op.schemaVersion === 'number' &&\n\t\ttypeof op.timestamp === 'object' &&\n\t\top.timestamp !== null\n\t)\n}\n\nfunction deepFreeze<T>(obj: T): T {\n\tif (typeof obj !== 'object' || obj === null) return obj\n\t// Typed arrays (e.g. richtext Yjs blobs) cannot be frozen in JS engines.\n\tif (ArrayBuffer.isView(obj)) return obj\n\tObject.freeze(obj)\n\tfor (const value of Object.values(obj)) {\n\t\tif (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {\n\t\t\tdeepFreeze(value)\n\t\t}\n\t}\n\treturn obj\n}\n","import { KoraError } from '../errors/errors'\nimport type { AtomicOp, Operation, OperationInput } from '../types'\nimport { bytesToBase64 } from './op-data-binary'\nimport { verifyOperationId } from './operation'\n\n/**\n * Thrown when an operation (or a record value) holds a value that has no canonical\n * JSON form: the op log and the wire are JSON, so such a value would be stored and\n * synced as something other than what was written and hashed.\n */\nexport class NonCanonicalValueError extends KoraError {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic readonly path: string,\n\t\tpublic readonly receivedType: string,\n\t) {\n\t\tsuper(message, 'NON_CANONICAL_VALUE', { path, receivedType })\n\t\tthis.name = 'NonCanonicalValueError'\n\t}\n}\n\n/**\n * The one canonical form of an operation body (RT-72 family: RT-79, RT-80, RT-83).\n *\n * The op log, the wire (JSON and protobuf, whose data is JSON) and every store hold an\n * operation as JSON. An operation's id is a hash of its body. So the body is put in its\n * canonical form ONCE, when the operation is created (and when a legacy operation is\n * ingested or rebuilt), and the canonical body is what is hashed, stored, sent and\n * folded: the four can never disagree, by construction.\n *\n * | Value | Canonical form |\n * |-----------------------------------------|-------------------------------------------------|\n * | `undefined` member of an insert's data | absent |\n * | `undefined` member of an update's data | `null`: the field is CLEARED (beta.12 meaning) |\n * | `undefined` member of an update's `previousData` | `null` (the field held no value) |\n * | `undefined` member inside an object value | absent (as JSON drops it); a `t.json()` field refuses it at validation |\n * | `undefined` array element | `null` (as JSON writes it) |\n * | `undefined` atomic op | absent |\n * | `-0` | `0` (JSON writes `0`) |\n * | valid `Date` | its ISO 8601 string (`toISOString()`), as JSON |\n * | `Uint8Array` / `ArrayBuffer` | `{ $koraBytes: <base64> }` (the op-log binary form) |\n * | plain object (`Object.prototype` or `null` prototype) | plain object, members canonical |\n * | array (dense) | array, elements canonical |\n * | string, finite number, boolean, `null` | itself |\n * | invalid `Date`, `NaN`, `±Infinity`, `BigInt`, function, symbol, `Map`, `Set`, `WeakMap`, `RegExp`, `Promise`, any other class instance, an object with a `toJSON` method, an own `__proto__` key, a circular reference | refused: {@link NonCanonicalValueError} naming the path and the fix |\n *\n * Canonicalization is idempotent: a canonical body is returned unchanged (same\n * references where nothing changed).\n *\n * @param input - The operation body (as given to `createOperation`, or a stored operation)\n * @returns The body with `data`, `previousData` and `atomicOps` canonical\n * @throws {NonCanonicalValueError} For a value with no canonical form\n */\nexport function canonicalizeOperationBody<T extends CanonicalizableBody>(input: T): T {\n\tconst isUpdate = input.type === 'update'\n\tconst where = `${input.collection}/${input.recordId}`\n\tconst data = canonicalTopLevel(input.data, isUpdate, 'data', where)\n\tconst previousData = canonicalTopLevel(input.previousData, isUpdate, 'previousData', where)\n\tlet atomicOps = input.atomicOps\n\tif (atomicOps !== undefined) {\n\t\tconst canonical = canonicalTopLevel(\n\t\t\tatomicOps as Record<string, unknown>,\n\t\t\tfalse,\n\t\t\t'atomicOps',\n\t\t\twhere,\n\t\t) as Record<string, AtomicOp> | null\n\t\tatomicOps = canonical ?? undefined\n\t}\n\tif (data === input.data && previousData === input.previousData && atomicOps === input.atomicOps) {\n\t\treturn input\n\t}\n\treturn {\n\t\t...input,\n\t\tdata,\n\t\tpreviousData,\n\t\t...(atomicOps !== undefined ? { atomicOps } : {}),\n\t}\n}\n\n/** The members of an operation body that {@link canonicalizeOperationBody} reads. */\nexport interface CanonicalizableBody {\n\ttype: OperationInput['type']\n\tcollection: string\n\trecordId: string\n\tdata: Record<string, unknown> | null\n\tpreviousData: Record<string, unknown> | null\n\tatomicOps?: Record<string, AtomicOp>\n}\n\n/**\n * The canonical form of one value inside an operation body or a record field (see\n * {@link canonicalizeOperationBody} for the table). Exported so schema validation\n * produces exactly the values an operation will carry.\n *\n * @param value - Any value\n * @param path - Where the value is, for the error message (e.g. `notes.extra.when`)\n * @returns The canonical value (the same reference when it already is)\n * @throws {NonCanonicalValueError} For a value with no canonical form\n */\nexport function canonicalValue(value: unknown, path: string): unknown {\n\treturn canonical(value, path, new Set())\n}\n\nfunction canonicalTopLevel(\n\trecord: Record<string, unknown> | null,\n\tundefinedIsNull: boolean,\n\tlabel: string,\n\twhere: string,\n): Record<string, unknown> | null {\n\tif (record === null || record === undefined) return null\n\tif (!isPlainObject(record)) {\n\t\tthrow new NonCanonicalValueError(\n\t\t\t`Operation ${label} of ${where} must be a plain object, got ${describe(record)}.`,\n\t\t\tlabel,\n\t\t\tdescribe(record),\n\t\t)\n\t}\n\tlet changed = false\n\tconst out: Record<string, unknown> = {}\n\tfor (const [key, member] of Object.entries(record)) {\n\t\tif (member === undefined) {\n\t\t\tchanged = true\n\t\t\t// In an update, `field: undefined` is a clear (as beta.12 applied it); the\n\t\t\t// canonical form says so explicitly. Elsewhere it is simply absent.\n\t\t\tif (undefinedIsNull) out[key] = null\n\t\t\tcontinue\n\t\t}\n\t\tconst next = canonical(member, `${where}.${key}`, new Set())\n\t\tif (!Object.is(next, member)) changed = true\n\t\tout[key] = next\n\t}\n\tif (Object.getPrototypeOf(record) !== Object.prototype) changed = true\n\treturn changed ? out : record\n}\n\nfunction canonical(value: unknown, path: string, ancestors: Set<object>): unknown {\n\tswitch (typeof value) {\n\t\tcase 'string':\n\t\tcase 'boolean':\n\t\t\treturn value\n\t\tcase 'number':\n\t\t\tif (!Number.isFinite(value)) {\n\t\t\t\tthrow refusal(path, String(value), 'Use a finite number, or null for \"no value\".')\n\t\t\t}\n\t\t\treturn Object.is(value, -0) ? 0 : value\n\t\tcase 'undefined':\n\t\t\t// Only reachable for array elements and the top level (members are handled by\n\t\t\t// the caller): JSON writes an undefined array element as null.\n\t\t\treturn null\n\t\tcase 'bigint':\n\t\t\tthrow refusal(path, 'bigint', 'Convert it to a number or a string.')\n\t\tcase 'function':\n\t\t\tthrow refusal(path, 'function', 'Store data only, not functions.')\n\t\tcase 'symbol':\n\t\t\tthrow refusal(path, 'symbol', 'Use a string instead.')\n\t}\n\tif (value === null) return null\n\tconst object = value as object\n\tif (object instanceof Uint8Array) return { $koraBytes: bytesToBase64(object) }\n\tif (object instanceof ArrayBuffer) return { $koraBytes: bytesToBase64(new Uint8Array(object)) }\n\tif (object instanceof Date) {\n\t\tconst time = object.getTime()\n\t\tif (Number.isNaN(time)) {\n\t\t\tthrow refusal(path, 'invalid Date', 'Pass a valid Date, or null.')\n\t\t}\n\t\treturn object.toISOString()\n\t}\n\tif (ancestors.has(object)) {\n\t\tthrow refusal(path, 'circular reference', 'Remove the cycle: JSON cannot hold it.')\n\t}\n\tif (Array.isArray(object)) {\n\t\tancestors.add(object)\n\t\tlet changed = false\n\t\tconst out: unknown[] = []\n\t\tfor (let index = 0; index < object.length; index++) {\n\t\t\tif (!(index in object)) {\n\t\t\t\tancestors.delete(object)\n\t\t\t\tthrow refusal(\n\t\t\t\t\t`${path}[${String(index)}]`,\n\t\t\t\t\t'array hole',\n\t\t\t\t\t'Use a dense array (fill the gap with null).',\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst item = object[index]\n\t\t\tconst next = canonical(item, `${path}[${String(index)}]`, ancestors)\n\t\t\tif (!Object.is(next, item)) changed = true\n\t\t\tout.push(next)\n\t\t}\n\t\tancestors.delete(object)\n\t\treturn changed ? out : object\n\t}\n\tif (!isPlainObject(object)) {\n\t\tconst type = describe(object)\n\t\tthrow refusal(\n\t\t\tpath,\n\t\t\ttype,\n\t\t\ttype === 'Map'\n\t\t\t\t? 'Convert it with Object.fromEntries(map).'\n\t\t\t\t: type === 'Set'\n\t\t\t\t\t? 'Convert it with [...set].'\n\t\t\t\t\t: 'Convert it to a plain object (JSON data only).',\n\t\t)\n\t}\n\tif (typeof (object as { toJSON?: unknown }).toJSON === 'function') {\n\t\tthrow refusal(path, 'object with toJSON', 'Store the value toJSON() returns instead.')\n\t}\n\t// JSON can carry an own \"__proto__\" key, but assigning it to an object sets the\n\t// prototype instead, so it would be lost on some replicas (the fold, any object\n\t// rebuilt by assignment). Refused everywhere, never silently dropped.\n\tif (Object.prototype.hasOwnProperty.call(object, '__proto__')) {\n\t\tthrow refusal(`${path}.__proto__`, '\"__proto__\" key', 'Rename the key.')\n\t}\n\tancestors.add(object)\n\tlet changed = Object.getPrototypeOf(object) !== Object.prototype\n\tconst out: Record<string, unknown> = {}\n\tfor (const [key, member] of Object.entries(object)) {\n\t\tif (member === undefined) {\n\t\t\tchanged = true\n\t\t\tcontinue\n\t\t}\n\t\tconst next = canonical(member, `${path}.${key}`, ancestors)\n\t\tif (!Object.is(next, member)) changed = true\n\t\tout[key] = next\n\t}\n\tancestors.delete(object)\n\treturn changed ? out : object\n}\n\nfunction refusal(path: string, receivedType: string, fix: string): NonCanonicalValueError {\n\treturn new NonCanonicalValueError(\n\t\t`Value at \"${path}\" (${receivedType}) has no JSON form, so it cannot be stored or synced unchanged. ${fix}`,\n\t\tpath,\n\t\treceivedType,\n\t)\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\tif (value === null || typeof value !== 'object' || Array.isArray(value)) return false\n\tconst proto = Object.getPrototypeOf(value)\n\treturn proto === Object.prototype || proto === null\n}\n\nfunction describe(value: unknown): string {\n\tif (value === null) return 'null'\n\tif (Array.isArray(value)) return 'array'\n\tif (typeof value !== 'object') return typeof value\n\tconst ctor = (value as { constructor?: { name?: unknown } }).constructor\n\treturn typeof ctor?.name === 'string' && ctor.name.length > 0 ? ctor.name : 'object'\n}\n\n/**\n * A legacy update in its canonical body when its id PROVES the beta.12 clear (RT-85):\n * the id is the content hash of the body with every `previousData` key absent from\n * `data` restored as `null` (version 1, or version 2 with a lost declaration), and not\n * of the body as it is. Such an id can only come from a writer that hashed the clear,\n * so the restored body is the one its author wrote and applied; a body a schema\n * transform or anything else rewrote never proves it.\n *\n * Used where a legacy body enters a replica without a known provenance: a device's\n * inbound delivery, and the one-time canonicalization of stored logs. The server's\n * ingest of a protocol-1 upload applies the same rule (plus the unverifiable\n * protocol-1 case, for the session's own node).\n *\n * @param op - Any operation\n * @returns The canonical operation, or `op` itself when nothing is proven\n */\nexport async function canonicalizeProvenLegacyClear<T extends Operation>(op: T): Promise<T> {\n\tconst restored = canonicalizeLegacyOperation(op)\n\tif (restored === op) return op\n\tif (await verifyOperationId(op)) return op\n\tif (await verifyOperationId(restored)) return restored\n\tif (op.hashVersion === undefined && (await verifyOperationId({ ...restored, hashVersion: 2 }))) {\n\t\treturn restored\n\t}\n\treturn op\n}\n\n/**\n * The canonical body of a legacy (version-1, beta.12) update (RT-71, RT-83): every\n * `previousData` key absent from `data` is a clear, restored as `null`. beta.12 wrote\n * `previousData[key]` for every key it validated, so such a key held `undefined` in\n * `data`: beta.12 hashed it as `null`, applied it as a cleared field, and its JSON log\n * and upload dropped it. `null` and `undefined` hash identically under version 1, so\n * the id is unchanged.\n *\n * Applied ONCE, where a genuine beta.12 body is identified (RT-85), never at fold time:\n * the server's ingest of a protocol-1 upload, a device's own beta.12 log at the\n * upgrade, and any body whose id proves the clear\n * ({@link canonicalizeProvenLegacyClear}). The result is stored, so the server, peers\n * and the upgraded writer fold the same body. A no-op\n * for anything else (a declared version 2, an envelope, an insert or delete, an update\n * whose data covers its previousData). Idempotent.\n *\n * @param op - Any operation\n * @returns The operation with the restored data, or `op` itself\n */\nexport function canonicalizeLegacyOperation<T extends LegacyBody>(op: T): T {\n\tif (op.type !== 'update' || op.hashVersion === 2 || op.encrypted !== undefined) return op\n\tconst previous = op.previousData\n\tif (previous === null || previous === undefined) return op\n\tconst data = op.data ?? {}\n\tconst missing = Object.keys(previous).filter((key) => !(key in data))\n\tif (missing.length === 0) return op\n\tconst out: Record<string, unknown> = { ...data }\n\tfor (const key of missing) out[key] = null\n\treturn { ...op, data: out }\n}\n\n/** The members of an operation {@link canonicalizeLegacyOperation} reads. */\nexport interface LegacyBody {\n\ttype: Operation['type']\n\tdata: Record<string, unknown> | null\n\tpreviousData: Record<string, unknown> | null\n\thashVersion?: Operation['hashVersion']\n\tencrypted?: Operation['encrypted']\n}\n","import { HybridLogicalClock } from '../clock/hlc'\nimport type { AtomicOp, HLCTimestamp, OperationInput } from '../types'\nimport { canonicalizeOperationBody } from './canonical-body'\n\n/**\n * Content-hash versions for operation ids.\n *\n * - **1** (Kora <= beta.12): hashes type, collection, recordId,\n * data, timestamp, nodeId and atomicOps. previousData, sequenceNumber,\n * causalDeps and schemaVersion are NOT covered (CORE-1).\n * - **2**: additionally hashes previousData, sequenceNumber, causalDeps (as a\n * set) and schemaVersion, with the version itself as a domain tag, and binary\n * values in their canonical tagged form. Operations carry `hashVersion: 2`.\n */\nexport type OperationHashVersion = 1 | 2\n\n/** Hash version new operations get: 2 since protocol v2 (beta.13). */\nexport const DEFAULT_OPERATION_HASH_VERSION: OperationHashVersion = 2\n\n/** The operation fields a content hash can cover. */\nexport interface HashableOperation {\n\tnodeId: string\n\ttype: OperationInput['type']\n\tcollection: string\n\trecordId: string\n\tdata: Record<string, unknown> | null\n\tpreviousData: Record<string, unknown> | null\n\ttimestamp: HLCTimestamp\n\tsequenceNumber: number\n\tcausalDeps: readonly string[]\n\tschemaVersion: number\n\tatomicOps?: Record<string, AtomicOp>\n}\n\n/**\n * Compute the content-addressed ID for an operation using SHA-256.\n * The same operation content always produces the same hash, ensuring deduplication.\n *\n * Two call forms:\n * - `computeOperationId(input, serializedTimestamp)`: the version-1 hash (legacy\n * form, used by the store's rebase and node-id rotation).\n * - `computeOperationId(op, version)`: the hash of the given version over a full\n * operation (with its HLC timestamp).\n *\n * @returns A hex-encoded SHA-256 hash\n */\nexport async function computeOperationId(input: OperationInput, timestamp: string): Promise<string>\nexport async function computeOperationId(\n\top: HashableOperation,\n\tversion: OperationHashVersion,\n): Promise<string>\nexport async function computeOperationId(\n\tinput: OperationInput | HashableOperation,\n\ttimestampOrVersion: string | OperationHashVersion,\n): Promise<string> {\n\tif (typeof timestampOrVersion === 'string') {\n\t\treturn sha256Hex(canonicalize(v1HashInput(input, timestampOrVersion)))\n\t}\n\tconst op = input as HashableOperation\n\tconst serializedTs = HybridLogicalClock.serialize(op.timestamp)\n\tif (timestampOrVersion === 1) return sha256Hex(canonicalize(v1HashInput(op, serializedTs)))\n\treturn sha256Hex(canonicalize(v2HashInput(op, serializedTs)))\n}\n\nfunction v1HashInput(\n\tinput: OperationInput | HashableOperation,\n\ttimestamp: string,\n): Record<string, unknown> {\n\t// Only include atomicOps when present — ensures backward compatibility\n\t// (existing operations without atomicOps produce identical hashes).\n\tconst hashInput: Record<string, unknown> = {\n\t\ttype: input.type,\n\t\tcollection: input.collection,\n\t\trecordId: input.recordId,\n\t\tdata: input.data,\n\t\ttimestamp,\n\t\tnodeId: input.nodeId,\n\t}\n\tif (input.atomicOps !== undefined && Object.keys(input.atomicOps).length > 0) {\n\t\thashInput.atomicOps = input.atomicOps\n\t}\n\treturn hashInput\n}\n\nfunction v2HashInput(op: HashableOperation, timestamp: string): Record<string, unknown> {\n\t// The canonical body (core canonical-body.ts) is what is hashed: the same function\n\t// produced the body the op log and the wire carry, so the hashed content and the\n\t// stored content are one by construction (RT-72, RT-79, RT-80).\n\tconst body = canonicalizeOperationBody(op)\n\tconst atomicOps =\n\t\tbody.atomicOps !== undefined && Object.keys(body.atomicOps).length > 0 ? body.atomicOps : null\n\treturn {\n\t\thashVersion: 2,\n\t\ttype: op.type,\n\t\tcollection: op.collection,\n\t\trecordId: op.recordId,\n\t\tdata: body.data,\n\t\tpreviousData: body.previousData,\n\t\ttimestamp,\n\t\tnodeId: op.nodeId,\n\t\tsequenceNumber: op.sequenceNumber,\n\t\t// A set of parents: their order carries no meaning, so a relay that reorders\n\t\t// them neither breaks verification nor creates a second id for one op.\n\t\tcausalDeps: [...op.causalDeps].sort(),\n\t\tschemaVersion: op.schemaVersion,\n\t\tatomicOps,\n\t}\n}\n\nasync function sha256Hex(canonical: string): Promise<string> {\n\tconst encoded = new TextEncoder().encode(canonical)\n\tconst hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', encoded)\n\treturn bufferToHex(hashBuffer)\n}\n\n/**\n * Deterministic JSON serialization with sorted keys.\n * Ensures identical objects always produce identical strings regardless of property insertion order.\n *\n * @param obj - The value to serialize\n * @returns A deterministic JSON string\n */\nexport function canonicalize(obj: unknown): string {\n\tif (obj === null) {\n\t\treturn 'null'\n\t}\n\n\tif (obj === undefined) {\n\t\treturn 'null'\n\t}\n\n\tif (typeof obj !== 'object') {\n\t\treturn JSON.stringify(obj)\n\t}\n\n\tif (Array.isArray(obj)) {\n\t\tconst items = obj.map((item) => canonicalize(item))\n\t\treturn `[${items.join(',')}]`\n\t}\n\n\tconst keys = Object.keys(obj as Record<string, unknown>).sort()\n\tconst pairs = keys.map((key) => {\n\t\tconst value = (obj as Record<string, unknown>)[key]\n\t\t// Serialize undefined values as null for deterministic output\n\t\treturn `${JSON.stringify(key)}:${canonicalize(value === undefined ? null : value)}`\n\t})\n\treturn `{${pairs.join(',')}}`\n}\n\nfunction bufferToHex(buffer: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(buffer)\n\treturn Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n","import type { KoraEventByType, KoraEventEmitter, KoraEventListener, KoraEventType } from './events'\n\ntype AnyListener = (event: never) => void\n\n/**\n * Concrete implementation of KoraEventEmitter.\n * Simple, synchronous event emitter for internal use across @kora packages.\n *\n * @example\n * ```typescript\n * const emitter = new SimpleEventEmitter()\n * const unsub = emitter.on('operation:created', (event) => {\n * console.log(event.operation.id)\n * })\n * emitter.emit({ type: 'operation:created', operation: someOp })\n * unsub() // unsubscribe\n * ```\n */\nexport class SimpleEventEmitter implements KoraEventEmitter {\n\tprivate listeners = new Map<string, Set<AnyListener>>()\n\n\ton<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): () => void {\n\t\tlet set = this.listeners.get(type)\n\t\tif (!set) {\n\t\t\tset = new Set()\n\t\t\tthis.listeners.set(type, set)\n\t\t}\n\t\tset.add(listener as AnyListener)\n\n\t\treturn () => {\n\t\t\tthis.off(type, listener)\n\t\t}\n\t}\n\n\toff<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): void {\n\t\tconst set = this.listeners.get(type)\n\t\tif (set) {\n\t\t\tset.delete(listener as AnyListener)\n\t\t\tif (set.size === 0) {\n\t\t\t\tthis.listeners.delete(type)\n\t\t\t}\n\t\t}\n\t}\n\n\temit<T extends KoraEventType>(event: KoraEventByType<T>): void {\n\t\tconst set = this.listeners.get(event.type)\n\t\tif (!set) return\n\t\tfor (const listener of set) {\n\t\t\t;(listener as (event: KoraEventByType<T>) => void)(event)\n\t\t}\n\t}\n\n\t/** Remove all listeners for all event types. */\n\tclear(): void {\n\t\tthis.listeners.clear()\n\t}\n\n\t/** Get the number of listeners for a specific event type. */\n\tlistenerCount(type: KoraEventType): number {\n\t\treturn this.listeners.get(type)?.size ?? 0\n\t}\n}\n","import { HybridLogicalClock } from '../clock/hlc'\nimport { OperationError } from '../errors/errors'\nimport type { Operation } from '../types'\n\n/**\n * Topological sort of operations based on their causal dependency DAG.\n * Uses Kahn's algorithm with a binary heap for O(V log V + E) performance.\n * Deterministic tie-breaking via HLC timestamp ensures identical output\n * regardless of input order.\n *\n * @param operations - The operations to sort\n * @returns Operations in causal order (dependencies before dependents)\n * @throws {OperationError} If a cycle is detected in the dependency graph\n */\nexport function topologicalSort(operations: Operation[]): Operation[] {\n\tif (operations.length <= 1) return [...operations]\n\n\t// Build adjacency list and in-degree map\n\tconst opMap = new Map<string, Operation>()\n\tfor (const op of operations) {\n\t\topMap.set(op.id, op)\n\t}\n\n\t// Only count edges where both ends are in the operation set\n\tconst inDegree = new Map<string, number>()\n\tconst dependents = new Map<string, string[]>()\n\n\tfor (const op of operations) {\n\t\tif (!inDegree.has(op.id)) {\n\t\t\tinDegree.set(op.id, 0)\n\t\t}\n\t\tif (!dependents.has(op.id)) {\n\t\t\tdependents.set(op.id, [])\n\t\t}\n\n\t\tfor (const depId of op.causalDeps) {\n\t\t\tif (opMap.has(depId)) {\n\t\t\t\t// depId -> op.id edge (depId must come before op.id)\n\t\t\t\tinDegree.set(op.id, (inDegree.get(op.id) ?? 0) + 1)\n\t\t\t\tconst deps = dependents.get(depId)\n\t\t\t\tif (deps) {\n\t\t\t\t\tdeps.push(op.id)\n\t\t\t\t} else {\n\t\t\t\t\tdependents.set(depId, [op.id])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Initialize min-heap with nodes that have no in-set dependencies\n\tconst heap = new MinHeap(compareByTimestamp)\n\tfor (const op of operations) {\n\t\tif ((inDegree.get(op.id) ?? 0) === 0) {\n\t\t\theap.push(op)\n\t\t}\n\t}\n\n\tconst result: Operation[] = []\n\n\twhile (heap.size > 0) {\n\t\t// Extract the earliest operation (deterministic tie-breaking by HLC)\n\t\tconst current = heap.pop()\n\t\tresult.push(current)\n\n\t\tconst deps = dependents.get(current.id) ?? []\n\n\t\tfor (const depId of deps) {\n\t\t\tconst deg = (inDegree.get(depId) ?? 0) - 1\n\t\t\tinDegree.set(depId, deg)\n\t\t\tif (deg === 0) {\n\t\t\t\tconst op = opMap.get(depId)\n\t\t\t\tif (op) heap.push(op)\n\t\t\t}\n\t\t}\n\t}\n\n\tif (result.length !== operations.length) {\n\t\tthrow new OperationError(\n\t\t\t`Cycle detected in operation dependency graph. Sorted ${result.length} of ${operations.length} operations.`,\n\t\t\t{\n\t\t\t\tsortedCount: result.length,\n\t\t\t\ttotalCount: operations.length,\n\t\t\t},\n\t\t)\n\t}\n\n\treturn result\n}\n\nfunction compareByTimestamp(a: Operation, b: Operation): number {\n\treturn HybridLogicalClock.compare(a.timestamp, b.timestamp)\n}\n\n/**\n * Binary min-heap for efficient priority queue operations.\n * push: O(log n), pop: O(log n) — replaces the O(n) sorted array approach.\n */\nclass MinHeap {\n\tprivate readonly data: Operation[] = []\n\tprivate readonly cmp: (a: Operation, b: Operation) => number\n\n\tconstructor(comparator: (a: Operation, b: Operation) => number) {\n\t\tthis.cmp = comparator\n\t}\n\n\tget size(): number {\n\t\treturn this.data.length\n\t}\n\n\tpush(item: Operation): void {\n\t\tthis.data.push(item)\n\t\tthis.bubbleUp(this.data.length - 1)\n\t}\n\n\tpop(): Operation {\n\t\tconst top = this.data[0] as Operation\n\t\tconst last = this.data.pop() as Operation\n\t\tif (this.data.length > 0) {\n\t\t\tthis.data[0] = last\n\t\t\tthis.sinkDown(0)\n\t\t}\n\t\treturn top\n\t}\n\n\tprivate bubbleUp(index: number): void {\n\t\tlet current = index\n\t\twhile (current > 0) {\n\t\t\tconst parentIndex = (current - 1) >> 1\n\t\t\tif (this.cmp(this.data[current] as Operation, this.data[parentIndex] as Operation) >= 0) break\n\t\t\tthis.swap(current, parentIndex)\n\t\t\tcurrent = parentIndex\n\t\t}\n\t}\n\n\tprivate sinkDown(index: number): void {\n\t\tconst length = this.data.length\n\t\tlet current = index\n\t\twhile (true) {\n\t\t\tlet smallest = current\n\t\t\tconst left = 2 * current + 1\n\t\t\tconst right = 2 * current + 2\n\n\t\t\tif (\n\t\t\t\tleft < length &&\n\t\t\t\tthis.cmp(this.data[left] as Operation, this.data[smallest] as Operation) < 0\n\t\t\t) {\n\t\t\t\tsmallest = left\n\t\t\t}\n\t\t\tif (\n\t\t\t\tright < length &&\n\t\t\t\tthis.cmp(this.data[right] as Operation, this.data[smallest] as Operation) < 0\n\t\t\t) {\n\t\t\t\tsmallest = right\n\t\t\t}\n\n\t\t\tif (smallest === current) break\n\t\t\tthis.swap(current, smallest)\n\t\t\tcurrent = smallest\n\t\t}\n\t}\n\n\tprivate swap(i: number, j: number): void {\n\t\tconst tmp = this.data[i] as Operation\n\t\tthis.data[i] = this.data[j] as Operation\n\t\tthis.data[j] = tmp\n\t}\n}\n","/**\n * Quote a SQL identifier (table or column name) for safe interpolation into\n * generated SQL.\n *\n * Uses standard double-quote quoting, which both SQLite and PostgreSQL honor,\n * and doubles any embedded double-quote per the SQL standard. Quoting is what\n * lets a valid JavaScript identifier — camelCase (`formResponses`), PascalCase\n * (`UserProfiles`), or a word that happens to be a SQL keyword (`order`,\n * `select`) — round-trip through generated DDL and queries without ever\n * producing invalid or ambiguous SQL.\n *\n * Schema validation (see `validateCollectionName` / `validateFieldName`)\n * already restricts identifiers to `[A-Za-z][A-Za-z0-9_]*`, so the escape here\n * never actually fires in practice; it is defense in depth so this helper is\n * correct for any input rather than only for pre-validated names.\n *\n * Consistency is the contract: every path that emits an identifier for a\n * user-defined collection or field MUST go through this helper, because a\n * quoted `CREATE TABLE \"order\"` paired with an unquoted `SELECT ... FROM order`\n * would reintroduce the exact keyword-collision bug this guards against.\n *\n * @param name - The raw identifier (collection name, field/column name).\n * @returns The identifier wrapped in double quotes, ready to splice into SQL.\n *\n * @example\n * ```typescript\n * quoteIdent('formResponses') // => '\"formResponses\"'\n * quoteIdent('order') // => '\"order\"'\n * ```\n */\nexport function quoteIdent(name: string): string {\n\treturn `\"${name.replace(/\"/g, '\"\"')}\"`\n}\n","import type {\n\tCollectionDefinition,\n\tFieldDescriptor,\n\tRelationDefinition,\n\tSchemaDefinition,\n} from '../types'\nimport { quoteIdent } from './quote-ident'\n\n/**\n * Generate CREATE TABLE and CREATE INDEX SQL for a single collection.\n *\n * @param collectionName - The collection name\n * @param collection - The collection definition\n * @param relations - Optional relations for FK references\n * @returns An array of SQL statements (CREATE TABLE + CREATE INDEX)\n */\nexport function generateSQL(\n\tcollectionName: string,\n\tcollection: CollectionDefinition,\n\trelations?: Record<string, RelationDefinition>,\n): string[] {\n\tconst statements: string[] = []\n\tconst columns: string[] = ['id TEXT PRIMARY KEY NOT NULL']\n\n\t// Track which fields already have indexes\n\tconst indexedFields = new Set(collection.indexes)\n\n\t// Collect FK fields for auto-indexing\n\tconst fkFields: string[] = []\n\n\tfor (const [fieldName, descriptor] of Object.entries(collection.fields)) {\n\t\tlet colDef = columnDefinition(fieldName, descriptor)\n\n\t\t// Add FK reference if a relation exists for this field\n\t\tif (relations) {\n\t\t\tfor (const rel of Object.values(relations)) {\n\t\t\t\tif (rel.from === collectionName && rel.field === fieldName) {\n\t\t\t\t\tcolDef += ` REFERENCES ${quoteIdent(rel.to)}(id)`\n\t\t\t\t\tfkFields.push(fieldName)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcolumns.push(colDef)\n\t}\n\n\t// Auto metadata columns\n\tcolumns.push('_created_at INTEGER NOT NULL')\n\tcolumns.push('_updated_at INTEGER NOT NULL')\n\tcolumns.push(\"_version TEXT NOT NULL DEFAULT ''\")\n\t// Per-field last-writer HLC versions (JSON: { field -> serialized HLC }).\n\t// Enables deterministic, order-independent field-level LWW so concurrent\n\t// edits to different fields of one record never clobber each other and\n\t// same-field conflicts converge to the max-timestamp writer on every node.\n\tcolumns.push(\"_field_versions TEXT NOT NULL DEFAULT '{}'\")\n\tcolumns.push('_deleted INTEGER NOT NULL DEFAULT 0')\n\n\tstatements.push(\n\t\t`CREATE TABLE IF NOT EXISTS ${quoteIdent(collectionName)} (\\n ${columns.join(',\\n ')}\\n)`,\n\t)\n\n\t// Add ALTER TABLE statements so new columns are added to existing tables.\n\t// These are tagged with --kora:safe-alter so the runtime can ignore \"duplicate column\" errors.\n\tfor (const [fieldName, descriptor] of Object.entries(collection.fields)) {\n\t\tconst colDef = columnDefinition(fieldName, descriptor)\n\t\tstatements.push(\n\t\t\t`--kora:safe-alter\\nALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN ${colDef}`,\n\t\t)\n\t}\n\tstatements.push(\n\t\t`--kora:safe-alter\\nALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`,\n\t)\n\tstatements.push(\n\t\t`--kora:safe-alter\\nALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '{}'`,\n\t)\n\n\t// Create indexes. Names come from collectionIndexName, which cannot collide\n\t// across collections (STORE-15); table and column are quoted so mixed-case /\n\t// keyword names resolve correctly.\n\tfor (const indexField of collection.indexes) {\n\t\tstatements.push(\n\t\t\t`CREATE INDEX IF NOT EXISTS ${quoteIdent(collectionIndexName(collectionName, indexField))} ON ${quoteIdent(collectionName)} (${quoteIdent(indexField)})`,\n\t\t)\n\t}\n\n\t// Auto-create indexes on FK columns not already indexed\n\tfor (const fkField of fkFields) {\n\t\tif (!indexedFields.has(fkField)) {\n\t\t\tstatements.push(\n\t\t\t\t`CREATE INDEX IF NOT EXISTS ${quoteIdent(collectionIndexName(collectionName, fkField))} ON ${quoteIdent(collectionName)} (${quoteIdent(fkField)})`,\n\t\t\t)\n\t\t}\n\t}\n\n\t// Per-collection operations log table. The table name carries the collection\n\t// name, so it is quoted too (a collection named `order` would otherwise make\n\t// `_kora_ops_order` collide with reserved-word handling on some engines).\n\tstatements.push(\n\t\t`CREATE TABLE IF NOT EXISTS ${quoteIdent(`_kora_ops_${collectionName}`)} (\n id TEXT PRIMARY KEY NOT NULL,\n node_id TEXT NOT NULL,\n type TEXT NOT NULL,\n record_id TEXT NOT NULL,\n data TEXT,\n previous_data TEXT,\n timestamp TEXT NOT NULL,\n sequence_number INTEGER NOT NULL,\n causal_deps TEXT NOT NULL,\n schema_version INTEGER NOT NULL\n)`,\n\t)\n\t// Record-scoped lookups run on every remote apply (latest-op-for-record,\n\t// orphaned-op fold on out-of-order inserts) — index them.\n\tstatements.push(\n\t\t`CREATE INDEX IF NOT EXISTS idx_kora_ops_${collectionName}_record_id ON ${quoteIdent(`_kora_ops_${collectionName}`)} (record_id)`,\n\t)\n\n\treturn statements\n}\n\n/**\n * Name of the index on `field` of `collection`.\n *\n * The collection name's length is part of the name, so two (collection, field)\n * pairs can never produce the same index name. The previous scheme,\n * `idx_<collection>_<field>`, gave `a_b`.`c` and `a`.`b_c` the same name, and the\n * second `CREATE INDEX IF NOT EXISTS` silently did nothing (STORE-15).\n *\n * @param collection - Collection (table) name\n * @param field - Indexed field (column) name\n * @returns A collision-free index name\n */\nexport function collectionIndexName(collection: string, field: string): string {\n\treturn `idx_${collection.length}_${collection}_${field}`\n}\n\n/**\n * Name the pre-beta.13 scheme gave the index on `field` of `collection`. Only\n * used to find and drop those indexes once their replacements exist.\n */\nexport function legacyCollectionIndexName(collection: string, field: string): string {\n\treturn `idx_${collection}_${field}`\n}\n\n/**\n * Prefix of the `generateFullDDL` statement `--kora:schema-ceiling <version>` (RT-109).\n * It is a SQL comment, so executing it does nothing; Kora's storage adapters read the\n * database's stored schema version there and refuse to run the rest of the DDL against a\n * database a newer build already migrated.\n */\nexport const SCHEMA_CEILING_DIRECTIVE = '--kora:schema-ceiling'\n\n/**\n * Generate the full DDL for all collections plus metadata tables.\n *\n * @param schema - The complete schema definition\n * @returns An array of all SQL statements needed to initialize the database\n */\nexport function generateFullDDL(schema: SchemaDefinition): string[] {\n\tconst statements: string[] = []\n\n\t// Metadata table\n\tstatements.push(\n\t\t'CREATE TABLE IF NOT EXISTS _kora_meta (\\n' +\n\t\t\t' key TEXT PRIMARY KEY NOT NULL,\\n' +\n\t\t\t' value TEXT NOT NULL\\n' +\n\t\t\t')',\n\t)\n\t// A SQL comment (a no-op for any executor): Kora's storage adapters refuse here, before\n\t// any other DDL touches it, a database whose stored schema version is newer than this\n\t// schema's (a newer build already migrated it, RT-109).\n\tstatements.push(`${SCHEMA_CEILING_DIRECTIVE} ${schema.version}`)\n\n\t// Version vector table\n\tstatements.push(\n\t\t'CREATE TABLE IF NOT EXISTS _kora_version_vector (\\n' +\n\t\t\t' node_id TEXT PRIMARY KEY NOT NULL,\\n' +\n\t\t\t' sequence_number INTEGER NOT NULL\\n' +\n\t\t\t')',\n\t)\n\n\t// Sequence counters table (offline-safe sequences)\n\tstatements.push(\n\t\t'CREATE TABLE IF NOT EXISTS _kora_sequences (\\n' +\n\t\t\t' name TEXT NOT NULL,\\n' +\n\t\t\t\" scope TEXT NOT NULL DEFAULT '',\\n\" +\n\t\t\t' node_id TEXT NOT NULL,\\n' +\n\t\t\t' counter INTEGER NOT NULL DEFAULT 0,\\n' +\n\t\t\t' PRIMARY KEY (name, scope, node_id)\\n' +\n\t\t\t')',\n\t)\n\n\t// Durable outbound sync queue (survives page refresh)\n\tstatements.push(\n\t\t'CREATE TABLE IF NOT EXISTS _kora_sync_queue (\\n' +\n\t\t\t' id TEXT PRIMARY KEY NOT NULL,\\n' +\n\t\t\t' payload TEXT NOT NULL\\n' +\n\t\t\t')',\n\t)\n\n\t// Durable record of operations the server rejected (survives page refresh).\n\t// An op lands here after being diverted out of the outbound queue, so it is\n\t// never retried yet stays explainable to the app until reconciled.\n\tstatements.push(\n\t\t'CREATE TABLE IF NOT EXISTS _kora_sync_rejected (\\n' +\n\t\t\t' operation_id TEXT PRIMARY KEY NOT NULL,\\n' +\n\t\t\t' collection TEXT NOT NULL,\\n' +\n\t\t\t' record_id TEXT NOT NULL,\\n' +\n\t\t\t' code TEXT NOT NULL,\\n' +\n\t\t\t' message TEXT NOT NULL,\\n' +\n\t\t\t' retriable INTEGER NOT NULL,\\n' +\n\t\t\t' rejected_at INTEGER NOT NULL\\n' +\n\t\t\t')',\n\t)\n\n\t// Durable merge / constraint audit trail (enterprise audit export)\n\tstatements.push(\n\t\t'CREATE TABLE IF NOT EXISTS _kora_audit_traces (\\n' +\n\t\t\t' id TEXT PRIMARY KEY NOT NULL,\\n' +\n\t\t\t' recorded_at INTEGER NOT NULL,\\n' +\n\t\t\t' event_type TEXT NOT NULL,\\n' +\n\t\t\t' collection TEXT NOT NULL,\\n' +\n\t\t\t' record_id TEXT NOT NULL,\\n' +\n\t\t\t' field TEXT NOT NULL,\\n' +\n\t\t\t' strategy TEXT NOT NULL,\\n' +\n\t\t\t' tier INTEGER NOT NULL,\\n' +\n\t\t\t' constraint_name TEXT,\\n' +\n\t\t\t' trace_json TEXT NOT NULL\\n' +\n\t\t\t')',\n\t)\n\tstatements.push(\n\t\t'CREATE INDEX IF NOT EXISTS idx_kora_audit_traces_recorded_at ON _kora_audit_traces (recorded_at)',\n\t)\n\tstatements.push(\n\t\t'CREATE INDEX IF NOT EXISTS idx_kora_audit_traces_collection ON _kora_audit_traces (collection)',\n\t)\n\n\tfor (const [name, collection] of Object.entries(schema.collections)) {\n\t\tstatements.push(...generateSQL(name, collection, schema.relations))\n\t}\n\n\treturn statements\n}\n\n/**\n * A schema field's column: its type and default only. Requiredness and enum membership\n * are value-domain rules, enforced by validation at write time on every replica; they\n * are never restated as `NOT NULL` / `CHECK` constraints, which a table cannot evolve\n * when the schema does (RT-101: an added enum value, a field made optional).\n */\nfunction columnDefinition(fieldName: string, descriptor: FieldDescriptor): string {\n\tconst sqlType = mapFieldType(descriptor)\n\tconst parts = [quoteIdent(fieldName), sqlType]\n\n\tif (descriptor.defaultValue !== undefined) {\n\t\tparts.push(`DEFAULT ${sqlDefaultLiteral(descriptor.defaultValue)}`)\n\t}\n\n\treturn parts.join(' ')\n}\n\n/**\n * Render a string as a SQL string literal, doubling embedded single quotes\n * (SEC-9b). Valid in SQLite and in Postgres with standard_conforming_strings\n * (the default since 9.1), where a backslash is an ordinary character.\n *\n * @param value - The string to quote\n * @returns The quoted literal, e.g. `'don''t know'`\n */\nexport function sqlStringLiteral(value: string): string {\n\treturn `'${value.replaceAll(\"'\", \"''\")}'`\n}\n\n/**\n * Render a schema default value as a SQL literal for a `DEFAULT` clause. Strings\n * and JSON-serialized arrays/objects are quoted with `sqlStringLiteral`, so a\n * default containing a quote yields valid DDL instead of breaking it (SEC-9b).\n *\n * @param value - The field's default value\n * @returns A SQL literal\n */\nexport function sqlDefaultLiteral(value: unknown): string {\n\tif (value === null) return 'NULL'\n\tif (typeof value === 'string') return sqlStringLiteral(value)\n\tif (typeof value === 'number') {\n\t\t// Non-finite numbers have no SQL literal; NULL is the only safe default.\n\t\treturn Number.isFinite(value) ? String(value) : 'NULL'\n\t}\n\tif (typeof value === 'boolean') return value ? '1' : '0'\n\t// Arrays and objects are stored as JSON strings\n\treturn sqlStringLiteral(JSON.stringify(value) ?? 'null')\n}\n\n/**\n * Render the `CHECK (col IN (...))` constraint of an enum field, each value\n * quoted with `sqlStringLiteral` (SEC-9b). No longer emitted by Kora's DDL (RT-101:\n * enum membership is enforced by validation only); kept for tooling and tests.\n *\n * @param fieldName - The enum column\n * @param values - The enum's allowed values\n * @returns The CHECK clause\n */\nexport function enumCheckConstraint(fieldName: string, values: readonly string[]): string {\n\treturn `CHECK (${quoteIdent(fieldName)} IN (${values.map(sqlStringLiteral).join(', ')}))`\n}\n\nfunction mapFieldType(descriptor: FieldDescriptor): string {\n\tswitch (descriptor.kind) {\n\t\tcase 'string':\n\t\t\treturn 'TEXT'\n\t\tcase 'number':\n\t\t\treturn 'REAL'\n\t\tcase 'boolean':\n\t\t\treturn 'INTEGER'\n\t\tcase 'enum':\n\t\t\treturn 'TEXT'\n\t\tcase 'timestamp':\n\t\t\treturn 'INTEGER'\n\t\tcase 'array':\n\t\t\treturn 'TEXT' // JSON-serialized\n\t\tcase 'object':\n\t\t\treturn 'TEXT' // JSON-serialized structured object\n\t\tcase 'json':\n\t\t\treturn 'TEXT' // JSON-serialized dynamic-key value\n\t\tcase 'blob':\n\t\t\treturn 'TEXT' // JSON-serialized content-addressed BlobRef (bytes stored out of band)\n\t\tcase 'secret':\n\t\t\treturn 'TEXT'\n\t\tcase 'richtext':\n\t\t\treturn 'BLOB' // Yjs state\n\t}\n}\n","import { quoteIdent } from './quote-ident'\n\n/**\n * Value-domain rules live in validation only (RT-101).\n *\n * Kora's generated DDL used to restate two schema rules as storage constraints: an enum\n * field's `CHECK (col IN (...))` and a required field's `NOT NULL`. The value domain\n * already enforces both at write time on every replica, and storage constraints cannot\n * evolve with the schema (SQLite cannot alter a column constraint): an enum value added\n * by a schema upgrade was valid everywhere but refused by every existing table. The DDL\n * no longer emits them, and existing tables are relaxed once, by these helpers.\n *\n * Kora's own bookkeeping columns (`id` and every `_`-prefixed column) keep their\n * constraints: they are written by Kora only and never evolve with the app schema.\n */\n\n/** A row of `PRAGMA table_info(<table>)`. */\nexport interface SqliteColumnInfo {\n\tcid: number\n\tname: string\n\ttype: string\n\tnotnull: number\n\tdflt_value: string | null\n\tpk: number\n}\n\n/** A row of `PRAGMA foreign_key_list(<table>)`. */\nexport interface SqliteForeignKeyInfo {\n\tid: number\n\tseq: number\n\ttable: string\n\tfrom: string\n\tto: string | null\n\ton_update: string\n\ton_delete: string\n\tmatch?: string\n}\n\n/** What a SQLite table looks like, read from its catalog. */\nexport interface SqliteTableCatalog {\n\t/** Table name */\n\ttable: string\n\t/** The table's `CREATE TABLE` statement (`sqlite_master.sql`) */\n\tsql: string\n\tcolumns: readonly SqliteColumnInfo[]\n\tforeignKeys: readonly SqliteForeignKeyInfo[]\n\t/** Column lists of the table's `UNIQUE` constraints (`index_list` origin `u`) */\n\tuniqueConstraints: readonly (readonly string[])[]\n\t/** `CREATE INDEX` / `CREATE TRIGGER` statements on the table (`sqlite_master.sql`) */\n\tdependents: readonly string[]\n}\n\n/** Columns Kora writes itself; their constraints are kept. */\nexport function isKoraInternalColumn(name: string): boolean {\n\treturn name === 'id' || name.startsWith('_')\n}\n\n/** One `CHECK` constraint of a SQLite `CREATE TABLE` statement. */\nexport interface SqliteCheckConstraint {\n\t/** The constraint name (`CONSTRAINT <name> CHECK ...`), unquoted, or null */\n\tname: string | null\n\t/** The checked expression, verbatim (the text between the parentheses) */\n\texpression: string\n\t/** The column whose definition carries the check (a column constraint), or null */\n\tcolumn: string | null\n}\n\ninterface SqlToken {\n\ttext: string\n\tstart: number\n\tend: number\n}\n\n/** Tokens of a SQLite statement: literals and quoted identifiers whole, comments skipped. */\nfunction scanSqlTokens(sql: string): SqlToken[] {\n\tconst tokens: SqlToken[] = []\n\tconst pattern =\n\t\t/\\s+|--[^\\n]*|\\/\\*[\\s\\S]*?(?:\\*\\/|$)|'(?:[^']|'')*'?|\"(?:[^\"]|\"\")*\"?|`(?:[^`]|``)*`?|\\[[^\\]]*\\]?|[A-Za-z_][A-Za-z0-9_$]*|[0-9]+(?:\\.[0-9]+)?|[(),]|[^\\s(),'\"`[A-Za-z0-9_]+/g\n\tfor (let match = pattern.exec(sql); match !== null; match = pattern.exec(sql)) {\n\t\tconst text = match[0]\n\t\tif (/^\\s/.test(text) || text.startsWith('--') || text.startsWith('/*')) continue\n\t\ttokens.push({ text, start: match.index, end: match.index + text.length })\n\t}\n\treturn tokens\n}\n\nfunction unquoteSqliteName(token: string | undefined): string | null {\n\tif (token === undefined) return null\n\tif (/^\"(?:[^\"]|\"\")*\"$/.test(token)) return token.slice(1, -1).replaceAll('\"\"', '\"')\n\tif (/^`(?:[^`]|``)*`$/.test(token)) return token.slice(1, -1).replaceAll('``', '`')\n\tif (/^\\[[^\\]]*\\]$/.test(token)) return token.slice(1, -1)\n\tif (/^'(?:[^']|'')*'$/.test(token)) return token.slice(1, -1).replaceAll(\"''\", \"'\")\n\tif (/^[A-Za-z_][A-Za-z0-9_$]*$/.test(token)) return token\n\treturn null\n}\n\nconst TABLE_CONSTRAINT_KEYWORDS: ReadonlySet<string> = new Set([\n\t'CONSTRAINT',\n\t'PRIMARY',\n\t'UNIQUE',\n\t'CHECK',\n\t'FOREIGN',\n])\n\n/**\n * Every `CHECK` constraint of a SQLite `CREATE TABLE` statement, column and table\n * constraints alike, with the expression verbatim (RT-111). The catalog pragmas do not\n * report checks, so a table rebuild must carry them over from the statement itself.\n *\n * @param createTableSql - The table's `CREATE TABLE` statement (`sqlite_master.sql`)\n * @returns The checks in statement order\n */\nexport function parseSqliteCheckConstraints(createTableSql: string): SqliteCheckConstraint[] {\n\tconst tokens = scanSqlTokens(createTableSql)\n\tconst open = tokens.findIndex((token) => token.text === '(')\n\tif (open < 0) return []\n\t// Split the column/constraint list into its top-level definitions.\n\tconst definitions: SqlToken[][] = []\n\tlet current: SqlToken[] = []\n\tlet depth = 0\n\tfor (let i = open + 1; i < tokens.length; i++) {\n\t\tconst token = tokens[i] as SqlToken\n\t\tif (token.text === '(') depth++\n\t\tif (token.text === ')') {\n\t\t\tif (depth === 0) break\n\t\t\tdepth--\n\t\t}\n\t\tif (token.text === ',' && depth === 0) {\n\t\t\tdefinitions.push(current)\n\t\t\tcurrent = []\n\t\t\tcontinue\n\t\t}\n\t\tcurrent.push(token)\n\t}\n\tif (current.length > 0) definitions.push(current)\n\n\tconst checks: SqliteCheckConstraint[] = []\n\tfor (const definition of definitions) {\n\t\tconst first = definition[0]\n\t\tif (first === undefined) continue\n\t\tconst column = TABLE_CONSTRAINT_KEYWORDS.has(first.text.toUpperCase())\n\t\t\t? null\n\t\t\t: unquoteSqliteName(first.text)\n\t\tlet level = 0\n\t\tfor (let i = 0; i < definition.length; i++) {\n\t\t\tconst token = definition[i] as SqlToken\n\t\t\tif (token.text === '(') level++\n\t\t\telse if (token.text === ')') level--\n\t\t\tif (level !== 0 || token.text.toUpperCase() !== 'CHECK') continue\n\t\t\tconst opening = definition[i + 1]\n\t\t\tif (opening?.text !== '(') continue\n\t\t\tlet inner = 0\n\t\t\tlet closing: SqlToken | undefined\n\t\t\tfor (let j = i + 1; j < definition.length; j++) {\n\t\t\t\tconst candidate = definition[j] as SqlToken\n\t\t\t\tif (candidate.text === '(') inner++\n\t\t\t\tif (candidate.text === ')' && --inner === 0) {\n\t\t\t\t\tclosing = candidate\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (closing === undefined) continue\n\t\t\tconst named =\n\t\t\t\ti >= 2 && (definition[i - 2] as SqlToken).text.toUpperCase() === 'CONSTRAINT'\n\t\t\t\t\t? unquoteSqliteName((definition[i - 1] as SqlToken).text)\n\t\t\t\t\t: null\n\t\t\tchecks.push({\n\t\t\t\tname: named,\n\t\t\t\texpression: createTableSql.slice(opening.end, closing.start).trim(),\n\t\t\t\tcolumn,\n\t\t\t})\n\t\t}\n\t}\n\treturn checks\n}\n\n/**\n * Whether a check is one of Kora's own value-domain checks (beta.12 and earlier DDL):\n * an enum check (see {@link parseEnumCheckDefinition}) on one column that is not Kora's\n * own and, when `enumColumns` is given, is an enum field of the schema. A check of any\n * other shape, or on another column, was added by hand and is kept (RT-111).\n *\n * @param check - A check of the table\n * @param enumColumns - The table's enum fields (undefined: any enum-shaped check counts)\n */\nexport function isKoraSqliteEnumCheck(\n\tcheck: SqliteCheckConstraint,\n\tenumColumns?: readonly string[],\n): boolean {\n\tconst shape = parseEnumCheckDefinition(`CHECK (${check.expression})`)\n\tif (shape === null || isKoraInternalColumn(shape.column)) return false\n\tif (check.column !== null && check.column !== shape.column) return false\n\treturn enumColumns === undefined || enumColumns.includes(shape.column)\n}\n\n/**\n * Whether a table still carries a value-domain constraint of Kora's: an enum `CHECK` on\n * an enum field (see {@link isKoraSqliteEnumCheck}), or `NOT NULL` on a column that is not\n * Kora's own. Checks added by hand never make a table need relaxation.\n *\n * @param catalog - The table as read from SQLite's catalog\n * @param enumColumns - The table's enum fields (undefined: any enum-shaped check counts)\n */\nexport function sqliteTableNeedsRelaxation(\n\tcatalog: SqliteTableCatalog,\n\tenumColumns?: readonly string[],\n): boolean {\n\tif (\n\t\tparseSqliteCheckConstraints(catalog.sql).some((check) =>\n\t\t\tisKoraSqliteEnumCheck(check, enumColumns),\n\t\t)\n\t) {\n\t\treturn true\n\t}\n\treturn catalog.columns.some(\n\t\t(column) => column.notnull === 1 && !isKoraInternalColumn(column.name),\n\t)\n}\n\n/**\n * Whether a check names any of `columns` (its own column, or an identifier in its\n * expression). A rebuild that drops a column cannot keep a check that reads it.\n *\n * @param check - A check of the table\n * @param columns - Column names\n */\nexport function sqliteCheckReferencesColumn(\n\tcheck: SqliteCheckConstraint,\n\tcolumns: ReadonlySet<string>,\n): boolean {\n\tif (check.column !== null && columns.has(check.column)) return true\n\treturn scanSqlTokens(check.expression).some((token) => {\n\t\tif (token.text.startsWith(\"'\")) return false\n\t\tconst name = unquoteSqliteName(token.text)\n\t\treturn name !== null && columns.has(name)\n\t})\n}\n\n/**\n * The table constraint that re-creates a kept check in a rebuilt table: a column check\n * becomes a table check (SQLite evaluates both the same way, per row).\n *\n * @param check - The check to keep\n */\nexport function sqliteCheckConstraintDefinition(check: SqliteCheckConstraint): string {\n\tconst name = check.name === null ? '' : `CONSTRAINT ${quoteIdent(check.name)} `\n\treturn `${name}CHECK (${check.expression})`\n}\n\n/**\n * Statements that rebuild a SQLite table without value-domain constraints (SQLite's\n * documented \"make other kinds of table schema changes\" procedure), keeping every row,\n * column (types and defaults), primary key, foreign key, `UNIQUE` constraint, index and\n * trigger, and every `CHECK` that is not one of Kora's enum checks (RT-111). Run them in\n * ONE transaction, with foreign key enforcement off when the database enforces foreign\n * keys (the drop of a referenced table would otherwise fail or fire its actions). Returns\n * an empty list when the table needs nothing, so running it again is a no-op\n * (idempotent); a rebuild interrupted mid-way rolls back with its transaction and runs\n * again at the next open (resumable).\n *\n * @param catalog - The table as read from SQLite's catalog\n * @param enumColumns - The table's enum fields, which decide which enum-shaped checks are\n * Kora's (undefined: any enum-shaped check on a non-internal column)\n * @returns The rebuild statements, or `[]`\n */\nexport function sqliteConstraintRelaxationStatements(\n\tcatalog: SqliteTableCatalog,\n\tenumColumns?: readonly string[],\n): string[] {\n\tif (!sqliteTableNeedsRelaxation(catalog, enumColumns)) return []\n\tconst table = catalog.table\n\tconst temp = `_kora_relax_${table}`\n\tconst ordered = [...catalog.columns].sort((a, b) => a.cid - b.cid)\n\tconst pkColumns = ordered.filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk)\n\n\tconst fkGroups = new Map<number, SqliteForeignKeyInfo[]>()\n\tfor (const fk of catalog.foreignKeys) {\n\t\tconst group = fkGroups.get(fk.id) ?? []\n\t\tgroup.push(fk)\n\t\tfkGroups.set(fk.id, group)\n\t}\n\tconst columnReferences = new Map<string, string>()\n\tconst tableConstraints: string[] = []\n\tfor (const group of fkGroups.values()) {\n\t\tgroup.sort((a, b) => a.seq - b.seq)\n\t\tconst first = group[0]\n\t\tif (!first) continue\n\t\tconst actions = foreignKeyActions(first)\n\t\tconst target = group.every((fk) => fk.to !== null && fk.to !== '')\n\t\t\t? `${quoteIdent(first.table)}(${group.map((fk) => quoteIdent(fk.to ?? '')).join(', ')})`\n\t\t\t: quoteIdent(first.table)\n\t\tif (group.length === 1) {\n\t\t\tcolumnReferences.set(first.from, `REFERENCES ${target}${actions}`)\n\t\t} else {\n\t\t\ttableConstraints.push(\n\t\t\t\t`FOREIGN KEY (${group.map((fk) => quoteIdent(fk.from)).join(', ')}) REFERENCES ${target}${actions}`,\n\t\t\t)\n\t\t}\n\t}\n\tif (pkColumns.length > 1) {\n\t\ttableConstraints.unshift(`PRIMARY KEY (${pkColumns.map((c) => quoteIdent(c.name)).join(', ')})`)\n\t}\n\tfor (const unique of catalog.uniqueConstraints) {\n\t\ttableConstraints.push(`UNIQUE (${unique.map(quoteIdent).join(', ')})`)\n\t}\n\tfor (const check of parseSqliteCheckConstraints(catalog.sql)) {\n\t\tif (!isKoraSqliteEnumCheck(check, enumColumns)) {\n\t\t\ttableConstraints.push(sqliteCheckConstraintDefinition(check))\n\t\t}\n\t}\n\n\tconst definitions = ordered.map((column) => {\n\t\tconst parts = [quoteIdent(column.name)]\n\t\tif (column.type) parts.push(column.type)\n\t\tif (pkColumns.length === 1 && column.pk === 1) parts.push('PRIMARY KEY')\n\t\tif (column.notnull === 1 && isKoraInternalColumn(column.name)) parts.push('NOT NULL')\n\t\tif (column.dflt_value !== null) parts.push(`DEFAULT (${column.dflt_value})`)\n\t\tconst reference = columnReferences.get(column.name)\n\t\tif (reference) parts.push(reference)\n\t\treturn parts.join(' ')\n\t})\n\n\tconst columnList = ordered.map((column) => quoteIdent(column.name)).join(', ')\n\treturn [\n\t\t`DROP TABLE IF EXISTS ${quoteIdent(temp)}`,\n\t\t`CREATE TABLE ${quoteIdent(temp)} (\\n ${[...definitions, ...tableConstraints].join(',\\n ')}\\n)`,\n\t\t`INSERT INTO ${quoteIdent(temp)} (${columnList}) SELECT ${columnList} FROM ${quoteIdent(table)}`,\n\t\t`DROP TABLE ${quoteIdent(table)}`,\n\t\t`ALTER TABLE ${quoteIdent(temp)} RENAME TO ${quoteIdent(table)}`,\n\t\t...catalog.dependents,\n\t]\n}\n\nfunction foreignKeyActions(fk: SqliteForeignKeyInfo): string {\n\tlet out = ''\n\tconst onDelete = fk.on_delete?.toUpperCase()\n\tconst onUpdate = fk.on_update?.toUpperCase()\n\tif (onDelete && onDelete !== 'NO ACTION') out += ` ON DELETE ${onDelete}`\n\tif (onUpdate && onUpdate !== 'NO ACTION') out += ` ON UPDATE ${onUpdate}`\n\treturn out\n}\n\n/** A `CHECK` that restricts one column to a list of string literals (an enum check). */\nexport interface EnumCheckShape {\n\t/** The column, unquoted */\n\tcolumn: string\n\t/** The allowed values, unquoted */\n\tvalues: string[]\n\t/** The check also allows NULL (`... OR col IS NULL`) */\n\tallowsNull: boolean\n}\n\n/** Keywords that never belong to a cast's type name. */\nconst EXPRESSION_KEYWORDS: ReadonlySet<string> = new Set([\n\t'AND',\n\t'ANY',\n\t'ARRAY',\n\t'IN',\n\t'IS',\n\t'NOT',\n\t'NULL',\n\t'OR',\n\t'VALID',\n])\n\nfunction tokenizeCheck(definition: string): string[] | null {\n\tconst tokens: string[] = []\n\tconst pattern =\n\t\t/\\s+|'(?:[^']|'')*'|\"(?:[^\"]|\"\")+\"|::|[A-Za-z_][A-Za-z0-9_$]*|[0-9]+|<>|!=|<=|>=|[()[\\],=<>]|./gy\n\tfor (let match = pattern.exec(definition); match !== null; match = pattern.exec(definition)) {\n\t\tif (match[0] === '') return null\n\t\tif (!/^\\s+$/.test(match[0])) tokens.push(match[0])\n\t\tif (pattern.lastIndex >= definition.length) break\n\t}\n\treturn tokens\n}\n\nfunction isWord(token: string | undefined): token is string {\n\treturn token !== undefined && /^[A-Za-z_][A-Za-z0-9_$]*$/.test(token)\n}\n\n/** Drop `::type` casts (`::text`, `::character varying`, `::text[]`, `::varchar(20)`). */\nfunction dropCasts(tokens: string[]): string[] {\n\tconst out: string[] = []\n\tlet i = 0\n\twhile (i < tokens.length) {\n\t\tif (tokens[i] !== '::') {\n\t\t\tout.push(tokens[i] as string)\n\t\t\ti++\n\t\t\tcontinue\n\t\t}\n\t\ti++\n\t\tlet words = 0\n\t\twhile (\n\t\t\t(isWord(tokens[i]) && !EXPRESSION_KEYWORDS.has((tokens[i] as string).toUpperCase())) ||\n\t\t\t/^\"/.test(tokens[i] ?? '')\n\t\t) {\n\t\t\ti++\n\t\t\twords++\n\t\t}\n\t\tif (words === 0) return ['::invalid']\n\t\tif (tokens[i] === '(' && /^[0-9]+$/.test(tokens[i + 1] ?? '')) {\n\t\t\tlet j = i + 1\n\t\t\twhile (/^[0-9]+$/.test(tokens[j] ?? '') || tokens[j] === ',') j++\n\t\t\tif (tokens[j] === ')') i = j + 1\n\t\t}\n\t\twhile (tokens[i] === '[' && tokens[i + 1] === ']') i += 2\n\t}\n\treturn out\n}\n\nfunction unquoteIdentifier(token: string): string | null {\n\tif (/^\"(?:[^\"]|\"\")+\"$/.test(token)) return token.slice(1, -1).replaceAll('\"\"', '\"')\n\tif (isWord(token) && !EXPRESSION_KEYWORDS.has(token.toUpperCase())) return token\n\treturn null\n}\n\nfunction unquoteLiteral(token: string | undefined): string | null {\n\tif (token === undefined || !/^'(?:[^']|'')*'$/.test(token)) return null\n\treturn token.slice(1, -1).replaceAll(\"''\", \"'\")\n}\n\n/**\n * Parse a `CHECK` constraint definition as an enum check, structurally: one column\n * compared to string literals, in any form Postgres normalizes `col IN (...)` to\n * (`col = ANY (ARRAY[...])`, the single-value `col = 'x'`, casts such as\n * `(col)::text = ANY ((ARRAY['x'::character varying])::text[])`), a disjunction of\n * equalities (`col = 'a' OR col = 'b'`), the SQLite source form `col IN ('a', 'b')`, an\n * optional `OR col IS NULL`, and a trailing `NOT VALID`. Anything else (another operator,\n * a function call, two columns, `AND`, `NOT`) is not an enum check and returns null.\n *\n * @param definition - The constraint definition (`pg_get_constraintdef`, or SQLite DDL)\n * @returns The column and values, or null when the check has another shape\n */\nexport function parseEnumCheckDefinition(definition: string): EnumCheckShape | null {\n\tconst raw = tokenizeCheck(definition.trim())\n\tif (raw === null || raw[0]?.toUpperCase() !== 'CHECK') return null\n\tlet tokens = dropCasts(raw.slice(1)).filter((token) => token !== '(' && token !== ')')\n\tconst upper = (index: number): string => (tokens[index] ?? '').toUpperCase()\n\tif (upper(tokens.length - 2) === 'NOT' && upper(tokens.length - 1) === 'VALID') {\n\t\ttokens = tokens.slice(0, -2)\n\t}\n\tlet column: string | null = null\n\tconst values: string[] = []\n\tlet allowsNull = false\n\tlet i = 0\n\tconst readLiteral = (): boolean => {\n\t\tconst value = unquoteLiteral(tokens[i])\n\t\tif (value === null) return false\n\t\tvalues.push(value)\n\t\ti++\n\t\treturn true\n\t}\n\tconst readList = (): boolean => {\n\t\tif (!readLiteral()) return false\n\t\twhile (tokens[i] === ',') {\n\t\t\ti++\n\t\t\tif (!readLiteral()) return false\n\t\t}\n\t\treturn true\n\t}\n\tfor (;;) {\n\t\tconst name = unquoteIdentifier(tokens[i] ?? '')\n\t\tif (name === null || (column !== null && name !== column)) return null\n\t\tcolumn = name\n\t\ti++\n\t\tif (tokens[i] === '=' && upper(i + 1) === 'ANY' && upper(i + 2) === 'ARRAY') {\n\t\t\tif (tokens[i + 3] !== '[') return null\n\t\t\ti += 4\n\t\t\tif (!readList() || tokens[i] !== ']') return null\n\t\t\ti++\n\t\t} else if (tokens[i] === '=') {\n\t\t\ti++\n\t\t\tif (!readLiteral()) return null\n\t\t} else if (upper(i) === 'IN') {\n\t\t\ti++\n\t\t\tif (!readList()) return null\n\t\t} else if (upper(i) === 'IS' && upper(i + 1) === 'NULL') {\n\t\t\tallowsNull = true\n\t\t\ti += 2\n\t\t} else {\n\t\t\treturn null\n\t\t}\n\t\tif (i === tokens.length) break\n\t\tif (upper(i) !== 'OR') return null\n\t\ti++\n\t}\n\tif (column === null || values.length === 0) return null\n\treturn { column, values, allowsNull }\n}\n\n/**\n * Whether a Postgres `CHECK` constraint definition (`pg_get_constraintdef`) has the shape\n * of a Kora enum check (see {@link parseEnumCheckDefinition}). Other checks (added by hand)\n * are left alone.\n *\n * @param definition - The constraint definition text\n */\nexport function isPostgresEnumCheckDefinition(definition: string): boolean {\n\treturn parseEnumCheckDefinition(definition) !== null\n}\n\n/** The `col = ANY (ARRAY[...])` form only: what every multi-value beta.12 enum check became. */\nfunction isAnyArrayCheck(definition: string): boolean {\n\treturn /^CHECK\\s*\\(\\(*\\s*(?:\"(?:[^\"]|\"\")+\"|[A-Za-z_][A-Za-z0-9_$]*)(?:\\)?::[A-Za-z ]+)?\\s*=\\s*ANY\\s*\\(/i.test(\n\t\tdefinition.trim(),\n\t)\n}\n\n/** Runs a read-only SQL statement and returns its rows (an adapter's or a transaction's query). */\nexport type SqliteQueryFn = (sql: string) => Promise<Array<Record<string, unknown>>>\n\nfunction sqlText(value: string): string {\n\treturn `'${value.replaceAll(\"'\", \"''\")}'`\n}\n\n/**\n * Read a SQLite table's catalog through `query`, or null when the table does not exist.\n *\n * @param query - Runs a SELECT/PRAGMA and returns rows\n * @param table - The table name\n */\nexport async function readSqliteTableCatalog(\n\tquery: SqliteQueryFn,\n\ttable: string,\n): Promise<SqliteTableCatalog | null> {\n\tconst master = await query(\n\t\t`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ${sqlText(table)}`,\n\t)\n\tconst tableSql = master[0]?.sql\n\tif (typeof tableSql !== 'string') return null\n\tconst columns = await query(\n\t\t`SELECT cid, name, type, \"notnull\" AS \"notnull\", dflt_value, pk FROM pragma_table_info(${sqlText(table)})`,\n\t)\n\tconst foreignKeys = await query(\n\t\t`SELECT id, seq, \"table\" AS \"table\", \"from\" AS \"from\", \"to\" AS \"to\", on_update, on_delete FROM pragma_foreign_key_list(${sqlText(table)})`,\n\t)\n\tconst uniqueConstraints: string[][] = []\n\tconst indexes = await query(\n\t\t`SELECT name FROM pragma_index_list(${sqlText(table)}) WHERE origin = 'u'`,\n\t)\n\tfor (const index of indexes) {\n\t\tconst info = await query(\n\t\t\t`SELECT name FROM pragma_index_info(${sqlText(String(index.name))}) ORDER BY seqno`,\n\t\t)\n\t\tuniqueConstraints.push(info.map((row) => String(row.name)))\n\t}\n\tconst dependents = await query(\n\t\t`SELECT sql FROM sqlite_master WHERE type IN ('index', 'trigger') AND tbl_name = ${sqlText(table)} AND sql IS NOT NULL ORDER BY type, name`,\n\t)\n\treturn {\n\t\ttable,\n\t\tsql: tableSql,\n\t\tcolumns: columns.map((c) => ({\n\t\t\tcid: Number(c.cid),\n\t\t\tname: String(c.name),\n\t\t\ttype: typeof c.type === 'string' ? c.type : '',\n\t\t\tnotnull: Number(c.notnull),\n\t\t\tdflt_value: typeof c.dflt_value === 'string' ? c.dflt_value : null,\n\t\t\tpk: Number(c.pk),\n\t\t})),\n\t\tforeignKeys: foreignKeys.map((fk) => ({\n\t\t\tid: Number(fk.id),\n\t\t\tseq: Number(fk.seq),\n\t\t\ttable: String(fk.table),\n\t\t\tfrom: String(fk.from),\n\t\t\tto: typeof fk.to === 'string' ? fk.to : null,\n\t\t\ton_update: String(fk.on_update ?? 'NO ACTION'),\n\t\t\ton_delete: String(fk.on_delete ?? 'NO ACTION'),\n\t\t})),\n\t\tuniqueConstraints,\n\t\tdependents: dependents.map((row) => String(row.sql)),\n\t}\n}\n\n/**\n * Every statement that relaxes the given SQLite tables (one rebuild per table that still\n * carries a value-domain constraint; tables that do not exist or need nothing are\n * skipped). Run them in one transaction; see {@link sqliteConstraintRelaxationStatements}.\n *\n * @param query - Runs a SELECT/PRAGMA and returns rows (inside the caller's transaction)\n * @param tables - The collection tables to check\n * @param enumColumnsByTable - Each table's enum fields (only enum checks on them are\n * Kora's, RT-111); a table missing here treats any enum-shaped check as Kora's\n * @returns The statements, or `[]` when every table is already relaxed (idempotent)\n */\nexport async function planSqliteConstraintRelaxation(\n\tquery: SqliteQueryFn,\n\ttables: readonly string[],\n\tenumColumnsByTable: Readonly<Record<string, readonly string[]>> = {},\n): Promise<string[]> {\n\tconst statements: string[] = []\n\tfor (const table of tables) {\n\t\tconst catalog = await readSqliteTableCatalog(query, table)\n\t\tif (catalog) {\n\t\t\tstatements.push(...sqliteConstraintRelaxationStatements(catalog, enumColumnsByTable[table]))\n\t\t}\n\t}\n\treturn statements\n}\n\n/**\n * The `ALTER TABLE` statements that relax Postgres collection tables: drop every Kora enum\n * `CHECK` (one column compared to a literal list, see {@link isPostgresEnumCheckDefinition})\n * and every `NOT NULL` on a schema field. Kora's own columns, multi-column checks and\n * checks of any other shape (added by hand) are kept. Reads the catalog of the current\n * schema through `query`, so run it and the statements in one transaction.\n *\n * A check is matched structurally against the schema (RT-108): it is parsed as an enum\n * check ({@link parseEnumCheckDefinition}, every form Postgres normalizes `IN (...)` to,\n * including the single-value `col = 'x'`) and dropped when its one column is an enum field\n * of the table (`enumFieldsByTable`; without it, any schema field), or when it restricts\n * any other non-internal column to two or more literals (the multi-value shape every\n * beta.12 enum check had).\n *\n * @param query - Runs a SELECT and returns rows\n * @param fieldsByTable - Collection table name to its schema field names\n * @param enumFieldsByTable - Collection table name to its enum field names (before and\n * after the change); when a table is missing, every schema field counts as one\n * @returns The statements, or `[]` when nothing is left to relax (idempotent)\n */\nexport async function planPostgresConstraintRelaxation(\n\tquery: SqliteQueryFn,\n\tfieldsByTable: Readonly<Record<string, readonly string[]>>,\n\tenumFieldsByTable: Readonly<Record<string, readonly string[]>> = {},\n): Promise<string[]> {\n\tconst tables = Object.keys(fieldsByTable)\n\tif (tables.length === 0) return []\n\tconst tableList = tables.map(sqlText).join(', ')\n\tconst statements: string[] = []\n\tconst checks = await query(\n\t\t`SELECT rel.relname::text AS table_name, con.conname::text AS name, pg_get_constraintdef(con.oid) AS definition,\n\t\t\t(SELECT array_agg(att.attname::text) FROM pg_attribute att WHERE att.attrelid = con.conrelid AND att.attnum = ANY (con.conkey)) AS columns\n\t\tFROM pg_constraint con\n\t\tJOIN pg_class rel ON rel.oid = con.conrelid\n\t\tJOIN pg_namespace nsp ON nsp.oid = rel.relnamespace\n\t\tWHERE con.contype = 'c' AND nsp.nspname = current_schema() AND rel.relname IN (${tableList})\n\t\tORDER BY rel.relname, con.conname`,\n\t)\n\tfor (const check of checks) {\n\t\tconst columns = Array.isArray(check.columns) ? check.columns.map(String) : []\n\t\tconst column = columns[0]\n\t\tif (columns.length !== 1 || column === undefined || isKoraInternalColumn(column)) continue\n\t\tconst shape = parseEnumCheckDefinition(String(check.definition))\n\t\tif (shape === null || shape.column !== column) continue\n\t\tconst table = String(check.table_name)\n\t\tconst enumColumns = enumFieldsByTable[table] ?? fieldsByTable[table] ?? []\n\t\tif (!enumColumns.includes(column) && !isAnyArrayCheck(String(check.definition))) continue\n\t\tstatements.push(\n\t\t\t`ALTER TABLE ${quoteIdent(String(check.table_name))} DROP CONSTRAINT IF EXISTS ${quoteIdent(String(check.name))}`,\n\t\t)\n\t}\n\tconst notNull = await query(\n\t\t`SELECT rel.relname::text AS table_name, att.attname::text AS column_name\n\t\tFROM pg_attribute att\n\t\tJOIN pg_class rel ON rel.oid = att.attrelid\n\t\tJOIN pg_namespace nsp ON nsp.oid = rel.relnamespace\n\t\tWHERE att.attnotnull AND att.attnum > 0 AND NOT att.attisdropped\n\t\t\tAND rel.relkind = 'r' AND nsp.nspname = current_schema() AND rel.relname IN (${tableList})\n\t\tORDER BY rel.relname, att.attnum`,\n\t)\n\tfor (const row of notNull) {\n\t\tconst table = String(row.table_name)\n\t\tconst column = String(row.column_name)\n\t\tif (isKoraInternalColumn(column)) continue\n\t\tif (!(fieldsByTable[table] ?? []).includes(column)) continue\n\t\tstatements.push(\n\t\t\t`ALTER TABLE ${quoteIdent(table)} ALTER COLUMN ${quoteIdent(column)} DROP NOT NULL`,\n\t\t)\n\t}\n\treturn statements\n}\n","import { HybridLogicalClock } from '../clock/hlc'\nimport type { HLCTimestamp } from '../types'\nimport type { Stamp } from './types'\n\n/**\n * Total order on stamps: authority class (only `merge('server-authoritative')`\n * fields carry one), then serialized HLC (which sorts exactly like\n * `HybridLogicalClock.compare`), then operation id.\n *\n * @returns Negative if a < b, positive if a > b, 0 if equal\n */\nexport function compareStamps(a: Stamp, b: Stamp): number {\n\tconst ca = a.c ?? 0\n\tconst cb = b.c ?? 0\n\tif (ca !== cb) return ca - cb\n\tif (a.t !== b.t) return a.t < b.t ? -1 : 1\n\tif (a.o !== b.o) return a.o < b.o ? -1 : 1\n\treturn 0\n}\n\n/** The later of two stamps (null counts as \"never\"). */\nexport function maxStamp(a: Stamp | null, b: Stamp | null): Stamp | null {\n\tif (a === null) return b\n\tif (b === null) return a\n\treturn compareStamps(a, b) >= 0 ? a : b\n}\n\n/** The earlier of two stamps (null counts as \"never\"). */\nexport function minStamp(a: Stamp | null, b: Stamp | null): Stamp | null {\n\tif (a === null) return b\n\tif (b === null) return a\n\treturn compareStamps(a, b) <= 0 ? a : b\n}\n\n/** True when `a` is strictly later than `b` (anything is later than null). */\nexport function isAfter(a: Stamp | null, b: Stamp | null): boolean {\n\tif (a === null) return false\n\tif (b === null) return true\n\treturn compareStamps(a, b) > 0\n}\n\n/**\n * Build a stamp from an HLC timestamp and an operation id. Throws\n * `InvalidTimestampError` for a timestamp that cannot be serialized (for example\n * a null wallTime left by a beta.12 backup restore): such an op must be repaired\n * by the log-integrity scan (W8) before it is folded, never folded with a guess.\n */\nexport function stampOf(timestamp: HLCTimestamp, opId: string): Stamp {\n\treturn { t: HybridLogicalClock.serialize(timestamp), o: opId }\n}\n\n/** The HLC timestamp a stamp was built from. */\nexport function stampTimestamp(stamp: Stamp): HLCTimestamp {\n\treturn HybridLogicalClock.deserialize(stamp.t)\n}\n","import type { CollectionDefinition, CustomResolver } from '../types'\nimport type { FoldFieldKind } from './types'\n\n/** How the fold merges one field, derived from the schema. */\nexport interface FieldPlan {\n\tkind: FoldFieldKind\n\t/** For 'set': removals are ignored (`merge('append-only')`). */\n\tappendOnly: boolean\n\t/** For 'res': the custom resolver. */\n\tresolver?: CustomResolver\n\t/** Name of the strategy for traces. */\n\tstrategy: string\n\t/** Tier for traces: 3 for custom resolvers, 1 otherwise. */\n\ttier: 1 | 3\n\t/** Secret field: values are redacted from traces. */\n\tsecret: boolean\n\t/** `merge('server-authoritative')`: writes of authoritative nodes win (class 1 stamps). */\n\tauthoritative?: boolean\n}\n\nconst LWW_PLAN: FieldPlan = {\n\tkind: 'reg',\n\tappendOnly: false,\n\tstrategy: 'lww',\n\ttier: 1,\n\tsecret: false,\n}\n\n/**\n * Decide how a field is folded. Precedence matches the pairwise engine it\n * replaces: a custom resolver (tier 3), then a schema-declared merge strategy,\n * then the field kind's default.\n *\n * | Schema | Fold kind |\n * |---|---|\n * | `resolve: { field }` | 'res' (resolver over the write log) |\n * | `merge('counter')` | 'ctr' (base + deltas) |\n * | `merge('max')` / `merge('min')` | 'max' / 'min' |\n * | array, `merge('append-only')` | 'set' with removals ignored |\n * | array (default or `merge('union')`) | 'set' (LWW element set) |\n * | object / json | 'map' (per-top-level-key LWW) |\n * | richtext | 'rt' (Yjs updates + string reset register) |\n * | `merge('server-authoritative')` | 'reg' with authority-classed stamps |\n * | everything else, `merge('lww')` | 'reg' |\n *\n * A field that is not in the schema (an older schema version's field, or an\n * unknown collection) folds as a last-write-wins register.\n *\n * @param collection - The collection definition, if the schema has it\n * @param field - The field name\n */\nexport function planField(collection: CollectionDefinition | undefined, field: string): FieldPlan {\n\tconst descriptor = collection?.fields[field]\n\tconst secret = descriptor?.kind === 'secret'\n\tconst resolver = collection?.resolvers?.[field]\n\tif (resolver) {\n\t\treturn { kind: 'res', appendOnly: false, resolver, strategy: 'custom', tier: 3, secret }\n\t}\n\tif (!descriptor) return LWW_PLAN\n\tswitch (descriptor.mergeStrategy) {\n\t\tcase 'counter':\n\t\t\treturn { kind: 'ctr', appendOnly: false, strategy: 'schema-counter', tier: 1, secret }\n\t\tcase 'max':\n\t\t\treturn { kind: 'max', appendOnly: false, strategy: 'schema-max', tier: 1, secret }\n\t\tcase 'min':\n\t\t\treturn { kind: 'min', appendOnly: false, strategy: 'schema-min', tier: 1, secret }\n\t\tcase 'append-only':\n\t\t\tif (descriptor.kind === 'array') {\n\t\t\t\treturn {\n\t\t\t\t\tkind: 'set',\n\t\t\t\t\tappendOnly: true,\n\t\t\t\t\tstrategy: 'schema-append-only',\n\t\t\t\t\ttier: 1,\n\t\t\t\t\tsecret,\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { ...LWW_PLAN, secret }\n\t\tcase 'lww':\n\t\t\treturn { ...LWW_PLAN, secret }\n\t\tcase 'server-authoritative':\n\t\t\t// A register whose writes from authoritative nodes (FoldOptions.\n\t\t\t// authoritativeNodeIds: the server's node ids) beat every other write,\n\t\t\t// whatever their HLC. Within a class, last write wins.\n\t\t\treturn {\n\t\t\t\t...LWW_PLAN,\n\t\t\t\tstrategy: 'schema-server-authoritative',\n\t\t\t\tsecret,\n\t\t\t\tauthoritative: true,\n\t\t\t}\n\t\tdefault:\n\t\t\tbreak\n\t}\n\tswitch (descriptor.kind) {\n\t\tcase 'array':\n\t\t\treturn { kind: 'set', appendOnly: false, strategy: 'lww-element-set', tier: 1, secret }\n\t\tcase 'object':\n\t\tcase 'json':\n\t\t\treturn { kind: 'map', appendOnly: false, strategy: 'object-key-lww', tier: 1, secret }\n\t\tcase 'richtext':\n\t\t\treturn { kind: 'rt', appendOnly: false, strategy: 'crdt-text', tier: 1, secret }\n\t\tdefault:\n\t\t\treturn { ...LWW_PLAN, secret }\n\t}\n}\n"],"mappings":"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","names":["canonical","changed","out","canonical"]}