korajs 1.0.0-beta.10 → 1.0.0-beta.12
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.
- package/dist/{chunk-CHGDHGEI.js → chunk-3WMSWJAQ.js} +7 -1
- package/dist/{chunk-CHGDHGEI.js.map → chunk-3WMSWJAQ.js.map} +1 -1
- package/dist/index.cjs +129 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -8
- package/dist/index.d.ts +44 -8
- package/dist/index.js +124 -4
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +6 -0
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +5 -0
- package/dist/testing.d.ts +5 -0
- package/dist/testing.js +1 -1
- package/package.json +13 -13
|
@@ -825,6 +825,12 @@ var MergeAwareSyncStore = class {
|
|
|
825
825
|
async applyRemoteOperation(op) {
|
|
826
826
|
return this.pipeline.applyRemote(op);
|
|
827
827
|
}
|
|
828
|
+
async applyScopeRetraction(collection, recordId) {
|
|
829
|
+
return this.store.applyScopeRetraction(collection, recordId);
|
|
830
|
+
}
|
|
831
|
+
async applyScopeNarrowing(scopes) {
|
|
832
|
+
return this.store.applyScopeNarrowing(scopes);
|
|
833
|
+
}
|
|
828
834
|
/**
|
|
829
835
|
* Delegates timestamp rebase to the store so the sync engine can re-stamp
|
|
830
836
|
* never-acknowledged operations after a fast device clock is corrected.
|
|
@@ -932,4 +938,4 @@ export {
|
|
|
932
938
|
StoreQueueStorage,
|
|
933
939
|
StoreSyncStatePersistence
|
|
934
940
|
};
|
|
935
|
-
//# sourceMappingURL=chunk-
|
|
941
|
+
//# sourceMappingURL=chunk-3WMSWJAQ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/apply-pipeline.ts","../src/build-side-effect-entry.ts","../src/audit-bridge.ts","../src/blob/blob-upload-coordinator.ts","../src/blob/sync-chunk-port.ts","../src/merge-aware-sync-store.ts","../src/store-queue-storage.ts","../src/store-sync-state.ts"],"sourcesContent":["import type {\n\tCollectionDefinition,\n\tHLCTimestamp,\n\tKoraEventEmitter,\n\tOperation,\n\tSchemaDefinition,\n} from '@korajs/core'\nimport {\n\tHybridLogicalClock,\n\tKoraError,\n\tcreateOperation,\n\treplayOperationsForRecord,\n} from '@korajs/core'\nimport { topologicalSort } from '@korajs/core/internal'\nimport type { MergeEngine, MergeResult, ReferentialMergeContext, SideEffectOp } from '@korajs/merge'\nimport { buildMergeRelationLookup, checkReferentialIntegrityOnDelete } from '@korajs/merge'\nimport type { ConstraintContext } from '@korajs/merge'\nimport type {\n\tApplyRemoteOptions,\n\tCollectionRecord,\n\tLocalMutationHandler,\n\tRowVersionState,\n\tStore,\n\tTransactionCommitBatch,\n\tTransactionCommitResult,\n} from '@korajs/store'\nimport { OptimisticLockError, richtextStatesEqual } from '@korajs/store'\nimport {\n\texecuteDelete,\n\texecuteInsert,\n\texecuteUpdate,\n\tresolveCausalDeps,\n} from '@korajs/store/internal'\nimport type { ApplyResult } from '@korajs/sync'\nimport { buildSideEffectEntry } from './build-side-effect-entry'\n\n/**\n * Whether the operation originated locally or arrived from sync.\n */\nexport type ApplyMode = 'local' | 'remote'\n\n/**\n * How many times a merge-engine apply retries when the row changed underneath\n * it (OptimisticLockError). Each retry recomputes the merge from fresh state;\n * since JavaScript is single-threaded, invalidation requires an actual new\n * local write landing at an `await` point, so contention is self-limiting.\n * Exhaustion rethrows: the operation stays unacknowledged and sync retries it\n * later — data is never dropped.\n */\nconst MERGE_APPLY_RETRY_LIMIT = 8\n\n/**\n * Dependencies for the unified apply pipeline.\n */\nexport interface ApplyPipelineDeps {\n\treadonly store: Store\n\treadonly mergeEngine: MergeEngine\n\treadonly emitter: KoraEventEmitter | null\n\t/** Called when a field-level merge runs (updates sync conflict counter). */\n\treadonly onMergeConflict?: () => void\n}\n\n/**\n * Context passed into each apply invocation.\n */\nexport interface ApplyContext {\n\treadonly mode: ApplyMode\n\treadonly schema: SchemaDefinition\n}\n\n/**\n * Single entry point for applying remote sync operations with merge + referential integrity.\n */\nexport class ApplyPipeline implements LocalMutationHandler {\n\tprivate readonly relationLookupMap: ReturnType<typeof buildMergeRelationLookup>\n\n\tconstructor(private readonly deps: ApplyPipelineDeps) {\n\t\tthis.relationLookupMap = buildMergeRelationLookup(deps.store.getSchema())\n\t}\n\n\t/** Local insert — same persistence path as remote apply side effects. */\n\tasync insert(collection: string, data: Record<string, unknown>): Promise<CollectionRecord> {\n\t\treturn executeInsert(this.deps.store.createMutationContext(collection), data)\n\t}\n\n\t/** Local update. */\n\tasync update(\n\t\tcollection: string,\n\t\tid: string,\n\t\tdata: Record<string, unknown>,\n\t): Promise<CollectionRecord> {\n\t\treturn executeUpdate(this.deps.store.createMutationContext(collection), id, data)\n\t}\n\n\t/**\n\t * Local delete with merge-package referential integrity (same rules as remote delete).\n\t */\n\tasync delete(collection: string, id: string): Promise<void> {\n\t\tconst ctx = this.deps.store.createMutationContext(collection)\n\t\tconst causalDeps = resolveCausalDeps(ctx)\n\t\tconst operation = await createOperation(\n\t\t\t{\n\t\t\t\tnodeId: ctx.nodeId,\n\t\t\t\ttype: 'delete',\n\t\t\t\tcollection,\n\t\t\t\trecordId: id,\n\t\t\t\tdata: null,\n\t\t\t\tpreviousData: null,\n\t\t\t\tsequenceNumber: await ctx.allocateSequenceNumber(),\n\t\t\t\tcausalDeps,\n\t\t\t\tschemaVersion: ctx.schema.version,\n\t\t\t},\n\t\t\tctx.clock,\n\t\t)\n\t\tctx.causalTracker?.afterOperation(collection, operation.id, ctx.inTransaction)\n\n\t\tconst refCtx = createReferentialMergeContext(this.deps.store)\n\t\tconst check = await checkReferentialIntegrityOnDelete(\n\t\t\toperation,\n\t\t\tthis.deps.store.getSchema(),\n\t\t\trefCtx,\n\t\t\tthis.relationLookupMap,\n\t\t)\n\n\t\tfor (const trace of check.traces) {\n\t\t\tthis.deps.emitter?.emit({ type: 'merge:conflict', trace })\n\t\t}\n\n\t\tif (!check.allowed) {\n\t\t\tthrow new KoraError(\n\t\t\t\t`Cannot delete record \"${id}\" from \"${collection}\": referential restrict policy violated`,\n\t\t\t\t'REFERENTIAL_INTEGRITY',\n\t\t\t\t{ collection, recordId: id },\n\t\t\t)\n\t\t}\n\n\t\tawait executeDelete(ctx, id, {\n\t\t\tskipReferentialEnforcement: true,\n\t\t\toperation,\n\t\t})\n\n\t\tif (check.sideEffectOps.length > 0) {\n\t\t\tawait applySideEffectOps(this.deps.store, check.sideEffectOps, operation.id)\n\t\t}\n\t}\n\n\t/**\n\t * Commit a buffered transaction: merge-package delete enforcement, causal ordering, single DB txn.\n\t */\n\tasync commitTransaction(batch: TransactionCommitBatch): Promise<TransactionCommitResult> {\n\t\tconst store = this.deps.store\n\t\tconst supplemental: TransactionCommitBatch['entries'] = []\n\n\t\tfor (const entry of batch.entries) {\n\t\t\tif (entry.operation.type !== 'delete') {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst refCtx = createReferentialMergeContext(store)\n\t\t\tconst check = await checkReferentialIntegrityOnDelete(\n\t\t\t\tentry.operation,\n\t\t\t\tstore.getSchema(),\n\t\t\t\trefCtx,\n\t\t\t\tthis.relationLookupMap,\n\t\t\t)\n\n\t\t\tfor (const trace of check.traces) {\n\t\t\t\tthis.deps.emitter?.emit({ type: 'merge:conflict', trace })\n\t\t\t}\n\n\t\t\tif (!check.allowed) {\n\t\t\t\tthrow new KoraError(\n\t\t\t\t\t`Cannot delete record \"${entry.operation.recordId}\" from \"${entry.collection}\": referential restrict policy violated`,\n\t\t\t\t\t'REFERENTIAL_INTEGRITY',\n\t\t\t\t\t{ collection: entry.collection, recordId: entry.operation.recordId },\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tfor (const effect of check.sideEffectOps) {\n\t\t\t\tconst ctx = store.createMutationContext(effect.collection, { inTransaction: true })\n\t\t\t\tsupplemental.push(\n\t\t\t\t\tawait buildSideEffectEntry(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\teffect,\n\t\t\t\t\t\tentry.operation.id,\n\t\t\t\t\t\tbatch.transactionId,\n\t\t\t\t\t\tbatch.mutationName,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tconst allEntries = [...batch.entries, ...supplemental]\n\t\tconst sortedOps = topologicalSort(allEntries.map((e) => e.operation))\n\t\tconst commandsByOpId = new Map(allEntries.map((e) => [e.operation.id, e.commands] as const))\n\n\t\tconst ctx = store.createMutationContext(\n\t\t\tbatch.entries[0]?.collection ?? Object.keys(store.getSchema().collections)[0] ?? 'todos',\n\t\t\t{ inTransaction: true },\n\t\t)\n\n\t\tawait ctx.adapter.transaction(async (tx) => {\n\t\t\tfor (const op of sortedOps) {\n\t\t\t\tconst commands = commandsByOpId.get(op.id)\n\t\t\t\tif (!commands) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor (const cmd of commands) {\n\t\t\t\t\tawait tx.execute(cmd.sql, cmd.params)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tconst affectedCollections = new Set<string>()\n\t\tfor (const entry of allEntries) {\n\t\t\taffectedCollections.add(entry.collection)\n\t\t}\n\n\t\treturn { operations: sortedOps, affectedCollections }\n\t}\n\n\tasync applyRemote(op: Operation): Promise<ApplyResult> {\n\t\treturn this.apply(op, { mode: 'remote', schema: this.deps.store.getSchema() })\n\t}\n\n\tasync apply(op: Operation, context: ApplyContext): Promise<ApplyResult> {\n\t\tif (context.mode === 'local') {\n\t\t\treturn this.deps.store.applyRemoteOperation(op)\n\t\t}\n\n\t\tif (op.type === 'delete') {\n\t\t\treturn this.applyRemoteDelete(op)\n\t\t}\n\n\t\tif (op.type === 'insert' && op.data) {\n\t\t\treturn this.applyRemoteInsert(op)\n\t\t}\n\n\t\tif (op.type === 'update' && op.data && op.previousData) {\n\t\t\treturn this.applyRemoteUpdate(op)\n\t\t}\n\n\t\treturn this.deps.store.applyRemoteOperation(op)\n\t}\n\n\tprivate async applyRemoteDelete(op: Operation): Promise<ApplyResult> {\n\t\tconst blocked = await this.resolveRemoteDeleteVsLocalUpdate(op)\n\t\tif (blocked) {\n\t\t\treturn 'skipped'\n\t\t}\n\n\t\tconst refCtx = createReferentialMergeContext(this.deps.store)\n\t\tconst check = await checkReferentialIntegrityOnDelete(\n\t\t\top,\n\t\t\tthis.deps.store.getSchema(),\n\t\t\trefCtx,\n\t\t\tthis.relationLookupMap,\n\t\t)\n\n\t\tfor (const trace of check.traces) {\n\t\t\tthis.deps.emitter?.emit({ type: 'merge:conflict', trace })\n\t\t}\n\n\t\tif (!check.allowed) {\n\t\t\treturn 'rejected'\n\t\t}\n\n\t\tconst result = await this.deps.store.applyRemoteOperation(op)\n\t\tif (result !== 'applied') {\n\t\t\treturn result\n\t\t}\n\n\t\tif (check.sideEffectOps.length > 0) {\n\t\t\tawait applySideEffectOps(this.deps.store, check.sideEffectOps, op.id)\n\t\t}\n\n\t\treturn 'applied'\n\t}\n\n\t/**\n\t * When a local update is newer than a remote delete, keep the record alive.\n\t */\n\tprivate async resolveRemoteDeleteVsLocalUpdate(op: Operation): Promise<boolean> {\n\t\tconst collectionDef = this.deps.store.getSchema().collections[op.collection]\n\t\tif (!collectionDef) {\n\t\t\treturn false\n\t\t}\n\n\t\tconst localOp = await this.deps.store.getLatestLocalOperationForRecord(\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t)\n\t\tif (!localOp || localOp.type !== 'update') {\n\t\t\treturn false\n\t\t}\n\n\t\tconst baseState = localOp.previousData ?? {}\n\t\tconst mergeResult = await this.deps.mergeEngine.merge({\n\t\t\tlocal: localOp,\n\t\t\tremote: op,\n\t\t\tbaseState,\n\t\t\tcollectionDef,\n\t\t})\n\n\t\tthis.emitMergeLifecycle(op, localOp, mergeResult)\n\n\t\treturn mergeResult.appliedOperation === 'local'\n\t}\n\n\tprivate async applyRemoteUpdate(op: Operation): Promise<ApplyResult> {\n\t\tconst schema = this.deps.store.getSchema()\n\t\tconst collectionDef = schema.collections[op.collection]\n\t\tif (!collectionDef) {\n\t\t\treturn this.deps.store.applyRemoteOperation(op)\n\t\t}\n\n\t\t// Merge-engine paths read state, compute a merge, then write. A concurrent\n\t\t// local mutation landing between read and write invalidates the merge; the\n\t\t// guarded apply detects this (OptimisticLockError) and we recompute from\n\t\t// fresh state. Bounded so a pathological writer can't spin forever —\n\t\t// exhaustion rethrows, leaving the op unacknowledged for a later sync\n\t\t// retry (never silently dropped).\n\t\tlet lockError: OptimisticLockError | null = null\n\t\tfor (let attempt = 0; attempt < MERGE_APPLY_RETRY_LIMIT; attempt++) {\n\t\t\ttry {\n\t\t\t\treturn await this.applyRemoteUpdateAttempt(op, collectionDef)\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof OptimisticLockError)) {\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t\tlockError = error\n\t\t\t}\n\t\t}\n\t\tthrow lockError ?? new OptimisticLockError(op.collection, op.recordId)\n\t}\n\n\tprivate async applyRemoteUpdateAttempt(\n\t\top: Operation,\n\t\tcollectionDef: CollectionDefinition,\n\t): Promise<ApplyResult> {\n\t\t// Snapshot the row's version state BEFORE reading the record: if a local\n\t\t// write lands between these two reads, the guard is older than the record\n\t\t// and the guarded apply fails safe (retry). The reverse order would let a\n\t\t// merge computed from a stale record pass a fresh guard and clobber the\n\t\t// local write.\n\t\tconst guard: RowVersionState = (await this.deps.store.getRowVersionState(\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t)) ?? { version: null, fieldVersions: null }\n\n\t\tconst accessor = this.deps.store.collection(op.collection)\n\t\tconst currentRecord = await accessor.findById(op.recordId)\n\n\t\tif (!currentRecord) {\n\t\t\tconst tombstoneResult = await this.applyRemoteUpdateOnDeletedRow(op, collectionDef, guard)\n\t\t\tif (tombstoneResult !== null) {\n\t\t\t\treturn tombstoneResult\n\t\t\t}\n\t\t\t// No materialized row at all: the store's atomic path appends the op\n\t\t\t// (nothing to guard — materialization is a no-op until the insert lands).\n\t\t\treturn this.deps.store.applyRemoteOperation(op)\n\t\t}\n\n\t\t// Pure scalar last-write-wins is resolved deterministically and atomically\n\t\t// inside the store using per-field HLC versions. Routing it there (instead\n\t\t// of pre-reading the row here to guess a conflict) both removes the\n\t\t// read-decide-write race — a concurrent local edit can no longer slip\n\t\t// between the check and the write — and guarantees every node converges to\n\t\t// the max-timestamp writer regardless of the order operations arrive. Only\n\t\t// CRDT fields (richtext, add-wins-set arrays), declared constraints, or\n\t\t// custom resolvers still need the three-tier merge engine.\n\t\tif (!updateNeedsMergeEngine(op, collectionDef)) {\n\t\t\t// The store resolves the write deterministically, but a same-field\n\t\t\t// concurrent edit is still a merge decision the developer/DevTools must\n\t\t\t// see. Emit the trace from the immutable local op in the log (race-free),\n\t\t\t// then let the store perform the authoritative atomic write.\n\t\t\tawait this.observeScalarMerge(op, collectionDef)\n\t\t\treturn this.deps.store.applyRemoteOperation(op)\n\t\t}\n\n\t\tlet hasConflict = false\n\t\tfor (const field of Object.keys(op.data ?? {})) {\n\t\t\tconst fieldDef = collectionDef.fields[field]\n\t\t\tconst expectedBase = op.previousData?.[field]\n\t\t\tconst currentLocal = currentRecord[field]\n\n\t\t\tif (fieldDef?.kind === 'richtext') {\n\t\t\t\tif (!richtextStatesEqual(currentLocal, expectedBase)) {\n\t\t\t\t\thasConflict = true\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (!deepEqual(expectedBase, currentLocal)) {\n\t\t\t\thasConflict = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (!hasConflict) {\n\t\t\t// No CRDT/constraint field the remote op touches has diverged locally\n\t\t\t// from the base it wrote from: a clean fast-forward. Guarded, because\n\t\t\t// the divergence check above read the row outside the write transaction.\n\t\t\treturn this.deps.store.applyRemoteOperation(op, {\n\t\t\t\tforceMaterialize: true,\n\t\t\t\tguardRowState: guard,\n\t\t\t})\n\t\t}\n\n\t\treturn this.applyMergedUpdate(op, collectionDef, currentRecord, op.previousData ?? {}, guard)\n\t}\n\n\t/**\n\t * Remote update vs local soft-delete: merge before materializing to avoid zombie rows.\n\t */\n\t/**\n\t * Emit merge observability (trace + conflict counter) for a scalar update that\n\t * concurrently touches a field a local operation also changed.\n\t *\n\t * The store resolves the actual value by deterministic per-field LWW; this\n\t * method exists only so the decision is loggable (CLAUDE.md: every merge\n\t * decision must be traceable). It reads the local operation from the append-\n\t * only log — which is immutable — rather than the materialized row, so it is\n\t * not subject to the read-then-write race the store write path was hardened\n\t * against. The merge engine is invoked purely to build an accurate trace; its\n\t * timestamps match the store's LWW comparison, so the trace never lies.\n\t */\n\tprivate async observeScalarMerge(\n\t\top: Operation,\n\t\tcollectionDef: CollectionDefinition,\n\t): Promise<void> {\n\t\tconst localOp = await this.deps.store.getLatestLocalOperationForRecord(\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t)\n\t\tif (!localOp || !localOp.data) {\n\t\t\treturn\n\t\t}\n\t\tconst remoteFields = Object.keys(op.data ?? {})\n\t\tconst sharesField = remoteFields.some((field) => localOp.data?.[field] !== undefined)\n\t\tif (!sharesField) {\n\t\t\treturn\n\t\t}\n\n\t\tconst mergeResult = await this.deps.mergeEngine.merge({\n\t\t\tlocal: localOp,\n\t\t\tremote: op,\n\t\t\tbaseState: op.previousData ?? {},\n\t\t\tcollectionDef,\n\t\t})\n\t\tthis.emitMergeLifecycle(op, localOp, mergeResult)\n\t}\n\n\tprivate async applyRemoteUpdateOnDeletedRow(\n\t\top: Operation,\n\t\tcollectionDef: CollectionDefinition,\n\t\tguard: RowVersionState,\n\t): Promise<ApplyResult | null> {\n\t\tconst snapshot = await this.deps.store.findMaterializedRow(op.collection, op.recordId)\n\t\tif (!snapshot?.deleted) {\n\t\t\t// Not a tombstone (never inserted): let the caller append the orphan.\n\t\t\treturn null\n\t\t}\n\n\t\t// A remote update landing on a tombstone must be resolved authoritatively by\n\t\t// folding the record's whole operation log in HLC order — the same fold the\n\t\t// server uses — so every device agrees on whether the update resurrects the\n\t\t// record and on the resulting field values. The previous path only handled a\n\t\t// LOCAL delete (via the pairwise merge engine) and left a remote-created\n\t\t// tombstone hidden on any passive observer that received delete-then-update,\n\t\t// and it materialized the resurrecting op's raw value rather than the composed\n\t\t// atomic chain. Folding fixes both: delete-vs-newer-update convergence and\n\t\t// atomic resurrection (increments before and after the delete compose correctly).\n\t\tconst priorOps = await this.deps.store.getOperationsForRecord(op.collection, op.recordId)\n\t\tconst ops = [...priorOps, op].sort((a, b) =>\n\t\t\tHybridLogicalClock.compare(a.timestamp, b.timestamp),\n\t\t)\n\t\tconst folded = replayOperationsForRecord(ops)\n\n\t\tif (!folded) {\n\t\t\t// The delete still wins: append the op to the log (so a later fold — e.g. an\n\t\t\t// atomic resurrection composing this op's delta — is complete) but leave the\n\t\t\t// row a clean tombstone: no zombie fields, no version regression.\n\t\t\treturn this.deps.store.applyRemoteOperation(op, { logOnly: true })\n\t\t}\n\n\t\t// Resurrected: materialize the authoritative folded state and clear the\n\t\t// tombstone. The log keeps the canonical op; the row takes the folded data at\n\t\t// the newest operation's timestamp. Guarded so a concurrent local write retries.\n\t\tconst newestTimestamp = (ops[ops.length - 1] as Operation).timestamp\n\t\treturn this.deps.store.applyRemoteOperation(op, {\n\t\t\treactivateIfDeleted: true,\n\t\t\tforceMaterialize: true,\n\t\t\tmaterializeData: folded,\n\t\t\tmaterializeTimestamp: maxTimestamp(op.timestamp, newestTimestamp),\n\t\t\tguardRowState: guard,\n\t\t})\n\t}\n\n\tprivate async applyMergedUpdate(\n\t\top: Operation,\n\t\tcollectionDef: CollectionDefinition,\n\t\tcurrentRecord: CollectionRecord,\n\t\tbaseState: Record<string, unknown>,\n\t\tguard: RowVersionState,\n\t\tapplyOptions?: ApplyRemoteOptions,\n\t): Promise<ApplyResult> {\n\t\tconst latestLocal = await this.deps.store.getLatestLocalOperationForRecord(\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t)\n\t\tconst localTimestamp = resolveLocalTimestamp(\n\t\t\tlatestLocal,\n\t\t\tcurrentRecord,\n\t\t\tthis.deps.store.getNodeId(),\n\t\t)\n\t\t// The spread of `op` would copy the REMOTE op's atomicOps onto the local\n\t\t// side, making atomic composition double the remote delta instead of\n\t\t// summing both intents. The local side's intent metadata must come from\n\t\t// the actual local operation — or be absent entirely.\n\t\tconst { atomicOps: _remoteAtomicOps, ...opWithoutAtomic } = op\n\t\tconst localOp: Operation = {\n\t\t\t...opWithoutAtomic,\n\t\t\tdata: buildLocalDiff(baseState, currentRecord, Object.keys(op.data ?? {})),\n\t\t\tpreviousData: op.previousData,\n\t\t\tnodeId: this.deps.store.getNodeId(),\n\t\t\ttimestamp: localTimestamp,\n\t\t\t...(latestLocal?.atomicOps !== undefined ? { atomicOps: latestLocal.atomicOps } : {}),\n\t\t}\n\n\t\tconst constraintContext = createConstraintContext(this.deps.store)\n\t\tconst mergeResult = await this.deps.mergeEngine.merge(\n\t\t\t{\n\t\t\t\tlocal: localOp,\n\t\t\t\tremote: op,\n\t\t\t\tbaseState,\n\t\t\t\tcollectionDef,\n\t\t\t},\n\t\t\tconstraintContext,\n\t\t)\n\n\t\t// Atomic-op fields are materialized by folding the record's operation log in\n\t\t// HLC order (the same fold the server uses), not through the merge engine's\n\t\t// pairwise `base + localDelta + remoteDelta`. The pairwise form is correct only\n\t\t// for two concurrent writes from a shared base; with a third concurrent writer\n\t\t// the current row already folds in an earlier remote delta, and re-deriving from\n\t\t// base + the local device's own delta silently drops it (a lost update). The\n\t\t// fold composes a same-type atomic chain and last-write-wins for anything else\n\t\t// (including a plain set breaking the chain), so every device — author or passive\n\t\t// observer — converges to the same value the server materializes.\n\t\tconst mergedData = await this.materializeAtomicFieldsFromLog(op, mergeResult.mergedData)\n\n\t\t// The merged data is authoritative (it already folds in the current local\n\t\t// row), so it must materialize even when its timestamp ties the current\n\t\t// row version — otherwise the device that authored the newer of two\n\t\t// concurrent edits silently drops the merge result and never converges.\n\t\t// The LOG stores the canonical op (ops are immutable, content-addressed);\n\t\t// only the row write uses the merged data and max(local, remote) stamp.\n\t\t// Guarded: a local write since our snapshot invalidates the merge.\n\t\tconst applyResult = await this.deps.store.applyRemoteOperation(op, {\n\t\t\t...applyOptions,\n\t\t\tforceMaterialize: true,\n\t\t\tmaterializeData: mergedData,\n\t\t\tmaterializeTimestamp: maxTimestamp(op.timestamp, localTimestamp),\n\t\t\tguardRowState: guard,\n\t\t})\n\n\t\t// Emit AFTER the guarded apply so a retried attempt doesn't double-report\n\t\t// a merge decision that never materialized.\n\t\tthis.emitMergeLifecycle(op, localOp, mergeResult)\n\n\t\tif (applyResult === 'applied' && mergeResult.sideEffects.length > 0) {\n\t\t\tawait applySideEffectOps(this.deps.store, mergeResult.sideEffects, op.id)\n\t\t}\n\n\t\treturn applyResult\n\t}\n\n\t/**\n\t * Materialize the operation's atomic-op fields by folding the record's full\n\t * operation log (in HLC order, including this not-yet-appended op) through the\n\t * shared atomic-aware replay — the same fold the server uses. Non-atomic fields\n\t * keep their merge-engine result. This composes a same-type atomic chain and\n\t * last-write-wins for anything else, so all replicas converge regardless of how\n\t * many concurrent atomic writers there were or which device authored them.\n\t */\n\tprivate async materializeAtomicFieldsFromLog(\n\t\top: Operation,\n\t\tmergedData: Record<string, unknown>,\n\t): Promise<Record<string, unknown>> {\n\t\tif (!op.atomicOps || Object.keys(op.atomicOps).length === 0) {\n\t\t\treturn mergedData\n\t\t}\n\t\tconst priorOps = await this.deps.store.getOperationsForRecord(op.collection, op.recordId)\n\t\t// The op being applied is not in the log yet; fold it in at its HLC position.\n\t\tconst ops = [...priorOps, op].sort((a, b) =>\n\t\t\tHybridLogicalClock.compare(a.timestamp, b.timestamp),\n\t\t)\n\t\tconst folded = replayOperationsForRecord(ops)\n\t\tif (!folded) {\n\t\t\treturn mergedData\n\t\t}\n\t\tconst result = { ...mergedData }\n\t\tfor (const field of Object.keys(op.atomicOps)) {\n\t\t\tif (field in folded) {\n\t\t\t\tresult[field] = folded[field]\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate async applyRemoteInsert(op: Operation): Promise<ApplyResult> {\n\t\tconst collectionDef = this.deps.store.getSchema().collections[op.collection]\n\t\tif (!collectionDef || !op.data) {\n\t\t\treturn this.deps.store.applyRemoteOperation(op)\n\t\t}\n\n\t\tlet lockError: OptimisticLockError | null = null\n\t\tfor (let attempt = 0; attempt < MERGE_APPLY_RETRY_LIMIT; attempt++) {\n\t\t\ttry {\n\t\t\t\treturn await this.applyRemoteInsertAttempt(op, collectionDef)\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof OptimisticLockError)) {\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t\tlockError = error\n\t\t\t}\n\t\t}\n\t\tthrow lockError ?? new OptimisticLockError(op.collection, op.recordId)\n\t}\n\n\tprivate async applyRemoteInsertAttempt(\n\t\top: Operation,\n\t\tcollectionDef: CollectionDefinition,\n\t): Promise<ApplyResult> {\n\t\t// Guard snapshot BEFORE the record read (see applyRemoteUpdateAttempt).\n\t\tconst guard: RowVersionState = (await this.deps.store.getRowVersionState(\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t)) ?? { version: null, fieldVersions: null }\n\n\t\tconst snapshot = await this.deps.store.findMaterializedRow(op.collection, op.recordId)\n\t\tif (!snapshot || snapshot.deleted) {\n\t\t\t// Absent row and insert-vs-tombstone are resolved atomically inside the\n\t\t\t// store; guard against a local write racing this decision.\n\t\t\treturn this.deps.store.applyRemoteOperation(op, { guardRowState: guard })\n\t\t}\n\n\t\tconst localOp = await resolveLocalOpForRecord(\n\t\t\tthis.deps.store,\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t\tsnapshot.record,\n\t\t)\n\n\t\tconst mergeResult = await this.deps.mergeEngine.merge({\n\t\t\tlocal: localOp,\n\t\t\tremote: op,\n\t\t\tbaseState: {},\n\t\t\tcollectionDef,\n\t\t})\n\n\t\t// Canonical op in the log; merged data + max(local, remote) stamp on the\n\t\t// row. Force, because the merged result folds in the current local row and\n\t\t// must win even on a version tie with it.\n\t\tconst applyResult = await this.deps.store.applyRemoteOperation(op, {\n\t\t\tforceMaterialize: true,\n\t\t\tmaterializeData: mergeResult.mergedData,\n\t\t\tmaterializeTimestamp: maxTimestamp(op.timestamp, localOp.timestamp),\n\t\t\tguardRowState: guard,\n\t\t})\n\n\t\tthis.emitMergeLifecycle(op, localOp, mergeResult)\n\n\t\treturn applyResult\n\t}\n\n\tprivate emitMergeLifecycle(remote: Operation, local: Operation, mergeResult: MergeResult): void {\n\t\tthis.deps.emitter?.emit({\n\t\t\ttype: 'merge:started',\n\t\t\toperationA: remote,\n\t\t\toperationB: local,\n\t\t})\n\n\t\tconst hadMergeConflict = mergeResult.traces.some(\n\t\t\t(t) =>\n\t\t\t\tt.strategy !== 'no-conflict-local' &&\n\t\t\t\tt.strategy !== 'no-conflict-remote' &&\n\t\t\t\tt.strategy !== 'no-conflict-unchanged',\n\t\t)\n\t\tif (hadMergeConflict) {\n\t\t\tthis.deps.onMergeConflict?.()\n\t\t}\n\n\t\tfor (const trace of mergeResult.traces) {\n\t\t\tif (\n\t\t\t\ttrace.strategy !== 'no-conflict-local' &&\n\t\t\t\ttrace.strategy !== 'no-conflict-remote' &&\n\t\t\t\ttrace.strategy !== 'no-conflict-unchanged'\n\t\t\t) {\n\t\t\t\tthis.deps.emitter?.emit({ type: 'merge:conflict', trace })\n\t\t\t}\n\t\t}\n\t\tconst firstTrace = mergeResult.traces[0]\n\t\tif (firstTrace) {\n\t\t\tthis.deps.emitter?.emit({ type: 'merge:completed', trace: firstTrace })\n\t\t}\n\t}\n}\n\n/**\n * Prefer the latest local op, then any op in the log for this record, then a snapshot-derived insert.\n */\nasync function resolveLocalOpForRecord(\n\tstore: Store,\n\tcollection: string,\n\trecordId: string,\n\trecord: CollectionRecord,\n): Promise<Operation> {\n\tconst localLatest = await store.getLatestLocalOperationForRecord(collection, recordId)\n\tif (localLatest) {\n\t\treturn localLatest\n\t}\n\n\tconst anyLatest = await store.getLatestOperationForRecord(collection, recordId)\n\tif (anyLatest) {\n\t\treturn anyLatest\n\t}\n\n\treturn syntheticInsertFromSnapshot(\n\t\trecord,\n\t\tcollection,\n\t\tstore.getNodeId(),\n\t\tstore.getSchema().version,\n\t)\n}\n\nfunction syntheticInsertFromSnapshot(\n\trecord: CollectionRecord,\n\tcollection: string,\n\tnodeId: string,\n\tschemaVersion: number,\n): Operation {\n\tconst { id, createdAt, updatedAt, ...fields } = record\n\treturn {\n\t\tid: `synthetic-local-${id}`,\n\t\tnodeId,\n\t\ttype: 'insert',\n\t\tcollection,\n\t\trecordId: id,\n\t\tdata: fields,\n\t\tpreviousData: null,\n\t\ttimestamp: { wallTime: updatedAt, logical: 0, nodeId },\n\t\tsequenceNumber: 0,\n\t\tcausalDeps: [],\n\t\tschemaVersion,\n\t}\n}\n\nfunction createConstraintContext(store: Store): ConstraintContext {\n\treturn {\n\t\tasync queryRecords(collection: string, where: Record<string, unknown>) {\n\t\t\tconst rows = await store.collection(collection).where(where).exec()\n\t\t\treturn rows as Record<string, unknown>[]\n\t\t},\n\t\tasync countRecords(collection: string, where: Record<string, unknown>) {\n\t\t\treturn store.collection(collection).where(where).count()\n\t\t},\n\t}\n}\n\nfunction createReferentialMergeContext(store: Store): ReferentialMergeContext {\n\treturn {\n\t\tasync queryRecords(collection: string, where: Record<string, unknown>) {\n\t\t\tconst rows = await store.collection(collection).where(where).exec()\n\t\t\treturn rows as Record<string, unknown>[]\n\t\t},\n\t\tasync recordExists(collection: string, recordId: string) {\n\t\t\tconst row = await store.collection(collection).findById(recordId)\n\t\t\treturn row !== null\n\t\t},\n\t}\n}\n\nasync function applySideEffectOps(\n\tstore: Store,\n\tsideEffects: SideEffectOp[],\n\tparentOpId: string,\n): Promise<void> {\n\tfor (const effect of sideEffects) {\n\t\tconst ctx = store.createMutationContext(effect.collection, {\n\t\t\textraCausalDeps: [parentOpId],\n\t\t})\n\t\tif (effect.type === 'delete') {\n\t\t\tawait executeDelete(ctx, effect.recordId, { skipReferentialEnforcement: true })\n\t\t} else if (effect.type === 'update' && effect.data) {\n\t\t\tawait executeUpdate(ctx, effect.recordId, effect.data)\n\t\t}\n\t}\n}\n\n/**\n * Whether applying this remote update requires the three-tier merge engine.\n *\n * Scalar fields converge under deterministic per-field LWW handled entirely in\n * the store. The merge engine is only needed when a changed field is a CRDT\n * (richtext or add-wins-set array), when the collection declares constraints, or\n * when a changed field has a custom resolver.\n */\nfunction updateNeedsMergeEngine(op: Operation, collectionDef: CollectionDefinition): boolean {\n\tif (collectionDef.constraints.length > 0) {\n\t\treturn true\n\t}\n\t// Intent-preserving atomic ops (increment/max/min) COMPOSE with a concurrent\n\t// local atomic op instead of last-write-wins — plain per-field LWW would\n\t// silently drop one side's delta (a lost update).\n\tif (op.atomicOps && Object.keys(op.atomicOps).length > 0) {\n\t\treturn true\n\t}\n\tfor (const field of Object.keys(op.data ?? {})) {\n\t\tconst kind = collectionDef.fields[field]?.kind\n\t\t// CRDT kinds converge key/element-wise via the merge engine, not by\n\t\t// whole-value LWW: richtext (Yjs), arrays (add-wins set), and structured\n\t\t// object/json (recursive LWW-map). Plain per-field LWW would clobber a\n\t\t// concurrent edit to a different key of the same object.\n\t\tif (kind === 'richtext' || kind === 'array' || kind === 'object' || kind === 'json') {\n\t\t\treturn true\n\t\t}\n\t\tif (collectionDef.resolvers[field]) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunction buildLocalDiff(\n\tbaseState: Record<string, unknown>,\n\tcurrentRecord: Record<string, unknown>,\n\tfields: string[],\n): Record<string, unknown> {\n\tconst diff: Record<string, unknown> = {}\n\tfor (const field of fields) {\n\t\tdiff[field] = currentRecord[field]\n\t}\n\treturn diff\n}\n\nfunction resolveLocalTimestamp(\n\tlatestLocal: Operation | null,\n\tcurrentRecord: CollectionRecord,\n\tnodeId: string,\n): HLCTimestamp {\n\tif (latestLocal) {\n\t\treturn latestLocal.timestamp\n\t}\n\tconst updatedAt = currentRecord.updatedAt\n\tif (typeof updatedAt === 'number') {\n\t\treturn { wallTime: updatedAt, logical: 0, nodeId }\n\t}\n\treturn { wallTime: Date.now(), logical: 0, nodeId }\n}\n\nfunction maxTimestamp(a: HLCTimestamp, b: HLCTimestamp): HLCTimestamp {\n\treturn HybridLogicalClock.compare(a, b) >= 0 ? a : b\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n\tif (a === b) return true\n\tif (a === null || b === null) return false\n\tif (a === undefined || b === undefined) return false\n\tif (typeof a !== typeof b) return false\n\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\tif (a.length !== b.length) return false\n\t\treturn a.every((val, i) => deepEqual(val, b[i]))\n\t}\n\n\tif (typeof a === 'object' && typeof b === 'object') {\n\t\tconst keysA = Object.keys(a as Record<string, unknown>)\n\t\tconst keysB = Object.keys(b as Record<string, unknown>)\n\t\tif (keysA.length !== keysB.length) return false\n\t\treturn keysA.every((key) =>\n\t\t\tdeepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n\t\t)\n\t}\n\n\treturn false\n}\n","import { createOperation } from '@korajs/core'\nimport type { SideEffectOp } from '@korajs/merge'\nimport type { TransactionBufferedEntry } from '@korajs/store'\nimport type { LocalMutationContext } from '@korajs/store/internal'\nimport {\n\tbuildInsertQuery,\n\tbuildSoftDeleteQuery,\n\tbuildUpdateQuery,\n\tserializeOperation,\n\tserializeRecord,\n\tserializeRowVersion,\n} from '@korajs/store/internal'\n\n/**\n * Materialize a merge-package referential side effect as an operation log entry + SQL commands.\n */\nexport async function buildSideEffectEntry(\n\tctx: LocalMutationContext,\n\teffect: SideEffectOp,\n\tparentOpId: string,\n\ttransactionId?: string,\n\tmutationName?: string,\n): Promise<TransactionBufferedEntry> {\n\tconst causalDeps = [parentOpId]\n\tconst baseInput = {\n\t\tnodeId: ctx.nodeId,\n\t\tcollection: effect.collection,\n\t\trecordId: effect.recordId,\n\t\tsequenceNumber: await ctx.allocateSequenceNumber(),\n\t\tcausalDeps,\n\t\tschemaVersion: ctx.schema.version,\n\t\t...(transactionId !== undefined ? { transactionId } : {}),\n\t\t...(mutationName !== undefined ? { mutationName } : {}),\n\t}\n\n\tif (effect.type === 'delete') {\n\t\tconst operation = await createOperation(\n\t\t\t{\n\t\t\t\t...baseInput,\n\t\t\t\ttype: 'delete',\n\t\t\t\tdata: null,\n\t\t\t\tpreviousData: effect.previousData,\n\t\t\t},\n\t\t\tctx.clock,\n\t\t)\n\t\tctx.causalTracker?.afterOperation(effect.collection, operation.id, ctx.inTransaction)\n\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\tconst deleteQuery = buildSoftDeleteQuery(\n\t\t\teffect.collection,\n\t\t\teffect.recordId,\n\t\t\toperation.timestamp.wallTime,\n\t\t\tversion,\n\t\t)\n\t\tconst opInsert = buildInsertQuery(\n\t\t\t`_kora_ops_${effect.collection}`,\n\t\t\tserializeOperation(operation) as unknown as Record<string, unknown>,\n\t\t)\n\n\t\treturn {\n\t\t\toperation,\n\t\t\tcollection: effect.collection,\n\t\t\tcommands: [\n\t\t\t\t{ sql: deleteQuery.sql, params: deleteQuery.params },\n\t\t\t\t{ sql: opInsert.sql, params: opInsert.params },\n\t\t\t\t{\n\t\t\t\t\tsql: 'INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)',\n\t\t\t\t\tparams: [ctx.nodeId, operation.sequenceNumber],\n\t\t\t\t},\n\t\t\t],\n\t\t}\n\t}\n\n\tconst operation = await createOperation(\n\t\t{\n\t\t\t...baseInput,\n\t\t\ttype: 'update',\n\t\t\tdata: effect.data,\n\t\t\tpreviousData: effect.previousData,\n\t\t},\n\t\tctx.clock,\n\t)\n\tctx.causalTracker?.afterOperation(effect.collection, operation.id, ctx.inTransaction)\n\n\tconst definition = ctx.schema.collections[effect.collection]\n\tconst serializedChanges =\n\t\teffect.data && definition\n\t\t\t? serializeRecord(effect.data, definition.fields)\n\t\t\t: (effect.data ?? {})\n\tconst version = serializeRowVersion(operation.timestamp)\n\tconst updateQuery = buildUpdateQuery(effect.collection, effect.recordId, {\n\t\t...serializedChanges,\n\t\t_updated_at: operation.timestamp.wallTime,\n\t\t_version: version,\n\t})\n\tconst opInsert = buildInsertQuery(\n\t\t`_kora_ops_${effect.collection}`,\n\t\tserializeOperation(operation) as unknown as Record<string, unknown>,\n\t)\n\n\treturn {\n\t\toperation,\n\t\tcollection: effect.collection,\n\t\tcommands: [\n\t\t\t{ sql: updateQuery.sql, params: updateQuery.params },\n\t\t\t{ sql: opInsert.sql, params: opInsert.params },\n\t\t\t{\n\t\t\t\tsql: 'INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)',\n\t\t\t\tparams: [ctx.nodeId, operation.sequenceNumber],\n\t\t\t},\n\t\t],\n\t}\n}\n","import type { KoraEvent, KoraEventEmitter } from '@korajs/core'\nimport type { Store } from '@korajs/store'\nimport { persistedAuditTraceFromEvent } from '@korajs/store'\n\nconst AUDIT_EVENT_TYPES = ['merge:completed', 'merge:conflict', 'constraint:violated'] as const\n\ntype AuditEventType = (typeof AUDIT_EVENT_TYPES)[number]\n\nfunction isAuditEvent(event: KoraEvent): event is Extract<KoraEvent, { type: AuditEventType }> {\n\treturn (AUDIT_EVENT_TYPES as readonly string[]).includes(event.type)\n}\n\n/**\n * Persist merge and constraint traces from the event bus to `_kora_audit_traces`.\n */\nexport function wireAuditPersistence(store: Store, emitter: KoraEventEmitter): () => void {\n\tconst unsubscribers = AUDIT_EVENT_TYPES.map((eventType) =>\n\t\temitter.on(eventType, (event) => {\n\t\t\tif (!isAuditEvent(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst trace = persistedAuditTraceFromEvent(event)\n\t\t\tvoid store.appendAuditTrace(trace).catch(() => {\n\t\t\t\t// Audit persistence must not break mutations or sync.\n\t\t\t})\n\t\t}),\n\t)\n\n\treturn () => {\n\t\tfor (const unsub of unsubscribers) {\n\t\t\tunsub()\n\t\t}\n\t}\n}\n","import { type BlobRef, type KoraEventEmitter, isBlobRef } from '@korajs/core'\nimport { type ContentAddressedBlobStore, parseBlobManifest } from '@korajs/store'\nimport type { SyncEngine } from '@korajs/sync'\n\n/** Collect the blob references (with a manifest) carried by an operation's data. */\nfunction blobRefsInData(data: Record<string, unknown> | null): BlobRef[] {\n\tif (!data) {\n\t\treturn []\n\t}\n\tconst refs: BlobRef[] = []\n\tfor (const value of Object.values(data)) {\n\t\tif (isBlobRef(value) && value.manifestHash !== undefined) {\n\t\t\trefs.push(value)\n\t\t}\n\t}\n\treturn refs\n}\n\n/**\n * Upload a single blob (its manifest and every chunk) to the server so the bytes\n * remain available after this device disconnects. Reads the already-staged bytes\n * from the local blob store; skips anything the store does not hold.\n */\nasync function uploadBlob(\n\tref: BlobRef,\n\tsyncEngine: SyncEngine,\n\tblobStore: ContentAddressedBlobStore,\n): Promise<void> {\n\tconst manifestHash = ref.manifestHash\n\tif (manifestHash === undefined) {\n\t\treturn\n\t}\n\tconst manifestBytes = await blobStore.get(manifestHash)\n\tif (manifestBytes === null) {\n\t\treturn\n\t}\n\t// Upload chunks first so the manifest a peer resolves is never dangling.\n\tconst manifest = parseBlobManifest(manifestBytes)\n\tconst seen = new Set<string>()\n\tfor (const chunkHash of manifest.chunkHashes) {\n\t\tif (seen.has(chunkHash)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(chunkHash)\n\t\tconst bytes = await blobStore.get(chunkHash)\n\t\tif (bytes !== null) {\n\t\t\tsyncEngine.uploadBlobChunk(chunkHash, bytes)\n\t\t}\n\t}\n\tsyncEngine.uploadBlobChunk(manifestHash, manifestBytes)\n}\n\n/**\n * Automatically upload the bytes behind `blob` fields to the server as their\n * operations are synced. Triggered on `sync:sent`, so a blob authored offline is\n * uploaded when its operation is finally pushed on reconnect — the same event\n * that makes the reference visible to other devices. Each blob is uploaded once\n * per session (deduplicated by manifest hash).\n *\n * A no-op unless the connected server advertised central blob storage.\n *\n * @returns An unsubscribe function.\n */\nexport function wireBlobUpload(\n\temitter: KoraEventEmitter,\n\tsyncEngine: SyncEngine,\n\tblobStore: ContentAddressedBlobStore,\n): () => void {\n\tconst uploaded = new Set<string>()\n\treturn emitter.on('sync:sent', (event) => {\n\t\tif (!syncEngine.isBlobStorageEnabled()) {\n\t\t\treturn\n\t\t}\n\t\tfor (const op of event.operations) {\n\t\t\tfor (const ref of blobRefsInData(op.data)) {\n\t\t\t\tconst manifestHash = ref.manifestHash\n\t\t\t\tif (manifestHash === undefined || uploaded.has(manifestHash)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tuploaded.add(manifestHash)\n\t\t\t\tvoid uploadBlob(ref, syncEngine, blobStore).catch(() => {\n\t\t\t\t\t// Upload is best-effort; a failure just means the blob is served\n\t\t\t\t\t// peer-to-peer until a later successful upload. Allow a retry.\n\t\t\t\t\tuploaded.delete(manifestHash)\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t})\n}\n","import type { ChunkMessage, ChunkMessagePort } from '@korajs/store'\nimport type { SyncEngine } from '@korajs/sync'\n\n/**\n * Bind the store's transport-agnostic {@link ChunkMessagePort} to a live sync\n * connection. The returned port carries blob chunk requests and responses over\n * the same WebSocket the sync engine already owns, so blob bytes transfer out of\n * band from the operation stream without a second connection.\n *\n * Pair it with `@korajs/store`'s `createRemoteChunkProvider(port)` to pull a\n * blob's chunks from peers (or the server), and `serveBlobChunks(port, store)`\n * to answer requests for chunks this device holds. The sync engine's blob-chunk\n * channel handles the base64 wire encoding; this adapter only maps message\n * shapes, which are identical on both sides.\n *\n * @param syncEngine - The client's sync engine (from `app.getSyncEngine()`)\n * @returns A `ChunkMessagePort` over the sync connection\n *\n * @example\n * ```typescript\n * const port = createSyncEngineChunkPort(app.getSyncEngine())\n * const provider = createRemoteChunkProvider(port)\n * const result = await receiveBlob(manifest, provider, { chunkStore, blobStore })\n * ```\n */\nexport function createSyncEngineChunkPort(syncEngine: SyncEngine): ChunkMessagePort {\n\tconst channel = syncEngine.getBlobChunkChannel()\n\treturn {\n\t\tsend(message: ChunkMessage): void {\n\t\t\t// ChunkMessage and the channel's decoded message shape are identical\n\t\t\t// (request: {requestId, hash}; response: {requestId, bytes}).\n\t\t\tchannel.send(message)\n\t\t},\n\t\tonMessage(handler: (message: ChunkMessage) => void): void {\n\t\t\tchannel.onMessage((decoded) => {\n\t\t\t\t// The store's ChunkMessagePort speaks only request/response. Uploads\n\t\t\t\t// (blob-chunk-push) are a separate server-ingest concern, not routed here.\n\t\t\t\tif (decoded.type === 'blob-chunk-request' || decoded.type === 'blob-chunk-response') {\n\t\t\t\t\thandler(decoded)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t}\n}\n","import type { KoraEventEmitter, Operation, VersionVector } from '@korajs/core'\nimport type { MergeEngine } from '@korajs/merge'\nimport type { Store } from '@korajs/store'\nimport type { ApplyResult, SyncStore } from '@korajs/sync'\nimport { ApplyPipeline } from './apply-pipeline'\n\nexport interface MergeAwareSyncStoreOptions {\n\t/** Increments SyncEngine conflict counter when merge runs on a conflicting update. */\n\tonMergeConflict?: () => void\n}\n\n/**\n * Wraps a Store to route remote sync operations through {@link ApplyPipeline}.\n *\n * Ensures remote deletes honor referential integrity (cascade, set-null, restrict)\n * and remote updates use the full three-tier merge engine with constraint context.\n */\nexport class MergeAwareSyncStore implements SyncStore {\n\tprivate readonly pipeline: ApplyPipeline\n\n\tconstructor(\n\t\tprivate readonly store: Store,\n\t\tmergeEngine: MergeEngine,\n\t\temitter: KoraEventEmitter | null,\n\t\toptions?: MergeAwareSyncStoreOptions,\n\t) {\n\t\tthis.pipeline = new ApplyPipeline({\n\t\t\tstore,\n\t\t\tmergeEngine,\n\t\t\temitter,\n\t\t\tonMergeConflict: options?.onMergeConflict,\n\t\t})\n\t}\n\n\tgetVersionVector(): VersionVector {\n\t\treturn this.store.getVersionVector()\n\t}\n\n\tgetNodeId(): string {\n\t\treturn this.store.getNodeId()\n\t}\n\n\tasync getOperationRange(nodeId: string, fromSeq: number, toSeq: number): Promise<Operation[]> {\n\t\treturn this.store.getOperationRange(nodeId, fromSeq, toSeq)\n\t}\n\n\t/**\n\t * Read a record's current field values (including a soft-deleted row) so the sync\n\t * engine can backfill scope / query-subset fields a partial update or delete does\n\t * not restate, and thus never wrongly drop an in-scope edit from sync.\n\t */\n\tasync readRecordFields(\n\t\tcollection: string,\n\t\trecordId: string,\n\t): Promise<Record<string, unknown> | null> {\n\t\tconst snapshot = await this.store.findMaterializedRow(collection, recordId)\n\t\treturn snapshot ? snapshot.record : null\n\t}\n\n\tasync applyRemoteOperation(op: Operation): Promise<ApplyResult> {\n\t\treturn this.pipeline.applyRemote(op)\n\t}\n\n\t/**\n\t * Delegates timestamp rebase to the store so the sync engine can re-stamp\n\t * never-acknowledged operations after a fast device clock is corrected.\n\t */\n\tasync rebaseUnsyncedOperations(\n\t\tids: string[],\n\t\tcorrectedNowMs: number,\n\t): Promise<{ operations: Operation[]; idMapping: Record<string, string>; rebasedCount: number }> {\n\t\treturn this.store.rebaseUnsyncedOperations(ids, correctedNowMs)\n\t}\n}\n","import type { Operation } from '@korajs/core'\nimport type { StorageAdapter } from '@korajs/store'\nimport { deserializeOperation, serializeOperation } from '@korajs/store/internal'\nimport type { OperationRow } from '@korajs/store/internal'\nimport type { QueueStorage } from '@korajs/sync'\n\ninterface SyncQueueRow {\n\tid: string\n\tpayload: string\n}\n\ntype QueuePayload = OperationRow & { _collection: string }\n\n/**\n * Persists the outbound sync queue in `_kora_sync_queue` via the local StorageAdapter.\n */\nexport class StoreQueueStorage implements QueueStorage {\n\tconstructor(private readonly adapter: StorageAdapter) {}\n\n\tasync load(): Promise<Operation[]> {\n\t\tconst rows = await this.adapter.query<SyncQueueRow>(\n\t\t\t'SELECT id, payload FROM _kora_sync_queue ORDER BY rowid ASC',\n\t\t)\n\t\treturn rows.map((row) => operationFromQueuePayload(row.payload))\n\t}\n\n\tasync enqueue(op: Operation): Promise<void> {\n\t\tconst row = serializeOperation(op)\n\t\tconst payload: QueuePayload = { ...row, _collection: op.collection }\n\t\tawait this.adapter.execute(\n\t\t\t'INSERT OR REPLACE INTO _kora_sync_queue (id, payload) VALUES (?, ?)',\n\t\t\t[op.id, JSON.stringify(payload)],\n\t\t)\n\t}\n\n\tasync dequeue(ids: string[]): Promise<void> {\n\t\tif (ids.length === 0) return\n\t\tconst placeholders = ids.map(() => '?').join(', ')\n\t\tawait this.adapter.execute(`DELETE FROM _kora_sync_queue WHERE id IN (${placeholders})`, ids)\n\t}\n\n\tasync count(): Promise<number> {\n\t\tconst rows = await this.adapter.query<{ cnt: number }>(\n\t\t\t'SELECT COUNT(*) as cnt FROM _kora_sync_queue',\n\t\t)\n\t\treturn rows[0]?.cnt ?? 0\n\t}\n}\n\nfunction operationFromQueuePayload(payload: string): Operation {\n\tconst parsed = JSON.parse(payload) as QueuePayload\n\tconst op = deserializeOperation(parsed)\n\treturn {\n\t\t...op,\n\t\tcollection: parsed._collection,\n\t}\n}\n","import type { Operation, VersionVector } from '@korajs/core'\nimport { mergeVersionVectors } from '@korajs/store'\nimport type { Store } from '@korajs/store'\nimport { decodeDeltaCursor, encodeDeltaCursor, operationMatchesScope } from '@korajs/sync'\nimport type { DeltaCursor, SyncScopeMap, SyncStatePersistence } from '@korajs/sync'\n\n/**\n * Persists and queries sync acknowledgment state via the local Store.\n */\nexport class StoreSyncStatePersistence implements SyncStatePersistence {\n\tconstructor(\n\t\tprivate readonly store: Store,\n\t\tprivate readonly scope?: SyncScopeMap,\n\t) {}\n\n\tloadLastAckedServerVector(): Promise<VersionVector> {\n\t\treturn this.store.loadLastAckedServerVector()\n\t}\n\n\tsaveLastAckedServerVector(vector: VersionVector): Promise<void> {\n\t\treturn this.store.saveLastAckedServerVector(vector)\n\t}\n\n\tmergeServerVectors(a: VersionVector, b: VersionVector): VersionVector {\n\t\treturn this.store.mergeServerVectors(a, b)\n\t}\n\n\tasync countUnsyncedOperations(serverVector: VersionVector): Promise<number> {\n\t\tconst ops = await this.getUnsyncedOperations(serverVector)\n\t\treturn ops.length\n\t}\n\n\tasync getUnsyncedOperations(serverVector: VersionVector): Promise<Operation[]> {\n\t\tconst ops = await this.store.getUnsyncedOperations(serverVector)\n\t\treturn ops.filter((op) => operationMatchesScope(op, this.scope))\n\t}\n\n\tloadDeltaCursor(): Promise<DeltaCursor | null> {\n\t\treturn this.store.loadDeltaCursor().then((encoded) => decodeDeltaCursor(encoded))\n\t}\n\n\tasync saveDeltaCursor(cursor: DeltaCursor | null): Promise<void> {\n\t\tawait this.store.saveDeltaCursor(cursor ? encodeDeltaCursor(cursor) : null)\n\t}\n\n\tloadDeliveryWatermark(signature: string): Promise<number> {\n\t\treturn this.store.loadDeliveryWatermark(signature)\n\t}\n\n\tasync saveDeliveryWatermark(signature: string, watermark: number): Promise<void> {\n\t\tawait this.store.saveDeliveryWatermark(signature, watermark)\n\t}\n\n\tloadAllDeliveryWatermarks(): Promise<Record<string, number>> {\n\t\treturn this.store.loadAllDeliveryWatermarks()\n\t}\n\n\tasync deleteDeliveryWatermark(signature: string): Promise<void> {\n\t\tawait this.store.deleteDeliveryWatermark(signature)\n\t}\n}\n"],"mappings":";AAOA;AAAA,EACC;AAAA,EACA;AAAA,EACA,mBAAAA;AAAA,EACA;AAAA,OACM;AACP,SAAS,uBAAuB;AAEhC,SAAS,0BAA0B,yCAAyC;AAW5E,SAAS,qBAAqB,2BAA2B;AACzD;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;;;AChCP,SAAS,uBAAuB;AAIhC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AAKP,eAAsB,qBACrB,KACA,QACA,YACA,eACA,cACoC;AACpC,QAAM,aAAa,CAAC,UAAU;AAC9B,QAAM,YAAY;AAAA,IACjB,QAAQ,IAAI;AAAA,IACZ,YAAY,OAAO;AAAA,IACnB,UAAU,OAAO;AAAA,IACjB,gBAAgB,MAAM,IAAI,uBAAuB;AAAA,IACjD;AAAA,IACA,eAAe,IAAI,OAAO;AAAA,IAC1B,GAAI,kBAAkB,SAAY,EAAE,cAAc,IAAI,CAAC;AAAA,IACvD,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,EACtD;AAEA,MAAI,OAAO,SAAS,UAAU;AAC7B,UAAMC,aAAY,MAAM;AAAA,MACvB;AAAA,QACC,GAAG;AAAA,QACH,MAAM;AAAA,QACN,MAAM;AAAA,QACN,cAAc,OAAO;AAAA,MACtB;AAAA,MACA,IAAI;AAAA,IACL;AACA,QAAI,eAAe,eAAe,OAAO,YAAYA,WAAU,IAAI,IAAI,aAAa;AAEpF,UAAMC,WAAU,oBAAoBD,WAAU,SAAS;AACvD,UAAM,cAAc;AAAA,MACnB,OAAO;AAAA,MACP,OAAO;AAAA,MACPA,WAAU,UAAU;AAAA,MACpBC;AAAA,IACD;AACA,UAAMC,YAAW;AAAA,MAChB,aAAa,OAAO,UAAU;AAAA,MAC9B,mBAAmBF,UAAS;AAAA,IAC7B;AAEA,WAAO;AAAA,MACN,WAAAA;AAAA,MACA,YAAY,OAAO;AAAA,MACnB,UAAU;AAAA,QACT,EAAE,KAAK,YAAY,KAAK,QAAQ,YAAY,OAAO;AAAA,QACnD,EAAE,KAAKE,UAAS,KAAK,QAAQA,UAAS,OAAO;AAAA,QAC7C;AAAA,UACC,KAAK;AAAA,UACL,QAAQ,CAAC,IAAI,QAAQF,WAAU,cAAc;AAAA,QAC9C;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAEA,QAAM,YAAY,MAAM;AAAA,IACvB;AAAA,MACC,GAAG;AAAA,MACH,MAAM;AAAA,MACN,MAAM,OAAO;AAAA,MACb,cAAc,OAAO;AAAA,IACtB;AAAA,IACA,IAAI;AAAA,EACL;AACA,MAAI,eAAe,eAAe,OAAO,YAAY,UAAU,IAAI,IAAI,aAAa;AAEpF,QAAM,aAAa,IAAI,OAAO,YAAY,OAAO,UAAU;AAC3D,QAAM,oBACL,OAAO,QAAQ,aACZ,gBAAgB,OAAO,MAAM,WAAW,MAAM,IAC7C,OAAO,QAAQ,CAAC;AACrB,QAAM,UAAU,oBAAoB,UAAU,SAAS;AACvD,QAAM,cAAc,iBAAiB,OAAO,YAAY,OAAO,UAAU;AAAA,IACxE,GAAG;AAAA,IACH,aAAa,UAAU,UAAU;AAAA,IACjC,UAAU;AAAA,EACX,CAAC;AACD,QAAM,WAAW;AAAA,IAChB,aAAa,OAAO,UAAU;AAAA,IAC9B,mBAAmB,SAAS;AAAA,EAC7B;AAEA,SAAO;AAAA,IACN;AAAA,IACA,YAAY,OAAO;AAAA,IACnB,UAAU;AAAA,MACT,EAAE,KAAK,YAAY,KAAK,QAAQ,YAAY,OAAO;AAAA,MACnD,EAAE,KAAK,SAAS,KAAK,QAAQ,SAAS,OAAO;AAAA,MAC7C;AAAA,QACC,KAAK;AAAA,QACL,QAAQ,CAAC,IAAI,QAAQ,UAAU,cAAc;AAAA,MAC9C;AAAA,IACD;AAAA,EACD;AACD;;;AD/DA,IAAM,0BAA0B;AAwBzB,IAAM,gBAAN,MAAoD;AAAA,EAG1D,YAA6B,MAAyB;AAAzB;AAC5B,SAAK,oBAAoB,yBAAyB,KAAK,MAAM,UAAU,CAAC;AAAA,EACzE;AAAA,EAF6B;AAAA,EAFZ;AAAA;AAAA,EAOjB,MAAM,OAAO,YAAoB,MAA0D;AAC1F,WAAO,cAAc,KAAK,KAAK,MAAM,sBAAsB,UAAU,GAAG,IAAI;AAAA,EAC7E;AAAA;AAAA,EAGA,MAAM,OACL,YACA,IACA,MAC4B;AAC5B,WAAO,cAAc,KAAK,KAAK,MAAM,sBAAsB,UAAU,GAAG,IAAI,IAAI;AAAA,EACjF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,YAAoB,IAA2B;AAC3D,UAAM,MAAM,KAAK,KAAK,MAAM,sBAAsB,UAAU;AAC5D,UAAM,aAAa,kBAAkB,GAAG;AACxC,UAAM,YAAY,MAAMG;AAAA,MACvB;AAAA,QACC,QAAQ,IAAI;AAAA,QACZ,MAAM;AAAA,QACN;AAAA,QACA,UAAU;AAAA,QACV,MAAM;AAAA,QACN,cAAc;AAAA,QACd,gBAAgB,MAAM,IAAI,uBAAuB;AAAA,QACjD;AAAA,QACA,eAAe,IAAI,OAAO;AAAA,MAC3B;AAAA,MACA,IAAI;AAAA,IACL;AACA,QAAI,eAAe,eAAe,YAAY,UAAU,IAAI,IAAI,aAAa;AAE7E,UAAM,SAAS,8BAA8B,KAAK,KAAK,KAAK;AAC5D,UAAM,QAAQ,MAAM;AAAA,MACnB;AAAA,MACA,KAAK,KAAK,MAAM,UAAU;AAAA,MAC1B;AAAA,MACA,KAAK;AAAA,IACN;AAEA,eAAW,SAAS,MAAM,QAAQ;AACjC,WAAK,KAAK,SAAS,KAAK,EAAE,MAAM,kBAAkB,MAAM,CAAC;AAAA,IAC1D;AAEA,QAAI,CAAC,MAAM,SAAS;AACnB,YAAM,IAAI;AAAA,QACT,yBAAyB,EAAE,WAAW,UAAU;AAAA,QAChD;AAAA,QACA,EAAE,YAAY,UAAU,GAAG;AAAA,MAC5B;AAAA,IACD;AAEA,UAAM,cAAc,KAAK,IAAI;AAAA,MAC5B,4BAA4B;AAAA,MAC5B;AAAA,IACD,CAAC;AAED,QAAI,MAAM,cAAc,SAAS,GAAG;AACnC,YAAM,mBAAmB,KAAK,KAAK,OAAO,MAAM,eAAe,UAAU,EAAE;AAAA,IAC5E;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,OAAiE;AACxF,UAAM,QAAQ,KAAK,KAAK;AACxB,UAAM,eAAkD,CAAC;AAEzD,eAAW,SAAS,MAAM,SAAS;AAClC,UAAI,MAAM,UAAU,SAAS,UAAU;AACtC;AAAA,MACD;AACA,YAAM,SAAS,8BAA8B,KAAK;AAClD,YAAM,QAAQ,MAAM;AAAA,QACnB,MAAM;AAAA,QACN,MAAM,UAAU;AAAA,QAChB;AAAA,QACA,KAAK;AAAA,MACN;AAEA,iBAAW,SAAS,MAAM,QAAQ;AACjC,aAAK,KAAK,SAAS,KAAK,EAAE,MAAM,kBAAkB,MAAM,CAAC;AAAA,MAC1D;AAEA,UAAI,CAAC,MAAM,SAAS;AACnB,cAAM,IAAI;AAAA,UACT,yBAAyB,MAAM,UAAU,QAAQ,WAAW,MAAM,UAAU;AAAA,UAC5E;AAAA,UACA,EAAE,YAAY,MAAM,YAAY,UAAU,MAAM,UAAU,SAAS;AAAA,QACpE;AAAA,MACD;AAEA,iBAAW,UAAU,MAAM,eAAe;AACzC,cAAMC,OAAM,MAAM,sBAAsB,OAAO,YAAY,EAAE,eAAe,KAAK,CAAC;AAClF,qBAAa;AAAA,UACZ,MAAM;AAAA,YACLA;AAAA,YACA;AAAA,YACA,MAAM,UAAU;AAAA,YAChB,MAAM;AAAA,YACN,MAAM;AAAA,UACP;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAEA,UAAM,aAAa,CAAC,GAAG,MAAM,SAAS,GAAG,YAAY;AACrD,UAAM,YAAY,gBAAgB,WAAW,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC;AACpE,UAAM,iBAAiB,IAAI,IAAI,WAAW,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,IAAI,EAAE,QAAQ,CAAU,CAAC;AAE3F,UAAM,MAAM,MAAM;AAAA,MACjB,MAAM,QAAQ,CAAC,GAAG,cAAc,OAAO,KAAK,MAAM,UAAU,EAAE,WAAW,EAAE,CAAC,KAAK;AAAA,MACjF,EAAE,eAAe,KAAK;AAAA,IACvB;AAEA,UAAM,IAAI,QAAQ,YAAY,OAAO,OAAO;AAC3C,iBAAW,MAAM,WAAW;AAC3B,cAAM,WAAW,eAAe,IAAI,GAAG,EAAE;AACzC,YAAI,CAAC,UAAU;AACd;AAAA,QACD;AACA,mBAAW,OAAO,UAAU;AAC3B,gBAAM,GAAG,QAAQ,IAAI,KAAK,IAAI,MAAM;AAAA,QACrC;AAAA,MACD;AAAA,IACD,CAAC;AAED,UAAM,sBAAsB,oBAAI,IAAY;AAC5C,eAAW,SAAS,YAAY;AAC/B,0BAAoB,IAAI,MAAM,UAAU;AAAA,IACzC;AAEA,WAAO,EAAE,YAAY,WAAW,oBAAoB;AAAA,EACrD;AAAA,EAEA,MAAM,YAAY,IAAqC;AACtD,WAAO,KAAK,MAAM,IAAI,EAAE,MAAM,UAAU,QAAQ,KAAK,KAAK,MAAM,UAAU,EAAE,CAAC;AAAA,EAC9E;AAAA,EAEA,MAAM,MAAM,IAAe,SAA6C;AACvE,QAAI,QAAQ,SAAS,SAAS;AAC7B,aAAO,KAAK,KAAK,MAAM,qBAAqB,EAAE;AAAA,IAC/C;AAEA,QAAI,GAAG,SAAS,UAAU;AACzB,aAAO,KAAK,kBAAkB,EAAE;AAAA,IACjC;AAEA,QAAI,GAAG,SAAS,YAAY,GAAG,MAAM;AACpC,aAAO,KAAK,kBAAkB,EAAE;AAAA,IACjC;AAEA,QAAI,GAAG,SAAS,YAAY,GAAG,QAAQ,GAAG,cAAc;AACvD,aAAO,KAAK,kBAAkB,EAAE;AAAA,IACjC;AAEA,WAAO,KAAK,KAAK,MAAM,qBAAqB,EAAE;AAAA,EAC/C;AAAA,EAEA,MAAc,kBAAkB,IAAqC;AACpE,UAAM,UAAU,MAAM,KAAK,iCAAiC,EAAE;AAC9D,QAAI,SAAS;AACZ,aAAO;AAAA,IACR;AAEA,UAAM,SAAS,8BAA8B,KAAK,KAAK,KAAK;AAC5D,UAAM,QAAQ,MAAM;AAAA,MACnB;AAAA,MACA,KAAK,KAAK,MAAM,UAAU;AAAA,MAC1B;AAAA,MACA,KAAK;AAAA,IACN;AAEA,eAAW,SAAS,MAAM,QAAQ;AACjC,WAAK,KAAK,SAAS,KAAK,EAAE,MAAM,kBAAkB,MAAM,CAAC;AAAA,IAC1D;AAEA,QAAI,CAAC,MAAM,SAAS;AACnB,aAAO;AAAA,IACR;AAEA,UAAM,SAAS,MAAM,KAAK,KAAK,MAAM,qBAAqB,EAAE;AAC5D,QAAI,WAAW,WAAW;AACzB,aAAO;AAAA,IACR;AAEA,QAAI,MAAM,cAAc,SAAS,GAAG;AACnC,YAAM,mBAAmB,KAAK,KAAK,OAAO,MAAM,eAAe,GAAG,EAAE;AAAA,IACrE;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iCAAiC,IAAiC;AAC/E,UAAM,gBAAgB,KAAK,KAAK,MAAM,UAAU,EAAE,YAAY,GAAG,UAAU;AAC3E,QAAI,CAAC,eAAe;AACnB,aAAO;AAAA,IACR;AAEA,UAAM,UAAU,MAAM,KAAK,KAAK,MAAM;AAAA,MACrC,GAAG;AAAA,MACH,GAAG;AAAA,IACJ;AACA,QAAI,CAAC,WAAW,QAAQ,SAAS,UAAU;AAC1C,aAAO;AAAA,IACR;AAEA,UAAM,YAAY,QAAQ,gBAAgB,CAAC;AAC3C,UAAM,cAAc,MAAM,KAAK,KAAK,YAAY,MAAM;AAAA,MACrD,OAAO;AAAA,MACP,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACD,CAAC;AAED,SAAK,mBAAmB,IAAI,SAAS,WAAW;AAEhD,WAAO,YAAY,qBAAqB;AAAA,EACzC;AAAA,EAEA,MAAc,kBAAkB,IAAqC;AACpE,UAAM,SAAS,KAAK,KAAK,MAAM,UAAU;AACzC,UAAM,gBAAgB,OAAO,YAAY,GAAG,UAAU;AACtD,QAAI,CAAC,eAAe;AACnB,aAAO,KAAK,KAAK,MAAM,qBAAqB,EAAE;AAAA,IAC/C;AAQA,QAAI,YAAwC;AAC5C,aAAS,UAAU,GAAG,UAAU,yBAAyB,WAAW;AACnE,UAAI;AACH,eAAO,MAAM,KAAK,yBAAyB,IAAI,aAAa;AAAA,MAC7D,SAAS,OAAO;AACf,YAAI,EAAE,iBAAiB,sBAAsB;AAC5C,gBAAM;AAAA,QACP;AACA,oBAAY;AAAA,MACb;AAAA,IACD;AACA,UAAM,aAAa,IAAI,oBAAoB,GAAG,YAAY,GAAG,QAAQ;AAAA,EACtE;AAAA,EAEA,MAAc,yBACb,IACA,eACuB;AAMvB,UAAM,QAA0B,MAAM,KAAK,KAAK,MAAM;AAAA,MACrD,GAAG;AAAA,MACH,GAAG;AAAA,IACJ,KAAM,EAAE,SAAS,MAAM,eAAe,KAAK;AAE3C,UAAM,WAAW,KAAK,KAAK,MAAM,WAAW,GAAG,UAAU;AACzD,UAAM,gBAAgB,MAAM,SAAS,SAAS,GAAG,QAAQ;AAEzD,QAAI,CAAC,eAAe;AACnB,YAAM,kBAAkB,MAAM,KAAK,8BAA8B,IAAI,eAAe,KAAK;AACzF,UAAI,oBAAoB,MAAM;AAC7B,eAAO;AAAA,MACR;AAGA,aAAO,KAAK,KAAK,MAAM,qBAAqB,EAAE;AAAA,IAC/C;AAUA,QAAI,CAAC,uBAAuB,IAAI,aAAa,GAAG;AAK/C,YAAM,KAAK,mBAAmB,IAAI,aAAa;AAC/C,aAAO,KAAK,KAAK,MAAM,qBAAqB,EAAE;AAAA,IAC/C;AAEA,QAAI,cAAc;AAClB,eAAW,SAAS,OAAO,KAAK,GAAG,QAAQ,CAAC,CAAC,GAAG;AAC/C,YAAM,WAAW,cAAc,OAAO,KAAK;AAC3C,YAAM,eAAe,GAAG,eAAe,KAAK;AAC5C,YAAM,eAAe,cAAc,KAAK;AAExC,UAAI,UAAU,SAAS,YAAY;AAClC,YAAI,CAAC,oBAAoB,cAAc,YAAY,GAAG;AACrD,wBAAc;AAAA,QACf;AACA;AAAA,MACD;AAEA,UAAI,CAAC,UAAU,cAAc,YAAY,GAAG;AAC3C,sBAAc;AACd;AAAA,MACD;AAAA,IACD;AAEA,QAAI,CAAC,aAAa;AAIjB,aAAO,KAAK,KAAK,MAAM,qBAAqB,IAAI;AAAA,QAC/C,kBAAkB;AAAA,QAClB,eAAe;AAAA,MAChB,CAAC;AAAA,IACF;AAEA,WAAO,KAAK,kBAAkB,IAAI,eAAe,eAAe,GAAG,gBAAgB,CAAC,GAAG,KAAK;AAAA,EAC7F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAc,mBACb,IACA,eACgB;AAChB,UAAM,UAAU,MAAM,KAAK,KAAK,MAAM;AAAA,MACrC,GAAG;AAAA,MACH,GAAG;AAAA,IACJ;AACA,QAAI,CAAC,WAAW,CAAC,QAAQ,MAAM;AAC9B;AAAA,IACD;AACA,UAAM,eAAe,OAAO,KAAK,GAAG,QAAQ,CAAC,CAAC;AAC9C,UAAM,cAAc,aAAa,KAAK,CAAC,UAAU,QAAQ,OAAO,KAAK,MAAM,MAAS;AACpF,QAAI,CAAC,aAAa;AACjB;AAAA,IACD;AAEA,UAAM,cAAc,MAAM,KAAK,KAAK,YAAY,MAAM;AAAA,MACrD,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,WAAW,GAAG,gBAAgB,CAAC;AAAA,MAC/B;AAAA,IACD,CAAC;AACD,SAAK,mBAAmB,IAAI,SAAS,WAAW;AAAA,EACjD;AAAA,EAEA,MAAc,8BACb,IACA,eACA,OAC8B;AAC9B,UAAM,WAAW,MAAM,KAAK,KAAK,MAAM,oBAAoB,GAAG,YAAY,GAAG,QAAQ;AACrF,QAAI,CAAC,UAAU,SAAS;AAEvB,aAAO;AAAA,IACR;AAWA,UAAM,WAAW,MAAM,KAAK,KAAK,MAAM,uBAAuB,GAAG,YAAY,GAAG,QAAQ;AACxF,UAAM,MAAM,CAAC,GAAG,UAAU,EAAE,EAAE;AAAA,MAAK,CAAC,GAAG,MACtC,mBAAmB,QAAQ,EAAE,WAAW,EAAE,SAAS;AAAA,IACpD;AACA,UAAM,SAAS,0BAA0B,GAAG;AAE5C,QAAI,CAAC,QAAQ;AAIZ,aAAO,KAAK,KAAK,MAAM,qBAAqB,IAAI,EAAE,SAAS,KAAK,CAAC;AAAA,IAClE;AAKA,UAAM,kBAAmB,IAAI,IAAI,SAAS,CAAC,EAAgB;AAC3D,WAAO,KAAK,KAAK,MAAM,qBAAqB,IAAI;AAAA,MAC/C,qBAAqB;AAAA,MACrB,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,sBAAsB,aAAa,GAAG,WAAW,eAAe;AAAA,MAChE,eAAe;AAAA,IAChB,CAAC;AAAA,EACF;AAAA,EAEA,MAAc,kBACb,IACA,eACA,eACA,WACA,OACA,cACuB;AACvB,UAAM,cAAc,MAAM,KAAK,KAAK,MAAM;AAAA,MACzC,GAAG;AAAA,MACH,GAAG;AAAA,IACJ;AACA,UAAM,iBAAiB;AAAA,MACtB;AAAA,MACA;AAAA,MACA,KAAK,KAAK,MAAM,UAAU;AAAA,IAC3B;AAKA,UAAM,EAAE,WAAW,kBAAkB,GAAG,gBAAgB,IAAI;AAC5D,UAAM,UAAqB;AAAA,MAC1B,GAAG;AAAA,MACH,MAAM,eAAe,WAAW,eAAe,OAAO,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC;AAAA,MACzE,cAAc,GAAG;AAAA,MACjB,QAAQ,KAAK,KAAK,MAAM,UAAU;AAAA,MAClC,WAAW;AAAA,MACX,GAAI,aAAa,cAAc,SAAY,EAAE,WAAW,YAAY,UAAU,IAAI,CAAC;AAAA,IACpF;AAEA,UAAM,oBAAoB,wBAAwB,KAAK,KAAK,KAAK;AACjE,UAAM,cAAc,MAAM,KAAK,KAAK,YAAY;AAAA,MAC/C;AAAA,QACC,OAAO;AAAA,QACP,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,MACD;AAAA,MACA;AAAA,IACD;AAWA,UAAM,aAAa,MAAM,KAAK,+BAA+B,IAAI,YAAY,UAAU;AASvF,UAAM,cAAc,MAAM,KAAK,KAAK,MAAM,qBAAqB,IAAI;AAAA,MAClE,GAAG;AAAA,MACH,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,sBAAsB,aAAa,GAAG,WAAW,cAAc;AAAA,MAC/D,eAAe;AAAA,IAChB,CAAC;AAID,SAAK,mBAAmB,IAAI,SAAS,WAAW;AAEhD,QAAI,gBAAgB,aAAa,YAAY,YAAY,SAAS,GAAG;AACpE,YAAM,mBAAmB,KAAK,KAAK,OAAO,YAAY,aAAa,GAAG,EAAE;AAAA,IACzE;AAEA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,+BACb,IACA,YACmC;AACnC,QAAI,CAAC,GAAG,aAAa,OAAO,KAAK,GAAG,SAAS,EAAE,WAAW,GAAG;AAC5D,aAAO;AAAA,IACR;AACA,UAAM,WAAW,MAAM,KAAK,KAAK,MAAM,uBAAuB,GAAG,YAAY,GAAG,QAAQ;AAExF,UAAM,MAAM,CAAC,GAAG,UAAU,EAAE,EAAE;AAAA,MAAK,CAAC,GAAG,MACtC,mBAAmB,QAAQ,EAAE,WAAW,EAAE,SAAS;AAAA,IACpD;AACA,UAAM,SAAS,0BAA0B,GAAG;AAC5C,QAAI,CAAC,QAAQ;AACZ,aAAO;AAAA,IACR;AACA,UAAM,SAAS,EAAE,GAAG,WAAW;AAC/B,eAAW,SAAS,OAAO,KAAK,GAAG,SAAS,GAAG;AAC9C,UAAI,SAAS,QAAQ;AACpB,eAAO,KAAK,IAAI,OAAO,KAAK;AAAA,MAC7B;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA,EAEA,MAAc,kBAAkB,IAAqC;AACpE,UAAM,gBAAgB,KAAK,KAAK,MAAM,UAAU,EAAE,YAAY,GAAG,UAAU;AAC3E,QAAI,CAAC,iBAAiB,CAAC,GAAG,MAAM;AAC/B,aAAO,KAAK,KAAK,MAAM,qBAAqB,EAAE;AAAA,IAC/C;AAEA,QAAI,YAAwC;AAC5C,aAAS,UAAU,GAAG,UAAU,yBAAyB,WAAW;AACnE,UAAI;AACH,eAAO,MAAM,KAAK,yBAAyB,IAAI,aAAa;AAAA,MAC7D,SAAS,OAAO;AACf,YAAI,EAAE,iBAAiB,sBAAsB;AAC5C,gBAAM;AAAA,QACP;AACA,oBAAY;AAAA,MACb;AAAA,IACD;AACA,UAAM,aAAa,IAAI,oBAAoB,GAAG,YAAY,GAAG,QAAQ;AAAA,EACtE;AAAA,EAEA,MAAc,yBACb,IACA,eACuB;AAEvB,UAAM,QAA0B,MAAM,KAAK,KAAK,MAAM;AAAA,MACrD,GAAG;AAAA,MACH,GAAG;AAAA,IACJ,KAAM,EAAE,SAAS,MAAM,eAAe,KAAK;AAE3C,UAAM,WAAW,MAAM,KAAK,KAAK,MAAM,oBAAoB,GAAG,YAAY,GAAG,QAAQ;AACrF,QAAI,CAAC,YAAY,SAAS,SAAS;AAGlC,aAAO,KAAK,KAAK,MAAM,qBAAqB,IAAI,EAAE,eAAe,MAAM,CAAC;AAAA,IACzE;AAEA,UAAM,UAAU,MAAM;AAAA,MACrB,KAAK,KAAK;AAAA,MACV,GAAG;AAAA,MACH,GAAG;AAAA,MACH,SAAS;AAAA,IACV;AAEA,UAAM,cAAc,MAAM,KAAK,KAAK,YAAY,MAAM;AAAA,MACrD,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,WAAW,CAAC;AAAA,MACZ;AAAA,IACD,CAAC;AAKD,UAAM,cAAc,MAAM,KAAK,KAAK,MAAM,qBAAqB,IAAI;AAAA,MAClE,kBAAkB;AAAA,MAClB,iBAAiB,YAAY;AAAA,MAC7B,sBAAsB,aAAa,GAAG,WAAW,QAAQ,SAAS;AAAA,MAClE,eAAe;AAAA,IAChB,CAAC;AAED,SAAK,mBAAmB,IAAI,SAAS,WAAW;AAEhD,WAAO;AAAA,EACR;AAAA,EAEQ,mBAAmB,QAAmB,OAAkB,aAAgC;AAC/F,SAAK,KAAK,SAAS,KAAK;AAAA,MACvB,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,YAAY;AAAA,IACb,CAAC;AAED,UAAM,mBAAmB,YAAY,OAAO;AAAA,MAC3C,CAAC,MACA,EAAE,aAAa,uBACf,EAAE,aAAa,wBACf,EAAE,aAAa;AAAA,IACjB;AACA,QAAI,kBAAkB;AACrB,WAAK,KAAK,kBAAkB;AAAA,IAC7B;AAEA,eAAW,SAAS,YAAY,QAAQ;AACvC,UACC,MAAM,aAAa,uBACnB,MAAM,aAAa,wBACnB,MAAM,aAAa,yBAClB;AACD,aAAK,KAAK,SAAS,KAAK,EAAE,MAAM,kBAAkB,MAAM,CAAC;AAAA,MAC1D;AAAA,IACD;AACA,UAAM,aAAa,YAAY,OAAO,CAAC;AACvC,QAAI,YAAY;AACf,WAAK,KAAK,SAAS,KAAK,EAAE,MAAM,mBAAmB,OAAO,WAAW,CAAC;AAAA,IACvE;AAAA,EACD;AACD;AAKA,eAAe,wBACd,OACA,YACA,UACA,QACqB;AACrB,QAAM,cAAc,MAAM,MAAM,iCAAiC,YAAY,QAAQ;AACrF,MAAI,aAAa;AAChB,WAAO;AAAA,EACR;AAEA,QAAM,YAAY,MAAM,MAAM,4BAA4B,YAAY,QAAQ;AAC9E,MAAI,WAAW;AACd,WAAO;AAAA,EACR;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,MAAM,UAAU;AAAA,IAChB,MAAM,UAAU,EAAE;AAAA,EACnB;AACD;AAEA,SAAS,4BACR,QACA,YACA,QACA,eACY;AACZ,QAAM,EAAE,IAAI,WAAW,WAAW,GAAG,OAAO,IAAI;AAChD,SAAO;AAAA,IACN,IAAI,mBAAmB,EAAE;AAAA,IACzB;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,IACd,WAAW,EAAE,UAAU,WAAW,SAAS,GAAG,OAAO;AAAA,IACrD,gBAAgB;AAAA,IAChB,YAAY,CAAC;AAAA,IACb;AAAA,EACD;AACD;AAEA,SAAS,wBAAwB,OAAiC;AACjE,SAAO;AAAA,IACN,MAAM,aAAa,YAAoB,OAAgC;AACtE,YAAM,OAAO,MAAM,MAAM,WAAW,UAAU,EAAE,MAAM,KAAK,EAAE,KAAK;AAClE,aAAO;AAAA,IACR;AAAA,IACA,MAAM,aAAa,YAAoB,OAAgC;AACtE,aAAO,MAAM,WAAW,UAAU,EAAE,MAAM,KAAK,EAAE,MAAM;AAAA,IACxD;AAAA,EACD;AACD;AAEA,SAAS,8BAA8B,OAAuC;AAC7E,SAAO;AAAA,IACN,MAAM,aAAa,YAAoB,OAAgC;AACtE,YAAM,OAAO,MAAM,MAAM,WAAW,UAAU,EAAE,MAAM,KAAK,EAAE,KAAK;AAClE,aAAO;AAAA,IACR;AAAA,IACA,MAAM,aAAa,YAAoB,UAAkB;AACxD,YAAM,MAAM,MAAM,MAAM,WAAW,UAAU,EAAE,SAAS,QAAQ;AAChE,aAAO,QAAQ;AAAA,IAChB;AAAA,EACD;AACD;AAEA,eAAe,mBACd,OACA,aACA,YACgB;AAChB,aAAW,UAAU,aAAa;AACjC,UAAM,MAAM,MAAM,sBAAsB,OAAO,YAAY;AAAA,MAC1D,iBAAiB,CAAC,UAAU;AAAA,IAC7B,CAAC;AACD,QAAI,OAAO,SAAS,UAAU;AAC7B,YAAM,cAAc,KAAK,OAAO,UAAU,EAAE,4BAA4B,KAAK,CAAC;AAAA,IAC/E,WAAW,OAAO,SAAS,YAAY,OAAO,MAAM;AACnD,YAAM,cAAc,KAAK,OAAO,UAAU,OAAO,IAAI;AAAA,IACtD;AAAA,EACD;AACD;AAUA,SAAS,uBAAuB,IAAe,eAA8C;AAC5F,MAAI,cAAc,YAAY,SAAS,GAAG;AACzC,WAAO;AAAA,EACR;AAIA,MAAI,GAAG,aAAa,OAAO,KAAK,GAAG,SAAS,EAAE,SAAS,GAAG;AACzD,WAAO;AAAA,EACR;AACA,aAAW,SAAS,OAAO,KAAK,GAAG,QAAQ,CAAC,CAAC,GAAG;AAC/C,UAAM,OAAO,cAAc,OAAO,KAAK,GAAG;AAK1C,QAAI,SAAS,cAAc,SAAS,WAAW,SAAS,YAAY,SAAS,QAAQ;AACpF,aAAO;AAAA,IACR;AACA,QAAI,cAAc,UAAU,KAAK,GAAG;AACnC,aAAO;AAAA,IACR;AAAA,EACD;AACA,SAAO;AACR;AAEA,SAAS,eACR,WACA,eACA,QAC0B;AAC1B,QAAM,OAAgC,CAAC;AACvC,aAAW,SAAS,QAAQ;AAC3B,SAAK,KAAK,IAAI,cAAc,KAAK;AAAA,EAClC;AACA,SAAO;AACR;AAEA,SAAS,sBACR,aACA,eACA,QACe;AACf,MAAI,aAAa;AAChB,WAAO,YAAY;AAAA,EACpB;AACA,QAAM,YAAY,cAAc;AAChC,MAAI,OAAO,cAAc,UAAU;AAClC,WAAO,EAAE,UAAU,WAAW,SAAS,GAAG,OAAO;AAAA,EAClD;AACA,SAAO,EAAE,UAAU,KAAK,IAAI,GAAG,SAAS,GAAG,OAAO;AACnD;AAEA,SAAS,aAAa,GAAiB,GAA+B;AACrE,SAAO,mBAAmB,QAAQ,GAAG,CAAC,KAAK,IAAI,IAAI;AACpD;AAEA,SAAS,UAAU,GAAY,GAAqB;AACnD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,MAAM,QAAQ,MAAM,KAAM,QAAO;AACrC,MAAI,MAAM,UAAa,MAAM,OAAW,QAAO;AAC/C,MAAI,OAAO,MAAM,OAAO,EAAG,QAAO;AAElC,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AACzC,QAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,WAAO,EAAE,MAAM,CAAC,KAAK,MAAM,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC;AAAA,EAChD;AAEA,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AACnD,UAAM,QAAQ,OAAO,KAAK,CAA4B;AACtD,UAAM,QAAQ,OAAO,KAAK,CAA4B;AACtD,QAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,WAAO,MAAM;AAAA,MAAM,CAAC,QACnB,UAAW,EAA8B,GAAG,GAAI,EAA8B,GAAG,CAAC;AAAA,IACnF;AAAA,EACD;AAEA,SAAO;AACR;;;AEp3BA,SAAS,oCAAoC;AAE7C,IAAM,oBAAoB,CAAC,mBAAmB,kBAAkB,qBAAqB;AAIrF,SAAS,aAAa,OAAyE;AAC9F,SAAQ,kBAAwC,SAAS,MAAM,IAAI;AACpE;AAKO,SAAS,qBAAqB,OAAc,SAAuC;AACzF,QAAM,gBAAgB,kBAAkB;AAAA,IAAI,CAAC,cAC5C,QAAQ,GAAG,WAAW,CAAC,UAAU;AAChC,UAAI,CAAC,aAAa,KAAK,GAAG;AACzB;AAAA,MACD;AACA,YAAM,QAAQ,6BAA6B,KAAK;AAChD,WAAK,MAAM,iBAAiB,KAAK,EAAE,MAAM,MAAM;AAAA,MAE/C,CAAC;AAAA,IACF,CAAC;AAAA,EACF;AAEA,SAAO,MAAM;AACZ,eAAW,SAAS,eAAe;AAClC,YAAM;AAAA,IACP;AAAA,EACD;AACD;;;ACjCA,SAA8C,iBAAiB;AAC/D,SAAyC,yBAAyB;AAIlE,SAAS,eAAe,MAAiD;AACxE,MAAI,CAAC,MAAM;AACV,WAAO,CAAC;AAAA,EACT;AACA,QAAM,OAAkB,CAAC;AACzB,aAAW,SAAS,OAAO,OAAO,IAAI,GAAG;AACxC,QAAI,UAAU,KAAK,KAAK,MAAM,iBAAiB,QAAW;AACzD,WAAK,KAAK,KAAK;AAAA,IAChB;AAAA,EACD;AACA,SAAO;AACR;AAOA,eAAe,WACd,KACA,YACA,WACgB;AAChB,QAAM,eAAe,IAAI;AACzB,MAAI,iBAAiB,QAAW;AAC/B;AAAA,EACD;AACA,QAAM,gBAAgB,MAAM,UAAU,IAAI,YAAY;AACtD,MAAI,kBAAkB,MAAM;AAC3B;AAAA,EACD;AAEA,QAAM,WAAW,kBAAkB,aAAa;AAChD,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,aAAa,SAAS,aAAa;AAC7C,QAAI,KAAK,IAAI,SAAS,GAAG;AACxB;AAAA,IACD;AACA,SAAK,IAAI,SAAS;AAClB,UAAM,QAAQ,MAAM,UAAU,IAAI,SAAS;AAC3C,QAAI,UAAU,MAAM;AACnB,iBAAW,gBAAgB,WAAW,KAAK;AAAA,IAC5C;AAAA,EACD;AACA,aAAW,gBAAgB,cAAc,aAAa;AACvD;AAaO,SAAS,eACf,SACA,YACA,WACa;AACb,QAAM,WAAW,oBAAI,IAAY;AACjC,SAAO,QAAQ,GAAG,aAAa,CAAC,UAAU;AACzC,QAAI,CAAC,WAAW,qBAAqB,GAAG;AACvC;AAAA,IACD;AACA,eAAW,MAAM,MAAM,YAAY;AAClC,iBAAW,OAAO,eAAe,GAAG,IAAI,GAAG;AAC1C,cAAM,eAAe,IAAI;AACzB,YAAI,iBAAiB,UAAa,SAAS,IAAI,YAAY,GAAG;AAC7D;AAAA,QACD;AACA,iBAAS,IAAI,YAAY;AACzB,aAAK,WAAW,KAAK,YAAY,SAAS,EAAE,MAAM,MAAM;AAGvD,mBAAS,OAAO,YAAY;AAAA,QAC7B,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD,CAAC;AACF;;;AC/DO,SAAS,0BAA0B,YAA0C;AACnF,QAAM,UAAU,WAAW,oBAAoB;AAC/C,SAAO;AAAA,IACN,KAAK,SAA6B;AAGjC,cAAQ,KAAK,OAAO;AAAA,IACrB;AAAA,IACA,UAAU,SAAgD;AACzD,cAAQ,UAAU,CAAC,YAAY;AAG9B,YAAI,QAAQ,SAAS,wBAAwB,QAAQ,SAAS,uBAAuB;AACpF,kBAAQ,OAAO;AAAA,QAChB;AAAA,MACD,CAAC;AAAA,IACF;AAAA,EACD;AACD;;;AC1BO,IAAM,sBAAN,MAA+C;AAAA,EAGrD,YACkB,OACjB,aACA,SACA,SACC;AAJgB;AAKjB,SAAK,WAAW,IAAI,cAAc;AAAA,MACjC;AAAA,MACA;AAAA,MACA;AAAA,MACA,iBAAiB,SAAS;AAAA,IAC3B,CAAC;AAAA,EACF;AAAA,EAXkB;AAAA,EAHD;AAAA,EAgBjB,mBAAkC;AACjC,WAAO,KAAK,MAAM,iBAAiB;AAAA,EACpC;AAAA,EAEA,YAAoB;AACnB,WAAO,KAAK,MAAM,UAAU;AAAA,EAC7B;AAAA,EAEA,MAAM,kBAAkB,QAAgB,SAAiB,OAAqC;AAC7F,WAAO,KAAK,MAAM,kBAAkB,QAAQ,SAAS,KAAK;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,iBACL,YACA,UAC0C;AAC1C,UAAM,WAAW,MAAM,KAAK,MAAM,oBAAoB,YAAY,QAAQ;AAC1E,WAAO,WAAW,SAAS,SAAS;AAAA,EACrC;AAAA,EAEA,MAAM,qBAAqB,IAAqC;AAC/D,WAAO,KAAK,SAAS,YAAY,EAAE;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,yBACL,KACA,gBACgG;AAChG,WAAO,KAAK,MAAM,yBAAyB,KAAK,cAAc;AAAA,EAC/D;AACD;;;ACvEA,SAAS,sBAAsB,sBAAAC,2BAA0B;AAclD,IAAM,oBAAN,MAAgD;AAAA,EACtD,YAA6B,SAAyB;AAAzB;AAAA,EAA0B;AAAA,EAA1B;AAAA,EAE7B,MAAM,OAA6B;AAClC,UAAM,OAAO,MAAM,KAAK,QAAQ;AAAA,MAC/B;AAAA,IACD;AACA,WAAO,KAAK,IAAI,CAAC,QAAQ,0BAA0B,IAAI,OAAO,CAAC;AAAA,EAChE;AAAA,EAEA,MAAM,QAAQ,IAA8B;AAC3C,UAAM,MAAMA,oBAAmB,EAAE;AACjC,UAAM,UAAwB,EAAE,GAAG,KAAK,aAAa,GAAG,WAAW;AACnE,UAAM,KAAK,QAAQ;AAAA,MAClB;AAAA,MACA,CAAC,GAAG,IAAI,KAAK,UAAU,OAAO,CAAC;AAAA,IAChC;AAAA,EACD;AAAA,EAEA,MAAM,QAAQ,KAA8B;AAC3C,QAAI,IAAI,WAAW,EAAG;AACtB,UAAM,eAAe,IAAI,IAAI,MAAM,GAAG,EAAE,KAAK,IAAI;AACjD,UAAM,KAAK,QAAQ,QAAQ,6CAA6C,YAAY,KAAK,GAAG;AAAA,EAC7F;AAAA,EAEA,MAAM,QAAyB;AAC9B,UAAM,OAAO,MAAM,KAAK,QAAQ;AAAA,MAC/B;AAAA,IACD;AACA,WAAO,KAAK,CAAC,GAAG,OAAO;AAAA,EACxB;AACD;AAEA,SAAS,0BAA0B,SAA4B;AAC9D,QAAM,SAAS,KAAK,MAAM,OAAO;AACjC,QAAM,KAAK,qBAAqB,MAAM;AACtC,SAAO;AAAA,IACN,GAAG;AAAA,IACH,YAAY,OAAO;AAAA,EACpB;AACD;;;ACvDA,OAAoC;AAEpC,SAAS,mBAAmB,mBAAmB,6BAA6B;AAMrE,IAAM,4BAAN,MAAgE;AAAA,EACtE,YACkB,OACA,OAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EAGlB,4BAAoD;AACnD,WAAO,KAAK,MAAM,0BAA0B;AAAA,EAC7C;AAAA,EAEA,0BAA0B,QAAsC;AAC/D,WAAO,KAAK,MAAM,0BAA0B,MAAM;AAAA,EACnD;AAAA,EAEA,mBAAmB,GAAkB,GAAiC;AACrE,WAAO,KAAK,MAAM,mBAAmB,GAAG,CAAC;AAAA,EAC1C;AAAA,EAEA,MAAM,wBAAwB,cAA8C;AAC3E,UAAM,MAAM,MAAM,KAAK,sBAAsB,YAAY;AACzD,WAAO,IAAI;AAAA,EACZ;AAAA,EAEA,MAAM,sBAAsB,cAAmD;AAC9E,UAAM,MAAM,MAAM,KAAK,MAAM,sBAAsB,YAAY;AAC/D,WAAO,IAAI,OAAO,CAAC,OAAO,sBAAsB,IAAI,KAAK,KAAK,CAAC;AAAA,EAChE;AAAA,EAEA,kBAA+C;AAC9C,WAAO,KAAK,MAAM,gBAAgB,EAAE,KAAK,CAAC,YAAY,kBAAkB,OAAO,CAAC;AAAA,EACjF;AAAA,EAEA,MAAM,gBAAgB,QAA2C;AAChE,UAAM,KAAK,MAAM,gBAAgB,SAAS,kBAAkB,MAAM,IAAI,IAAI;AAAA,EAC3E;AAAA,EAEA,sBAAsB,WAAoC;AACzD,WAAO,KAAK,MAAM,sBAAsB,SAAS;AAAA,EAClD;AAAA,EAEA,MAAM,sBAAsB,WAAmB,WAAkC;AAChF,UAAM,KAAK,MAAM,sBAAsB,WAAW,SAAS;AAAA,EAC5D;AAAA,EAEA,4BAA6D;AAC5D,WAAO,KAAK,MAAM,0BAA0B;AAAA,EAC7C;AAAA,EAEA,MAAM,wBAAwB,WAAkC;AAC/D,UAAM,KAAK,MAAM,wBAAwB,SAAS;AAAA,EACnD;AACD;","names":["createOperation","operation","version","opInsert","createOperation","ctx","serializeOperation"]}
|
|
1
|
+
{"version":3,"sources":["../src/apply-pipeline.ts","../src/build-side-effect-entry.ts","../src/audit-bridge.ts","../src/blob/blob-upload-coordinator.ts","../src/blob/sync-chunk-port.ts","../src/merge-aware-sync-store.ts","../src/store-queue-storage.ts","../src/store-sync-state.ts"],"sourcesContent":["import type {\n\tCollectionDefinition,\n\tHLCTimestamp,\n\tKoraEventEmitter,\n\tOperation,\n\tSchemaDefinition,\n} from '@korajs/core'\nimport {\n\tHybridLogicalClock,\n\tKoraError,\n\tcreateOperation,\n\treplayOperationsForRecord,\n} from '@korajs/core'\nimport { topologicalSort } from '@korajs/core/internal'\nimport type { MergeEngine, MergeResult, ReferentialMergeContext, SideEffectOp } from '@korajs/merge'\nimport { buildMergeRelationLookup, checkReferentialIntegrityOnDelete } from '@korajs/merge'\nimport type { ConstraintContext } from '@korajs/merge'\nimport type {\n\tApplyRemoteOptions,\n\tCollectionRecord,\n\tLocalMutationHandler,\n\tRowVersionState,\n\tStore,\n\tTransactionCommitBatch,\n\tTransactionCommitResult,\n} from '@korajs/store'\nimport { OptimisticLockError, richtextStatesEqual } from '@korajs/store'\nimport {\n\texecuteDelete,\n\texecuteInsert,\n\texecuteUpdate,\n\tresolveCausalDeps,\n} from '@korajs/store/internal'\nimport type { ApplyResult } from '@korajs/sync'\nimport { buildSideEffectEntry } from './build-side-effect-entry'\n\n/**\n * Whether the operation originated locally or arrived from sync.\n */\nexport type ApplyMode = 'local' | 'remote'\n\n/**\n * How many times a merge-engine apply retries when the row changed underneath\n * it (OptimisticLockError). Each retry recomputes the merge from fresh state;\n * since JavaScript is single-threaded, invalidation requires an actual new\n * local write landing at an `await` point, so contention is self-limiting.\n * Exhaustion rethrows: the operation stays unacknowledged and sync retries it\n * later — data is never dropped.\n */\nconst MERGE_APPLY_RETRY_LIMIT = 8\n\n/**\n * Dependencies for the unified apply pipeline.\n */\nexport interface ApplyPipelineDeps {\n\treadonly store: Store\n\treadonly mergeEngine: MergeEngine\n\treadonly emitter: KoraEventEmitter | null\n\t/** Called when a field-level merge runs (updates sync conflict counter). */\n\treadonly onMergeConflict?: () => void\n}\n\n/**\n * Context passed into each apply invocation.\n */\nexport interface ApplyContext {\n\treadonly mode: ApplyMode\n\treadonly schema: SchemaDefinition\n}\n\n/**\n * Single entry point for applying remote sync operations with merge + referential integrity.\n */\nexport class ApplyPipeline implements LocalMutationHandler {\n\tprivate readonly relationLookupMap: ReturnType<typeof buildMergeRelationLookup>\n\n\tconstructor(private readonly deps: ApplyPipelineDeps) {\n\t\tthis.relationLookupMap = buildMergeRelationLookup(deps.store.getSchema())\n\t}\n\n\t/** Local insert — same persistence path as remote apply side effects. */\n\tasync insert(collection: string, data: Record<string, unknown>): Promise<CollectionRecord> {\n\t\treturn executeInsert(this.deps.store.createMutationContext(collection), data)\n\t}\n\n\t/** Local update. */\n\tasync update(\n\t\tcollection: string,\n\t\tid: string,\n\t\tdata: Record<string, unknown>,\n\t): Promise<CollectionRecord> {\n\t\treturn executeUpdate(this.deps.store.createMutationContext(collection), id, data)\n\t}\n\n\t/**\n\t * Local delete with merge-package referential integrity (same rules as remote delete).\n\t */\n\tasync delete(collection: string, id: string): Promise<void> {\n\t\tconst ctx = this.deps.store.createMutationContext(collection)\n\t\tconst causalDeps = resolveCausalDeps(ctx)\n\t\tconst operation = await createOperation(\n\t\t\t{\n\t\t\t\tnodeId: ctx.nodeId,\n\t\t\t\ttype: 'delete',\n\t\t\t\tcollection,\n\t\t\t\trecordId: id,\n\t\t\t\tdata: null,\n\t\t\t\tpreviousData: null,\n\t\t\t\tsequenceNumber: await ctx.allocateSequenceNumber(),\n\t\t\t\tcausalDeps,\n\t\t\t\tschemaVersion: ctx.schema.version,\n\t\t\t},\n\t\t\tctx.clock,\n\t\t)\n\t\tctx.causalTracker?.afterOperation(collection, operation.id, ctx.inTransaction)\n\n\t\tconst refCtx = createReferentialMergeContext(this.deps.store)\n\t\tconst check = await checkReferentialIntegrityOnDelete(\n\t\t\toperation,\n\t\t\tthis.deps.store.getSchema(),\n\t\t\trefCtx,\n\t\t\tthis.relationLookupMap,\n\t\t)\n\n\t\tfor (const trace of check.traces) {\n\t\t\tthis.deps.emitter?.emit({ type: 'merge:conflict', trace })\n\t\t}\n\n\t\tif (!check.allowed) {\n\t\t\tthrow new KoraError(\n\t\t\t\t`Cannot delete record \"${id}\" from \"${collection}\": referential restrict policy violated`,\n\t\t\t\t'REFERENTIAL_INTEGRITY',\n\t\t\t\t{ collection, recordId: id },\n\t\t\t)\n\t\t}\n\n\t\tawait executeDelete(ctx, id, {\n\t\t\tskipReferentialEnforcement: true,\n\t\t\toperation,\n\t\t})\n\n\t\tif (check.sideEffectOps.length > 0) {\n\t\t\tawait applySideEffectOps(this.deps.store, check.sideEffectOps, operation.id)\n\t\t}\n\t}\n\n\t/**\n\t * Commit a buffered transaction: merge-package delete enforcement, causal ordering, single DB txn.\n\t */\n\tasync commitTransaction(batch: TransactionCommitBatch): Promise<TransactionCommitResult> {\n\t\tconst store = this.deps.store\n\t\tconst supplemental: TransactionCommitBatch['entries'] = []\n\n\t\tfor (const entry of batch.entries) {\n\t\t\tif (entry.operation.type !== 'delete') {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconst refCtx = createReferentialMergeContext(store)\n\t\t\tconst check = await checkReferentialIntegrityOnDelete(\n\t\t\t\tentry.operation,\n\t\t\t\tstore.getSchema(),\n\t\t\t\trefCtx,\n\t\t\t\tthis.relationLookupMap,\n\t\t\t)\n\n\t\t\tfor (const trace of check.traces) {\n\t\t\t\tthis.deps.emitter?.emit({ type: 'merge:conflict', trace })\n\t\t\t}\n\n\t\t\tif (!check.allowed) {\n\t\t\t\tthrow new KoraError(\n\t\t\t\t\t`Cannot delete record \"${entry.operation.recordId}\" from \"${entry.collection}\": referential restrict policy violated`,\n\t\t\t\t\t'REFERENTIAL_INTEGRITY',\n\t\t\t\t\t{ collection: entry.collection, recordId: entry.operation.recordId },\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tfor (const effect of check.sideEffectOps) {\n\t\t\t\tconst ctx = store.createMutationContext(effect.collection, { inTransaction: true })\n\t\t\t\tsupplemental.push(\n\t\t\t\t\tawait buildSideEffectEntry(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\teffect,\n\t\t\t\t\t\tentry.operation.id,\n\t\t\t\t\t\tbatch.transactionId,\n\t\t\t\t\t\tbatch.mutationName,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tconst allEntries = [...batch.entries, ...supplemental]\n\t\tconst sortedOps = topologicalSort(allEntries.map((e) => e.operation))\n\t\tconst commandsByOpId = new Map(allEntries.map((e) => [e.operation.id, e.commands] as const))\n\n\t\tconst ctx = store.createMutationContext(\n\t\t\tbatch.entries[0]?.collection ?? Object.keys(store.getSchema().collections)[0] ?? 'todos',\n\t\t\t{ inTransaction: true },\n\t\t)\n\n\t\tawait ctx.adapter.transaction(async (tx) => {\n\t\t\tfor (const op of sortedOps) {\n\t\t\t\tconst commands = commandsByOpId.get(op.id)\n\t\t\t\tif (!commands) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor (const cmd of commands) {\n\t\t\t\t\tawait tx.execute(cmd.sql, cmd.params)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\tconst affectedCollections = new Set<string>()\n\t\tfor (const entry of allEntries) {\n\t\t\taffectedCollections.add(entry.collection)\n\t\t}\n\n\t\treturn { operations: sortedOps, affectedCollections }\n\t}\n\n\tasync applyRemote(op: Operation): Promise<ApplyResult> {\n\t\treturn this.apply(op, { mode: 'remote', schema: this.deps.store.getSchema() })\n\t}\n\n\tasync apply(op: Operation, context: ApplyContext): Promise<ApplyResult> {\n\t\tif (context.mode === 'local') {\n\t\t\treturn this.deps.store.applyRemoteOperation(op)\n\t\t}\n\n\t\tif (op.type === 'delete') {\n\t\t\treturn this.applyRemoteDelete(op)\n\t\t}\n\n\t\tif (op.type === 'insert' && op.data) {\n\t\t\treturn this.applyRemoteInsert(op)\n\t\t}\n\n\t\tif (op.type === 'update' && op.data && op.previousData) {\n\t\t\treturn this.applyRemoteUpdate(op)\n\t\t}\n\n\t\treturn this.deps.store.applyRemoteOperation(op)\n\t}\n\n\tprivate async applyRemoteDelete(op: Operation): Promise<ApplyResult> {\n\t\tconst blocked = await this.resolveRemoteDeleteVsLocalUpdate(op)\n\t\tif (blocked) {\n\t\t\treturn 'skipped'\n\t\t}\n\n\t\tconst refCtx = createReferentialMergeContext(this.deps.store)\n\t\tconst check = await checkReferentialIntegrityOnDelete(\n\t\t\top,\n\t\t\tthis.deps.store.getSchema(),\n\t\t\trefCtx,\n\t\t\tthis.relationLookupMap,\n\t\t)\n\n\t\tfor (const trace of check.traces) {\n\t\t\tthis.deps.emitter?.emit({ type: 'merge:conflict', trace })\n\t\t}\n\n\t\tif (!check.allowed) {\n\t\t\treturn 'rejected'\n\t\t}\n\n\t\tconst result = await this.deps.store.applyRemoteOperation(op)\n\t\tif (result !== 'applied') {\n\t\t\treturn result\n\t\t}\n\n\t\tif (check.sideEffectOps.length > 0) {\n\t\t\tawait applySideEffectOps(this.deps.store, check.sideEffectOps, op.id)\n\t\t}\n\n\t\treturn 'applied'\n\t}\n\n\t/**\n\t * When a local update is newer than a remote delete, keep the record alive.\n\t */\n\tprivate async resolveRemoteDeleteVsLocalUpdate(op: Operation): Promise<boolean> {\n\t\tconst collectionDef = this.deps.store.getSchema().collections[op.collection]\n\t\tif (!collectionDef) {\n\t\t\treturn false\n\t\t}\n\n\t\tconst localOp = await this.deps.store.getLatestLocalOperationForRecord(\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t)\n\t\tif (!localOp || localOp.type !== 'update') {\n\t\t\treturn false\n\t\t}\n\n\t\tconst baseState = localOp.previousData ?? {}\n\t\tconst mergeResult = await this.deps.mergeEngine.merge({\n\t\t\tlocal: localOp,\n\t\t\tremote: op,\n\t\t\tbaseState,\n\t\t\tcollectionDef,\n\t\t})\n\n\t\tthis.emitMergeLifecycle(op, localOp, mergeResult)\n\n\t\treturn mergeResult.appliedOperation === 'local'\n\t}\n\n\tprivate async applyRemoteUpdate(op: Operation): Promise<ApplyResult> {\n\t\tconst schema = this.deps.store.getSchema()\n\t\tconst collectionDef = schema.collections[op.collection]\n\t\tif (!collectionDef) {\n\t\t\treturn this.deps.store.applyRemoteOperation(op)\n\t\t}\n\n\t\t// Merge-engine paths read state, compute a merge, then write. A concurrent\n\t\t// local mutation landing between read and write invalidates the merge; the\n\t\t// guarded apply detects this (OptimisticLockError) and we recompute from\n\t\t// fresh state. Bounded so a pathological writer can't spin forever —\n\t\t// exhaustion rethrows, leaving the op unacknowledged for a later sync\n\t\t// retry (never silently dropped).\n\t\tlet lockError: OptimisticLockError | null = null\n\t\tfor (let attempt = 0; attempt < MERGE_APPLY_RETRY_LIMIT; attempt++) {\n\t\t\ttry {\n\t\t\t\treturn await this.applyRemoteUpdateAttempt(op, collectionDef)\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof OptimisticLockError)) {\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t\tlockError = error\n\t\t\t}\n\t\t}\n\t\tthrow lockError ?? new OptimisticLockError(op.collection, op.recordId)\n\t}\n\n\tprivate async applyRemoteUpdateAttempt(\n\t\top: Operation,\n\t\tcollectionDef: CollectionDefinition,\n\t): Promise<ApplyResult> {\n\t\t// Snapshot the row's version state BEFORE reading the record: if a local\n\t\t// write lands between these two reads, the guard is older than the record\n\t\t// and the guarded apply fails safe (retry). The reverse order would let a\n\t\t// merge computed from a stale record pass a fresh guard and clobber the\n\t\t// local write.\n\t\tconst guard: RowVersionState = (await this.deps.store.getRowVersionState(\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t)) ?? { version: null, fieldVersions: null }\n\n\t\tconst accessor = this.deps.store.collection(op.collection)\n\t\tconst currentRecord = await accessor.findById(op.recordId)\n\n\t\tif (!currentRecord) {\n\t\t\tconst tombstoneResult = await this.applyRemoteUpdateOnDeletedRow(op, collectionDef, guard)\n\t\t\tif (tombstoneResult !== null) {\n\t\t\t\treturn tombstoneResult\n\t\t\t}\n\t\t\t// No materialized row at all: the store's atomic path appends the op\n\t\t\t// (nothing to guard — materialization is a no-op until the insert lands).\n\t\t\treturn this.deps.store.applyRemoteOperation(op)\n\t\t}\n\n\t\t// Pure scalar last-write-wins is resolved deterministically and atomically\n\t\t// inside the store using per-field HLC versions. Routing it there (instead\n\t\t// of pre-reading the row here to guess a conflict) both removes the\n\t\t// read-decide-write race — a concurrent local edit can no longer slip\n\t\t// between the check and the write — and guarantees every node converges to\n\t\t// the max-timestamp writer regardless of the order operations arrive. Only\n\t\t// CRDT fields (richtext, add-wins-set arrays), declared constraints, or\n\t\t// custom resolvers still need the three-tier merge engine.\n\t\tif (!updateNeedsMergeEngine(op, collectionDef)) {\n\t\t\t// The store resolves the write deterministically, but a same-field\n\t\t\t// concurrent edit is still a merge decision the developer/DevTools must\n\t\t\t// see. Emit the trace from the immutable local op in the log (race-free),\n\t\t\t// then let the store perform the authoritative atomic write.\n\t\t\tawait this.observeScalarMerge(op, collectionDef)\n\t\t\treturn this.deps.store.applyRemoteOperation(op)\n\t\t}\n\n\t\tlet hasConflict = false\n\t\tfor (const field of Object.keys(op.data ?? {})) {\n\t\t\tconst fieldDef = collectionDef.fields[field]\n\t\t\tconst expectedBase = op.previousData?.[field]\n\t\t\tconst currentLocal = currentRecord[field]\n\n\t\t\tif (fieldDef?.kind === 'richtext') {\n\t\t\t\tif (!richtextStatesEqual(currentLocal, expectedBase)) {\n\t\t\t\t\thasConflict = true\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (!deepEqual(expectedBase, currentLocal)) {\n\t\t\t\thasConflict = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif (!hasConflict) {\n\t\t\t// No CRDT/constraint field the remote op touches has diverged locally\n\t\t\t// from the base it wrote from: a clean fast-forward. Guarded, because\n\t\t\t// the divergence check above read the row outside the write transaction.\n\t\t\treturn this.deps.store.applyRemoteOperation(op, {\n\t\t\t\tforceMaterialize: true,\n\t\t\t\tguardRowState: guard,\n\t\t\t})\n\t\t}\n\n\t\treturn this.applyMergedUpdate(op, collectionDef, currentRecord, op.previousData ?? {}, guard)\n\t}\n\n\t/**\n\t * Remote update vs local soft-delete: merge before materializing to avoid zombie rows.\n\t */\n\t/**\n\t * Emit merge observability (trace + conflict counter) for a scalar update that\n\t * concurrently touches a field a local operation also changed.\n\t *\n\t * The store resolves the actual value by deterministic per-field LWW; this\n\t * method exists only so the decision is loggable (CLAUDE.md: every merge\n\t * decision must be traceable). It reads the local operation from the append-\n\t * only log — which is immutable — rather than the materialized row, so it is\n\t * not subject to the read-then-write race the store write path was hardened\n\t * against. The merge engine is invoked purely to build an accurate trace; its\n\t * timestamps match the store's LWW comparison, so the trace never lies.\n\t */\n\tprivate async observeScalarMerge(\n\t\top: Operation,\n\t\tcollectionDef: CollectionDefinition,\n\t): Promise<void> {\n\t\tconst localOp = await this.deps.store.getLatestLocalOperationForRecord(\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t)\n\t\tif (!localOp || !localOp.data) {\n\t\t\treturn\n\t\t}\n\t\tconst remoteFields = Object.keys(op.data ?? {})\n\t\tconst sharesField = remoteFields.some((field) => localOp.data?.[field] !== undefined)\n\t\tif (!sharesField) {\n\t\t\treturn\n\t\t}\n\n\t\tconst mergeResult = await this.deps.mergeEngine.merge({\n\t\t\tlocal: localOp,\n\t\t\tremote: op,\n\t\t\tbaseState: op.previousData ?? {},\n\t\t\tcollectionDef,\n\t\t})\n\t\tthis.emitMergeLifecycle(op, localOp, mergeResult)\n\t}\n\n\tprivate async applyRemoteUpdateOnDeletedRow(\n\t\top: Operation,\n\t\tcollectionDef: CollectionDefinition,\n\t\tguard: RowVersionState,\n\t): Promise<ApplyResult | null> {\n\t\tconst snapshot = await this.deps.store.findMaterializedRow(op.collection, op.recordId)\n\t\tif (!snapshot?.deleted) {\n\t\t\t// Not a tombstone (never inserted): let the caller append the orphan.\n\t\t\treturn null\n\t\t}\n\n\t\t// A remote update landing on a tombstone must be resolved authoritatively by\n\t\t// folding the record's whole operation log in HLC order — the same fold the\n\t\t// server uses — so every device agrees on whether the update resurrects the\n\t\t// record and on the resulting field values. The previous path only handled a\n\t\t// LOCAL delete (via the pairwise merge engine) and left a remote-created\n\t\t// tombstone hidden on any passive observer that received delete-then-update,\n\t\t// and it materialized the resurrecting op's raw value rather than the composed\n\t\t// atomic chain. Folding fixes both: delete-vs-newer-update convergence and\n\t\t// atomic resurrection (increments before and after the delete compose correctly).\n\t\tconst priorOps = await this.deps.store.getOperationsForRecord(op.collection, op.recordId)\n\t\tconst ops = [...priorOps, op].sort((a, b) =>\n\t\t\tHybridLogicalClock.compare(a.timestamp, b.timestamp),\n\t\t)\n\t\tconst folded = replayOperationsForRecord(ops)\n\n\t\tif (!folded) {\n\t\t\t// The delete still wins: append the op to the log (so a later fold — e.g. an\n\t\t\t// atomic resurrection composing this op's delta — is complete) but leave the\n\t\t\t// row a clean tombstone: no zombie fields, no version regression.\n\t\t\treturn this.deps.store.applyRemoteOperation(op, { logOnly: true })\n\t\t}\n\n\t\t// Resurrected: materialize the authoritative folded state and clear the\n\t\t// tombstone. The log keeps the canonical op; the row takes the folded data at\n\t\t// the newest operation's timestamp. Guarded so a concurrent local write retries.\n\t\tconst newestTimestamp = (ops[ops.length - 1] as Operation).timestamp\n\t\treturn this.deps.store.applyRemoteOperation(op, {\n\t\t\treactivateIfDeleted: true,\n\t\t\tforceMaterialize: true,\n\t\t\tmaterializeData: folded,\n\t\t\tmaterializeTimestamp: maxTimestamp(op.timestamp, newestTimestamp),\n\t\t\tguardRowState: guard,\n\t\t})\n\t}\n\n\tprivate async applyMergedUpdate(\n\t\top: Operation,\n\t\tcollectionDef: CollectionDefinition,\n\t\tcurrentRecord: CollectionRecord,\n\t\tbaseState: Record<string, unknown>,\n\t\tguard: RowVersionState,\n\t\tapplyOptions?: ApplyRemoteOptions,\n\t): Promise<ApplyResult> {\n\t\tconst latestLocal = await this.deps.store.getLatestLocalOperationForRecord(\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t)\n\t\tconst localTimestamp = resolveLocalTimestamp(\n\t\t\tlatestLocal,\n\t\t\tcurrentRecord,\n\t\t\tthis.deps.store.getNodeId(),\n\t\t)\n\t\t// The spread of `op` would copy the REMOTE op's atomicOps onto the local\n\t\t// side, making atomic composition double the remote delta instead of\n\t\t// summing both intents. The local side's intent metadata must come from\n\t\t// the actual local operation — or be absent entirely.\n\t\tconst { atomicOps: _remoteAtomicOps, ...opWithoutAtomic } = op\n\t\tconst localOp: Operation = {\n\t\t\t...opWithoutAtomic,\n\t\t\tdata: buildLocalDiff(baseState, currentRecord, Object.keys(op.data ?? {})),\n\t\t\tpreviousData: op.previousData,\n\t\t\tnodeId: this.deps.store.getNodeId(),\n\t\t\ttimestamp: localTimestamp,\n\t\t\t...(latestLocal?.atomicOps !== undefined ? { atomicOps: latestLocal.atomicOps } : {}),\n\t\t}\n\n\t\tconst constraintContext = createConstraintContext(this.deps.store)\n\t\tconst mergeResult = await this.deps.mergeEngine.merge(\n\t\t\t{\n\t\t\t\tlocal: localOp,\n\t\t\t\tremote: op,\n\t\t\t\tbaseState,\n\t\t\t\tcollectionDef,\n\t\t\t},\n\t\t\tconstraintContext,\n\t\t)\n\n\t\t// Atomic-op fields are materialized by folding the record's operation log in\n\t\t// HLC order (the same fold the server uses), not through the merge engine's\n\t\t// pairwise `base + localDelta + remoteDelta`. The pairwise form is correct only\n\t\t// for two concurrent writes from a shared base; with a third concurrent writer\n\t\t// the current row already folds in an earlier remote delta, and re-deriving from\n\t\t// base + the local device's own delta silently drops it (a lost update). The\n\t\t// fold composes a same-type atomic chain and last-write-wins for anything else\n\t\t// (including a plain set breaking the chain), so every device — author or passive\n\t\t// observer — converges to the same value the server materializes.\n\t\tconst mergedData = await this.materializeAtomicFieldsFromLog(op, mergeResult.mergedData)\n\n\t\t// The merged data is authoritative (it already folds in the current local\n\t\t// row), so it must materialize even when its timestamp ties the current\n\t\t// row version — otherwise the device that authored the newer of two\n\t\t// concurrent edits silently drops the merge result and never converges.\n\t\t// The LOG stores the canonical op (ops are immutable, content-addressed);\n\t\t// only the row write uses the merged data and max(local, remote) stamp.\n\t\t// Guarded: a local write since our snapshot invalidates the merge.\n\t\tconst applyResult = await this.deps.store.applyRemoteOperation(op, {\n\t\t\t...applyOptions,\n\t\t\tforceMaterialize: true,\n\t\t\tmaterializeData: mergedData,\n\t\t\tmaterializeTimestamp: maxTimestamp(op.timestamp, localTimestamp),\n\t\t\tguardRowState: guard,\n\t\t})\n\n\t\t// Emit AFTER the guarded apply so a retried attempt doesn't double-report\n\t\t// a merge decision that never materialized.\n\t\tthis.emitMergeLifecycle(op, localOp, mergeResult)\n\n\t\tif (applyResult === 'applied' && mergeResult.sideEffects.length > 0) {\n\t\t\tawait applySideEffectOps(this.deps.store, mergeResult.sideEffects, op.id)\n\t\t}\n\n\t\treturn applyResult\n\t}\n\n\t/**\n\t * Materialize the operation's atomic-op fields by folding the record's full\n\t * operation log (in HLC order, including this not-yet-appended op) through the\n\t * shared atomic-aware replay — the same fold the server uses. Non-atomic fields\n\t * keep their merge-engine result. This composes a same-type atomic chain and\n\t * last-write-wins for anything else, so all replicas converge regardless of how\n\t * many concurrent atomic writers there were or which device authored them.\n\t */\n\tprivate async materializeAtomicFieldsFromLog(\n\t\top: Operation,\n\t\tmergedData: Record<string, unknown>,\n\t): Promise<Record<string, unknown>> {\n\t\tif (!op.atomicOps || Object.keys(op.atomicOps).length === 0) {\n\t\t\treturn mergedData\n\t\t}\n\t\tconst priorOps = await this.deps.store.getOperationsForRecord(op.collection, op.recordId)\n\t\t// The op being applied is not in the log yet; fold it in at its HLC position.\n\t\tconst ops = [...priorOps, op].sort((a, b) =>\n\t\t\tHybridLogicalClock.compare(a.timestamp, b.timestamp),\n\t\t)\n\t\tconst folded = replayOperationsForRecord(ops)\n\t\tif (!folded) {\n\t\t\treturn mergedData\n\t\t}\n\t\tconst result = { ...mergedData }\n\t\tfor (const field of Object.keys(op.atomicOps)) {\n\t\t\tif (field in folded) {\n\t\t\t\tresult[field] = folded[field]\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n\n\tprivate async applyRemoteInsert(op: Operation): Promise<ApplyResult> {\n\t\tconst collectionDef = this.deps.store.getSchema().collections[op.collection]\n\t\tif (!collectionDef || !op.data) {\n\t\t\treturn this.deps.store.applyRemoteOperation(op)\n\t\t}\n\n\t\tlet lockError: OptimisticLockError | null = null\n\t\tfor (let attempt = 0; attempt < MERGE_APPLY_RETRY_LIMIT; attempt++) {\n\t\t\ttry {\n\t\t\t\treturn await this.applyRemoteInsertAttempt(op, collectionDef)\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof OptimisticLockError)) {\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t\tlockError = error\n\t\t\t}\n\t\t}\n\t\tthrow lockError ?? new OptimisticLockError(op.collection, op.recordId)\n\t}\n\n\tprivate async applyRemoteInsertAttempt(\n\t\top: Operation,\n\t\tcollectionDef: CollectionDefinition,\n\t): Promise<ApplyResult> {\n\t\t// Guard snapshot BEFORE the record read (see applyRemoteUpdateAttempt).\n\t\tconst guard: RowVersionState = (await this.deps.store.getRowVersionState(\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t)) ?? { version: null, fieldVersions: null }\n\n\t\tconst snapshot = await this.deps.store.findMaterializedRow(op.collection, op.recordId)\n\t\tif (!snapshot || snapshot.deleted) {\n\t\t\t// Absent row and insert-vs-tombstone are resolved atomically inside the\n\t\t\t// store; guard against a local write racing this decision.\n\t\t\treturn this.deps.store.applyRemoteOperation(op, { guardRowState: guard })\n\t\t}\n\n\t\tconst localOp = await resolveLocalOpForRecord(\n\t\t\tthis.deps.store,\n\t\t\top.collection,\n\t\t\top.recordId,\n\t\t\tsnapshot.record,\n\t\t)\n\n\t\tconst mergeResult = await this.deps.mergeEngine.merge({\n\t\t\tlocal: localOp,\n\t\t\tremote: op,\n\t\t\tbaseState: {},\n\t\t\tcollectionDef,\n\t\t})\n\n\t\t// Canonical op in the log; merged data + max(local, remote) stamp on the\n\t\t// row. Force, because the merged result folds in the current local row and\n\t\t// must win even on a version tie with it.\n\t\tconst applyResult = await this.deps.store.applyRemoteOperation(op, {\n\t\t\tforceMaterialize: true,\n\t\t\tmaterializeData: mergeResult.mergedData,\n\t\t\tmaterializeTimestamp: maxTimestamp(op.timestamp, localOp.timestamp),\n\t\t\tguardRowState: guard,\n\t\t})\n\n\t\tthis.emitMergeLifecycle(op, localOp, mergeResult)\n\n\t\treturn applyResult\n\t}\n\n\tprivate emitMergeLifecycle(remote: Operation, local: Operation, mergeResult: MergeResult): void {\n\t\tthis.deps.emitter?.emit({\n\t\t\ttype: 'merge:started',\n\t\t\toperationA: remote,\n\t\t\toperationB: local,\n\t\t})\n\n\t\tconst hadMergeConflict = mergeResult.traces.some(\n\t\t\t(t) =>\n\t\t\t\tt.strategy !== 'no-conflict-local' &&\n\t\t\t\tt.strategy !== 'no-conflict-remote' &&\n\t\t\t\tt.strategy !== 'no-conflict-unchanged',\n\t\t)\n\t\tif (hadMergeConflict) {\n\t\t\tthis.deps.onMergeConflict?.()\n\t\t}\n\n\t\tfor (const trace of mergeResult.traces) {\n\t\t\tif (\n\t\t\t\ttrace.strategy !== 'no-conflict-local' &&\n\t\t\t\ttrace.strategy !== 'no-conflict-remote' &&\n\t\t\t\ttrace.strategy !== 'no-conflict-unchanged'\n\t\t\t) {\n\t\t\t\tthis.deps.emitter?.emit({ type: 'merge:conflict', trace })\n\t\t\t}\n\t\t}\n\t\tconst firstTrace = mergeResult.traces[0]\n\t\tif (firstTrace) {\n\t\t\tthis.deps.emitter?.emit({ type: 'merge:completed', trace: firstTrace })\n\t\t}\n\t}\n}\n\n/**\n * Prefer the latest local op, then any op in the log for this record, then a snapshot-derived insert.\n */\nasync function resolveLocalOpForRecord(\n\tstore: Store,\n\tcollection: string,\n\trecordId: string,\n\trecord: CollectionRecord,\n): Promise<Operation> {\n\tconst localLatest = await store.getLatestLocalOperationForRecord(collection, recordId)\n\tif (localLatest) {\n\t\treturn localLatest\n\t}\n\n\tconst anyLatest = await store.getLatestOperationForRecord(collection, recordId)\n\tif (anyLatest) {\n\t\treturn anyLatest\n\t}\n\n\treturn syntheticInsertFromSnapshot(\n\t\trecord,\n\t\tcollection,\n\t\tstore.getNodeId(),\n\t\tstore.getSchema().version,\n\t)\n}\n\nfunction syntheticInsertFromSnapshot(\n\trecord: CollectionRecord,\n\tcollection: string,\n\tnodeId: string,\n\tschemaVersion: number,\n): Operation {\n\tconst { id, createdAt, updatedAt, ...fields } = record\n\treturn {\n\t\tid: `synthetic-local-${id}`,\n\t\tnodeId,\n\t\ttype: 'insert',\n\t\tcollection,\n\t\trecordId: id,\n\t\tdata: fields,\n\t\tpreviousData: null,\n\t\ttimestamp: { wallTime: updatedAt, logical: 0, nodeId },\n\t\tsequenceNumber: 0,\n\t\tcausalDeps: [],\n\t\tschemaVersion,\n\t}\n}\n\nfunction createConstraintContext(store: Store): ConstraintContext {\n\treturn {\n\t\tasync queryRecords(collection: string, where: Record<string, unknown>) {\n\t\t\tconst rows = await store.collection(collection).where(where).exec()\n\t\t\treturn rows as Record<string, unknown>[]\n\t\t},\n\t\tasync countRecords(collection: string, where: Record<string, unknown>) {\n\t\t\treturn store.collection(collection).where(where).count()\n\t\t},\n\t}\n}\n\nfunction createReferentialMergeContext(store: Store): ReferentialMergeContext {\n\treturn {\n\t\tasync queryRecords(collection: string, where: Record<string, unknown>) {\n\t\t\tconst rows = await store.collection(collection).where(where).exec()\n\t\t\treturn rows as Record<string, unknown>[]\n\t\t},\n\t\tasync recordExists(collection: string, recordId: string) {\n\t\t\tconst row = await store.collection(collection).findById(recordId)\n\t\t\treturn row !== null\n\t\t},\n\t}\n}\n\nasync function applySideEffectOps(\n\tstore: Store,\n\tsideEffects: SideEffectOp[],\n\tparentOpId: string,\n): Promise<void> {\n\tfor (const effect of sideEffects) {\n\t\tconst ctx = store.createMutationContext(effect.collection, {\n\t\t\textraCausalDeps: [parentOpId],\n\t\t})\n\t\tif (effect.type === 'delete') {\n\t\t\tawait executeDelete(ctx, effect.recordId, { skipReferentialEnforcement: true })\n\t\t} else if (effect.type === 'update' && effect.data) {\n\t\t\tawait executeUpdate(ctx, effect.recordId, effect.data)\n\t\t}\n\t}\n}\n\n/**\n * Whether applying this remote update requires the three-tier merge engine.\n *\n * Scalar fields converge under deterministic per-field LWW handled entirely in\n * the store. The merge engine is only needed when a changed field is a CRDT\n * (richtext or add-wins-set array), when the collection declares constraints, or\n * when a changed field has a custom resolver.\n */\nfunction updateNeedsMergeEngine(op: Operation, collectionDef: CollectionDefinition): boolean {\n\tif (collectionDef.constraints.length > 0) {\n\t\treturn true\n\t}\n\t// Intent-preserving atomic ops (increment/max/min) COMPOSE with a concurrent\n\t// local atomic op instead of last-write-wins — plain per-field LWW would\n\t// silently drop one side's delta (a lost update).\n\tif (op.atomicOps && Object.keys(op.atomicOps).length > 0) {\n\t\treturn true\n\t}\n\tfor (const field of Object.keys(op.data ?? {})) {\n\t\tconst kind = collectionDef.fields[field]?.kind\n\t\t// CRDT kinds converge key/element-wise via the merge engine, not by\n\t\t// whole-value LWW: richtext (Yjs), arrays (add-wins set), and structured\n\t\t// object/json (recursive LWW-map). Plain per-field LWW would clobber a\n\t\t// concurrent edit to a different key of the same object.\n\t\tif (kind === 'richtext' || kind === 'array' || kind === 'object' || kind === 'json') {\n\t\t\treturn true\n\t\t}\n\t\tif (collectionDef.resolvers[field]) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunction buildLocalDiff(\n\tbaseState: Record<string, unknown>,\n\tcurrentRecord: Record<string, unknown>,\n\tfields: string[],\n): Record<string, unknown> {\n\tconst diff: Record<string, unknown> = {}\n\tfor (const field of fields) {\n\t\tdiff[field] = currentRecord[field]\n\t}\n\treturn diff\n}\n\nfunction resolveLocalTimestamp(\n\tlatestLocal: Operation | null,\n\tcurrentRecord: CollectionRecord,\n\tnodeId: string,\n): HLCTimestamp {\n\tif (latestLocal) {\n\t\treturn latestLocal.timestamp\n\t}\n\tconst updatedAt = currentRecord.updatedAt\n\tif (typeof updatedAt === 'number') {\n\t\treturn { wallTime: updatedAt, logical: 0, nodeId }\n\t}\n\treturn { wallTime: Date.now(), logical: 0, nodeId }\n}\n\nfunction maxTimestamp(a: HLCTimestamp, b: HLCTimestamp): HLCTimestamp {\n\treturn HybridLogicalClock.compare(a, b) >= 0 ? a : b\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n\tif (a === b) return true\n\tif (a === null || b === null) return false\n\tif (a === undefined || b === undefined) return false\n\tif (typeof a !== typeof b) return false\n\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\tif (a.length !== b.length) return false\n\t\treturn a.every((val, i) => deepEqual(val, b[i]))\n\t}\n\n\tif (typeof a === 'object' && typeof b === 'object') {\n\t\tconst keysA = Object.keys(a as Record<string, unknown>)\n\t\tconst keysB = Object.keys(b as Record<string, unknown>)\n\t\tif (keysA.length !== keysB.length) return false\n\t\treturn keysA.every((key) =>\n\t\t\tdeepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n\t\t)\n\t}\n\n\treturn false\n}\n","import { createOperation } from '@korajs/core'\nimport type { SideEffectOp } from '@korajs/merge'\nimport type { TransactionBufferedEntry } from '@korajs/store'\nimport type { LocalMutationContext } from '@korajs/store/internal'\nimport {\n\tbuildInsertQuery,\n\tbuildSoftDeleteQuery,\n\tbuildUpdateQuery,\n\tserializeOperation,\n\tserializeRecord,\n\tserializeRowVersion,\n} from '@korajs/store/internal'\n\n/**\n * Materialize a merge-package referential side effect as an operation log entry + SQL commands.\n */\nexport async function buildSideEffectEntry(\n\tctx: LocalMutationContext,\n\teffect: SideEffectOp,\n\tparentOpId: string,\n\ttransactionId?: string,\n\tmutationName?: string,\n): Promise<TransactionBufferedEntry> {\n\tconst causalDeps = [parentOpId]\n\tconst baseInput = {\n\t\tnodeId: ctx.nodeId,\n\t\tcollection: effect.collection,\n\t\trecordId: effect.recordId,\n\t\tsequenceNumber: await ctx.allocateSequenceNumber(),\n\t\tcausalDeps,\n\t\tschemaVersion: ctx.schema.version,\n\t\t...(transactionId !== undefined ? { transactionId } : {}),\n\t\t...(mutationName !== undefined ? { mutationName } : {}),\n\t}\n\n\tif (effect.type === 'delete') {\n\t\tconst operation = await createOperation(\n\t\t\t{\n\t\t\t\t...baseInput,\n\t\t\t\ttype: 'delete',\n\t\t\t\tdata: null,\n\t\t\t\tpreviousData: effect.previousData,\n\t\t\t},\n\t\t\tctx.clock,\n\t\t)\n\t\tctx.causalTracker?.afterOperation(effect.collection, operation.id, ctx.inTransaction)\n\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\tconst deleteQuery = buildSoftDeleteQuery(\n\t\t\teffect.collection,\n\t\t\teffect.recordId,\n\t\t\toperation.timestamp.wallTime,\n\t\t\tversion,\n\t\t)\n\t\tconst opInsert = buildInsertQuery(\n\t\t\t`_kora_ops_${effect.collection}`,\n\t\t\tserializeOperation(operation) as unknown as Record<string, unknown>,\n\t\t)\n\n\t\treturn {\n\t\t\toperation,\n\t\t\tcollection: effect.collection,\n\t\t\tcommands: [\n\t\t\t\t{ sql: deleteQuery.sql, params: deleteQuery.params },\n\t\t\t\t{ sql: opInsert.sql, params: opInsert.params },\n\t\t\t\t{\n\t\t\t\t\tsql: 'INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)',\n\t\t\t\t\tparams: [ctx.nodeId, operation.sequenceNumber],\n\t\t\t\t},\n\t\t\t],\n\t\t}\n\t}\n\n\tconst operation = await createOperation(\n\t\t{\n\t\t\t...baseInput,\n\t\t\ttype: 'update',\n\t\t\tdata: effect.data,\n\t\t\tpreviousData: effect.previousData,\n\t\t},\n\t\tctx.clock,\n\t)\n\tctx.causalTracker?.afterOperation(effect.collection, operation.id, ctx.inTransaction)\n\n\tconst definition = ctx.schema.collections[effect.collection]\n\tconst serializedChanges =\n\t\teffect.data && definition\n\t\t\t? serializeRecord(effect.data, definition.fields)\n\t\t\t: (effect.data ?? {})\n\tconst version = serializeRowVersion(operation.timestamp)\n\tconst updateQuery = buildUpdateQuery(effect.collection, effect.recordId, {\n\t\t...serializedChanges,\n\t\t_updated_at: operation.timestamp.wallTime,\n\t\t_version: version,\n\t})\n\tconst opInsert = buildInsertQuery(\n\t\t`_kora_ops_${effect.collection}`,\n\t\tserializeOperation(operation) as unknown as Record<string, unknown>,\n\t)\n\n\treturn {\n\t\toperation,\n\t\tcollection: effect.collection,\n\t\tcommands: [\n\t\t\t{ sql: updateQuery.sql, params: updateQuery.params },\n\t\t\t{ sql: opInsert.sql, params: opInsert.params },\n\t\t\t{\n\t\t\t\tsql: 'INSERT OR REPLACE INTO _kora_version_vector (node_id, sequence_number) VALUES (?, ?)',\n\t\t\t\tparams: [ctx.nodeId, operation.sequenceNumber],\n\t\t\t},\n\t\t],\n\t}\n}\n","import type { KoraEvent, KoraEventEmitter } from '@korajs/core'\nimport type { Store } from '@korajs/store'\nimport { persistedAuditTraceFromEvent } from '@korajs/store'\n\nconst AUDIT_EVENT_TYPES = ['merge:completed', 'merge:conflict', 'constraint:violated'] as const\n\ntype AuditEventType = (typeof AUDIT_EVENT_TYPES)[number]\n\nfunction isAuditEvent(event: KoraEvent): event is Extract<KoraEvent, { type: AuditEventType }> {\n\treturn (AUDIT_EVENT_TYPES as readonly string[]).includes(event.type)\n}\n\n/**\n * Persist merge and constraint traces from the event bus to `_kora_audit_traces`.\n */\nexport function wireAuditPersistence(store: Store, emitter: KoraEventEmitter): () => void {\n\tconst unsubscribers = AUDIT_EVENT_TYPES.map((eventType) =>\n\t\temitter.on(eventType, (event) => {\n\t\t\tif (!isAuditEvent(event)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst trace = persistedAuditTraceFromEvent(event)\n\t\t\tvoid store.appendAuditTrace(trace).catch(() => {\n\t\t\t\t// Audit persistence must not break mutations or sync.\n\t\t\t})\n\t\t}),\n\t)\n\n\treturn () => {\n\t\tfor (const unsub of unsubscribers) {\n\t\t\tunsub()\n\t\t}\n\t}\n}\n","import { type BlobRef, type KoraEventEmitter, isBlobRef } from '@korajs/core'\nimport { type ContentAddressedBlobStore, parseBlobManifest } from '@korajs/store'\nimport type { SyncEngine } from '@korajs/sync'\n\n/** Collect the blob references (with a manifest) carried by an operation's data. */\nfunction blobRefsInData(data: Record<string, unknown> | null): BlobRef[] {\n\tif (!data) {\n\t\treturn []\n\t}\n\tconst refs: BlobRef[] = []\n\tfor (const value of Object.values(data)) {\n\t\tif (isBlobRef(value) && value.manifestHash !== undefined) {\n\t\t\trefs.push(value)\n\t\t}\n\t}\n\treturn refs\n}\n\n/**\n * Upload a single blob (its manifest and every chunk) to the server so the bytes\n * remain available after this device disconnects. Reads the already-staged bytes\n * from the local blob store; skips anything the store does not hold.\n */\nasync function uploadBlob(\n\tref: BlobRef,\n\tsyncEngine: SyncEngine,\n\tblobStore: ContentAddressedBlobStore,\n): Promise<void> {\n\tconst manifestHash = ref.manifestHash\n\tif (manifestHash === undefined) {\n\t\treturn\n\t}\n\tconst manifestBytes = await blobStore.get(manifestHash)\n\tif (manifestBytes === null) {\n\t\treturn\n\t}\n\t// Upload chunks first so the manifest a peer resolves is never dangling.\n\tconst manifest = parseBlobManifest(manifestBytes)\n\tconst seen = new Set<string>()\n\tfor (const chunkHash of manifest.chunkHashes) {\n\t\tif (seen.has(chunkHash)) {\n\t\t\tcontinue\n\t\t}\n\t\tseen.add(chunkHash)\n\t\tconst bytes = await blobStore.get(chunkHash)\n\t\tif (bytes !== null) {\n\t\t\tsyncEngine.uploadBlobChunk(chunkHash, bytes)\n\t\t}\n\t}\n\tsyncEngine.uploadBlobChunk(manifestHash, manifestBytes)\n}\n\n/**\n * Automatically upload the bytes behind `blob` fields to the server as their\n * operations are synced. Triggered on `sync:sent`, so a blob authored offline is\n * uploaded when its operation is finally pushed on reconnect — the same event\n * that makes the reference visible to other devices. Each blob is uploaded once\n * per session (deduplicated by manifest hash).\n *\n * A no-op unless the connected server advertised central blob storage.\n *\n * @returns An unsubscribe function.\n */\nexport function wireBlobUpload(\n\temitter: KoraEventEmitter,\n\tsyncEngine: SyncEngine,\n\tblobStore: ContentAddressedBlobStore,\n): () => void {\n\tconst uploaded = new Set<string>()\n\treturn emitter.on('sync:sent', (event) => {\n\t\tif (!syncEngine.isBlobStorageEnabled()) {\n\t\t\treturn\n\t\t}\n\t\tfor (const op of event.operations) {\n\t\t\tfor (const ref of blobRefsInData(op.data)) {\n\t\t\t\tconst manifestHash = ref.manifestHash\n\t\t\t\tif (manifestHash === undefined || uploaded.has(manifestHash)) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tuploaded.add(manifestHash)\n\t\t\t\tvoid uploadBlob(ref, syncEngine, blobStore).catch(() => {\n\t\t\t\t\t// Upload is best-effort; a failure just means the blob is served\n\t\t\t\t\t// peer-to-peer until a later successful upload. Allow a retry.\n\t\t\t\t\tuploaded.delete(manifestHash)\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t})\n}\n","import type { ChunkMessage, ChunkMessagePort } from '@korajs/store'\nimport type { SyncEngine } from '@korajs/sync'\n\n/**\n * Bind the store's transport-agnostic {@link ChunkMessagePort} to a live sync\n * connection. The returned port carries blob chunk requests and responses over\n * the same WebSocket the sync engine already owns, so blob bytes transfer out of\n * band from the operation stream without a second connection.\n *\n * Pair it with `@korajs/store`'s `createRemoteChunkProvider(port)` to pull a\n * blob's chunks from peers (or the server), and `serveBlobChunks(port, store)`\n * to answer requests for chunks this device holds. The sync engine's blob-chunk\n * channel handles the base64 wire encoding; this adapter only maps message\n * shapes, which are identical on both sides.\n *\n * @param syncEngine - The client's sync engine (from `app.getSyncEngine()`)\n * @returns A `ChunkMessagePort` over the sync connection\n *\n * @example\n * ```typescript\n * const port = createSyncEngineChunkPort(app.getSyncEngine())\n * const provider = createRemoteChunkProvider(port)\n * const result = await receiveBlob(manifest, provider, { chunkStore, blobStore })\n * ```\n */\nexport function createSyncEngineChunkPort(syncEngine: SyncEngine): ChunkMessagePort {\n\tconst channel = syncEngine.getBlobChunkChannel()\n\treturn {\n\t\tsend(message: ChunkMessage): void {\n\t\t\t// ChunkMessage and the channel's decoded message shape are identical\n\t\t\t// (request: {requestId, hash}; response: {requestId, bytes}).\n\t\t\tchannel.send(message)\n\t\t},\n\t\tonMessage(handler: (message: ChunkMessage) => void): void {\n\t\t\tchannel.onMessage((decoded) => {\n\t\t\t\t// The store's ChunkMessagePort speaks only request/response. Uploads\n\t\t\t\t// (blob-chunk-push) are a separate server-ingest concern, not routed here.\n\t\t\t\tif (decoded.type === 'blob-chunk-request' || decoded.type === 'blob-chunk-response') {\n\t\t\t\t\thandler(decoded)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t}\n}\n","import type { KoraEventEmitter, Operation, VersionVector } from '@korajs/core'\nimport type { MergeEngine } from '@korajs/merge'\nimport type { Store } from '@korajs/store'\nimport type { ApplyResult, SyncStore } from '@korajs/sync'\nimport { ApplyPipeline } from './apply-pipeline'\n\nexport interface MergeAwareSyncStoreOptions {\n\t/** Increments SyncEngine conflict counter when merge runs on a conflicting update. */\n\tonMergeConflict?: () => void\n}\n\n/**\n * Wraps a Store to route remote sync operations through {@link ApplyPipeline}.\n *\n * Ensures remote deletes honor referential integrity (cascade, set-null, restrict)\n * and remote updates use the full three-tier merge engine with constraint context.\n */\nexport class MergeAwareSyncStore implements SyncStore {\n\tprivate readonly pipeline: ApplyPipeline\n\n\tconstructor(\n\t\tprivate readonly store: Store,\n\t\tmergeEngine: MergeEngine,\n\t\temitter: KoraEventEmitter | null,\n\t\toptions?: MergeAwareSyncStoreOptions,\n\t) {\n\t\tthis.pipeline = new ApplyPipeline({\n\t\t\tstore,\n\t\t\tmergeEngine,\n\t\t\temitter,\n\t\t\tonMergeConflict: options?.onMergeConflict,\n\t\t})\n\t}\n\n\tgetVersionVector(): VersionVector {\n\t\treturn this.store.getVersionVector()\n\t}\n\n\tgetNodeId(): string {\n\t\treturn this.store.getNodeId()\n\t}\n\n\tasync getOperationRange(nodeId: string, fromSeq: number, toSeq: number): Promise<Operation[]> {\n\t\treturn this.store.getOperationRange(nodeId, fromSeq, toSeq)\n\t}\n\n\t/**\n\t * Read a record's current field values (including a soft-deleted row) so the sync\n\t * engine can backfill scope / query-subset fields a partial update or delete does\n\t * not restate, and thus never wrongly drop an in-scope edit from sync.\n\t */\n\tasync readRecordFields(\n\t\tcollection: string,\n\t\trecordId: string,\n\t): Promise<Record<string, unknown> | null> {\n\t\tconst snapshot = await this.store.findMaterializedRow(collection, recordId)\n\t\treturn snapshot ? snapshot.record : null\n\t}\n\n\tasync applyRemoteOperation(op: Operation): Promise<ApplyResult> {\n\t\treturn this.pipeline.applyRemote(op)\n\t}\n\n\tasync applyScopeRetraction(collection: string, recordId: string): Promise<void> {\n\t\treturn this.store.applyScopeRetraction(collection, recordId)\n\t}\n\n\tasync applyScopeNarrowing(\n\t\tscopes: Record<string, Record<string, unknown>>,\n\t): Promise<Array<{ collection: string; recordId: string }>> {\n\t\treturn this.store.applyScopeNarrowing(scopes)\n\t}\n\n\t/**\n\t * Delegates timestamp rebase to the store so the sync engine can re-stamp\n\t * never-acknowledged operations after a fast device clock is corrected.\n\t */\n\tasync rebaseUnsyncedOperations(\n\t\tids: string[],\n\t\tcorrectedNowMs: number,\n\t): Promise<{ operations: Operation[]; idMapping: Record<string, string>; rebasedCount: number }> {\n\t\treturn this.store.rebaseUnsyncedOperations(ids, correctedNowMs)\n\t}\n}\n","import type { Operation } from '@korajs/core'\nimport type { StorageAdapter } from '@korajs/store'\nimport { deserializeOperation, serializeOperation } from '@korajs/store/internal'\nimport type { OperationRow } from '@korajs/store/internal'\nimport type { QueueStorage } from '@korajs/sync'\n\ninterface SyncQueueRow {\n\tid: string\n\tpayload: string\n}\n\ntype QueuePayload = OperationRow & { _collection: string }\n\n/**\n * Persists the outbound sync queue in `_kora_sync_queue` via the local StorageAdapter.\n */\nexport class StoreQueueStorage implements QueueStorage {\n\tconstructor(private readonly adapter: StorageAdapter) {}\n\n\tasync load(): Promise<Operation[]> {\n\t\tconst rows = await this.adapter.query<SyncQueueRow>(\n\t\t\t'SELECT id, payload FROM _kora_sync_queue ORDER BY rowid ASC',\n\t\t)\n\t\treturn rows.map((row) => operationFromQueuePayload(row.payload))\n\t}\n\n\tasync enqueue(op: Operation): Promise<void> {\n\t\tconst row = serializeOperation(op)\n\t\tconst payload: QueuePayload = { ...row, _collection: op.collection }\n\t\tawait this.adapter.execute(\n\t\t\t'INSERT OR REPLACE INTO _kora_sync_queue (id, payload) VALUES (?, ?)',\n\t\t\t[op.id, JSON.stringify(payload)],\n\t\t)\n\t}\n\n\tasync dequeue(ids: string[]): Promise<void> {\n\t\tif (ids.length === 0) return\n\t\tconst placeholders = ids.map(() => '?').join(', ')\n\t\tawait this.adapter.execute(`DELETE FROM _kora_sync_queue WHERE id IN (${placeholders})`, ids)\n\t}\n\n\tasync count(): Promise<number> {\n\t\tconst rows = await this.adapter.query<{ cnt: number }>(\n\t\t\t'SELECT COUNT(*) as cnt FROM _kora_sync_queue',\n\t\t)\n\t\treturn rows[0]?.cnt ?? 0\n\t}\n}\n\nfunction operationFromQueuePayload(payload: string): Operation {\n\tconst parsed = JSON.parse(payload) as QueuePayload\n\tconst op = deserializeOperation(parsed)\n\treturn {\n\t\t...op,\n\t\tcollection: parsed._collection,\n\t}\n}\n","import type { Operation, VersionVector } from '@korajs/core'\nimport { mergeVersionVectors } from '@korajs/store'\nimport type { Store } from '@korajs/store'\nimport { decodeDeltaCursor, encodeDeltaCursor, operationMatchesScope } from '@korajs/sync'\nimport type { DeltaCursor, SyncScopeMap, SyncStatePersistence } from '@korajs/sync'\n\n/**\n * Persists and queries sync acknowledgment state via the local Store.\n */\nexport class StoreSyncStatePersistence implements SyncStatePersistence {\n\tconstructor(\n\t\tprivate readonly store: Store,\n\t\tprivate readonly scope?: SyncScopeMap,\n\t) {}\n\n\tloadLastAckedServerVector(): Promise<VersionVector> {\n\t\treturn this.store.loadLastAckedServerVector()\n\t}\n\n\tsaveLastAckedServerVector(vector: VersionVector): Promise<void> {\n\t\treturn this.store.saveLastAckedServerVector(vector)\n\t}\n\n\tmergeServerVectors(a: VersionVector, b: VersionVector): VersionVector {\n\t\treturn this.store.mergeServerVectors(a, b)\n\t}\n\n\tasync countUnsyncedOperations(serverVector: VersionVector): Promise<number> {\n\t\tconst ops = await this.getUnsyncedOperations(serverVector)\n\t\treturn ops.length\n\t}\n\n\tasync getUnsyncedOperations(serverVector: VersionVector): Promise<Operation[]> {\n\t\tconst ops = await this.store.getUnsyncedOperations(serverVector)\n\t\treturn ops.filter((op) => operationMatchesScope(op, this.scope))\n\t}\n\n\tloadDeltaCursor(): Promise<DeltaCursor | null> {\n\t\treturn this.store.loadDeltaCursor().then((encoded) => decodeDeltaCursor(encoded))\n\t}\n\n\tasync saveDeltaCursor(cursor: DeltaCursor | null): Promise<void> {\n\t\tawait this.store.saveDeltaCursor(cursor ? encodeDeltaCursor(cursor) : null)\n\t}\n\n\tloadDeliveryWatermark(signature: string): Promise<number> {\n\t\treturn this.store.loadDeliveryWatermark(signature)\n\t}\n\n\tasync saveDeliveryWatermark(signature: string, watermark: number): Promise<void> {\n\t\tawait this.store.saveDeliveryWatermark(signature, watermark)\n\t}\n\n\tloadAllDeliveryWatermarks(): Promise<Record<string, number>> {\n\t\treturn this.store.loadAllDeliveryWatermarks()\n\t}\n\n\tasync deleteDeliveryWatermark(signature: string): Promise<void> {\n\t\tawait this.store.deleteDeliveryWatermark(signature)\n\t}\n}\n"],"mappings":"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","names":["createOperation","operation","version","opInsert","createOperation","ctx","serializeOperation"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1135,6 +1135,12 @@ var MergeAwareSyncStore = class {
|
|
|
1135
1135
|
async applyRemoteOperation(op2) {
|
|
1136
1136
|
return this.pipeline.applyRemote(op2);
|
|
1137
1137
|
}
|
|
1138
|
+
async applyScopeRetraction(collection, recordId) {
|
|
1139
|
+
return this.store.applyScopeRetraction(collection, recordId);
|
|
1140
|
+
}
|
|
1141
|
+
async applyScopeNarrowing(scopes) {
|
|
1142
|
+
return this.store.applyScopeNarrowing(scopes);
|
|
1143
|
+
}
|
|
1138
1144
|
/**
|
|
1139
1145
|
* Delegates timestamp rebase to the store so the sync engine can re-stamp
|
|
1140
1146
|
* never-acknowledged operations after a fast device clock is corrected.
|
|
@@ -1317,6 +1323,7 @@ function createSyncQuerySubscriptionHook(getSyncEngine) {
|
|
|
1317
1323
|
// src/initialize-app.ts
|
|
1318
1324
|
async function initializeApp(config, emitter, mergeEngine) {
|
|
1319
1325
|
const adapterType = config.store?.adapter ?? detectAdapterType();
|
|
1326
|
+
let effectiveAdapterType = adapterType;
|
|
1320
1327
|
const baseDbName = config.store?.name ?? "kora-db";
|
|
1321
1328
|
const authBinding = config.sync?.authClient ?? null;
|
|
1322
1329
|
const authUserId = config.store?.namespaceByAuthUser && authBinding?.resolveUserId ? await authBinding.resolveUserId() : void 0;
|
|
@@ -1357,6 +1364,7 @@ async function initializeApp(config, emitter, mergeEngine) {
|
|
|
1357
1364
|
emitter,
|
|
1358
1365
|
config.store?.workerResponseTimeoutMs
|
|
1359
1366
|
);
|
|
1367
|
+
effectiveAdapterType = "indexeddb";
|
|
1360
1368
|
store = buildStore(adapter);
|
|
1361
1369
|
await store.open();
|
|
1362
1370
|
emitter.emit({
|
|
@@ -1368,6 +1376,7 @@ async function initializeApp(config, emitter, mergeEngine) {
|
|
|
1368
1376
|
message: `OPFS persistence is unavailable (${fallbackReason}) for database "${dbName}"; Kora is using durable IndexedDB instead.`
|
|
1369
1377
|
});
|
|
1370
1378
|
} catch {
|
|
1379
|
+
effectiveAdapterType = "sqlite-wasm";
|
|
1371
1380
|
adapter = await createAdapter(
|
|
1372
1381
|
"sqlite-wasm",
|
|
1373
1382
|
dbName,
|
|
@@ -1413,6 +1422,9 @@ async function initializeApp(config, emitter, mergeEngine) {
|
|
|
1413
1422
|
url: config.sync.url,
|
|
1414
1423
|
transport: config.sync.transport,
|
|
1415
1424
|
auth: syncAuth,
|
|
1425
|
+
authState: authBinding?.resolveSyncState,
|
|
1426
|
+
querySubsets: config.sync.querySubsets,
|
|
1427
|
+
scopeExit: config.sync.scopeExit,
|
|
1416
1428
|
batchSize: config.sync.batchSize,
|
|
1417
1429
|
schemaVersion: config.sync.schemaVersion ?? config.schema.version,
|
|
1418
1430
|
scopeMap,
|
|
@@ -1449,7 +1461,15 @@ async function initializeApp(config, emitter, mergeEngine) {
|
|
|
1449
1461
|
unsubscribeLocalOperations,
|
|
1450
1462
|
authBinding,
|
|
1451
1463
|
blobStore,
|
|
1452
|
-
blobChunkProvider
|
|
1464
|
+
blobChunkProvider,
|
|
1465
|
+
storeInfo: {
|
|
1466
|
+
baseName: baseDbName,
|
|
1467
|
+
databaseName: dbName,
|
|
1468
|
+
authUserId: authUserId ?? null,
|
|
1469
|
+
persistence: effectiveAdapterType,
|
|
1470
|
+
durable: adapter.getStorageOpenState?.()?.persistent ?? adapterType !== "sqlite-wasm",
|
|
1471
|
+
isolationState: "ready"
|
|
1472
|
+
}
|
|
1453
1473
|
};
|
|
1454
1474
|
}
|
|
1455
1475
|
function emitOpfsUnavailable(emitter, dbName, reason) {
|
|
@@ -1573,6 +1593,66 @@ function createSyncControl(options) {
|
|
|
1573
1593
|
state.syncStatusBridge?.refresh();
|
|
1574
1594
|
}
|
|
1575
1595
|
},
|
|
1596
|
+
async setQuerySubsets(subsets) {
|
|
1597
|
+
await ready;
|
|
1598
|
+
state.syncEngine?.setQuerySubsets(subsets);
|
|
1599
|
+
},
|
|
1600
|
+
async waitForSettled(options2 = {}) {
|
|
1601
|
+
await ready;
|
|
1602
|
+
const upload = options2.upload ?? true;
|
|
1603
|
+
const download = options2.download ?? "active-view";
|
|
1604
|
+
const classify = () => {
|
|
1605
|
+
const status = state.syncEngine?.getStatus() ?? offlineSyncStatus();
|
|
1606
|
+
if (status.phase === "suspended")
|
|
1607
|
+
return { outcome: "suspended", reason: status.reason ?? "suspended", status };
|
|
1608
|
+
if (status.blockedFailure)
|
|
1609
|
+
return { outcome: "blocked", failure: status.blockedFailure, status };
|
|
1610
|
+
if (status.phase === "offline") return { outcome: "offline", status };
|
|
1611
|
+
const uploadDone = !upload || status.pendingOperations === 0 && (status.inFlightUploadOperations ?? 0) === 0;
|
|
1612
|
+
const downloadDone = download === false || status.activeViewComplete;
|
|
1613
|
+
return uploadDone && downloadDone ? { outcome: "settled", status } : null;
|
|
1614
|
+
};
|
|
1615
|
+
return await new Promise((resolve) => {
|
|
1616
|
+
let done = false;
|
|
1617
|
+
let unsubscribeReady = false;
|
|
1618
|
+
let cleanupPending = false;
|
|
1619
|
+
let timer = null;
|
|
1620
|
+
let unsubscribe = () => {
|
|
1621
|
+
};
|
|
1622
|
+
const finish = (result) => {
|
|
1623
|
+
if (done) return;
|
|
1624
|
+
done = true;
|
|
1625
|
+
if (unsubscribeReady) unsubscribe();
|
|
1626
|
+
else cleanupPending = true;
|
|
1627
|
+
if (timer) clearTimeout(timer);
|
|
1628
|
+
options2.signal?.removeEventListener("abort", onAbort);
|
|
1629
|
+
resolve(result);
|
|
1630
|
+
};
|
|
1631
|
+
const check = () => {
|
|
1632
|
+
const result = classify();
|
|
1633
|
+
if (result) finish(result);
|
|
1634
|
+
};
|
|
1635
|
+
const onAbort = () => finish({
|
|
1636
|
+
outcome: "aborted",
|
|
1637
|
+
status: state.syncEngine?.getStatus() ?? offlineSyncStatus()
|
|
1638
|
+
});
|
|
1639
|
+
unsubscribe = state.syncStatusBridge?.subscribe(check) ?? (() => {
|
|
1640
|
+
});
|
|
1641
|
+
unsubscribeReady = true;
|
|
1642
|
+
if (cleanupPending) unsubscribe();
|
|
1643
|
+
options2.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1644
|
+
if (options2.timeoutMs !== void 0)
|
|
1645
|
+
timer = setTimeout(
|
|
1646
|
+
() => finish({
|
|
1647
|
+
outcome: "timeout",
|
|
1648
|
+
status: state.syncEngine?.getStatus() ?? offlineSyncStatus()
|
|
1649
|
+
}),
|
|
1650
|
+
options2.timeoutMs
|
|
1651
|
+
);
|
|
1652
|
+
if (options2.signal?.aborted) onAbort();
|
|
1653
|
+
else check();
|
|
1654
|
+
});
|
|
1655
|
+
},
|
|
1576
1656
|
getStatus() {
|
|
1577
1657
|
if (state.syncEngine) {
|
|
1578
1658
|
return state.syncEngine.getStatus();
|
|
@@ -1609,13 +1689,22 @@ function createSyncControl(options) {
|
|
|
1609
1689
|
state: "disconnected",
|
|
1610
1690
|
status: {
|
|
1611
1691
|
status: "offline",
|
|
1692
|
+
phase: "offline",
|
|
1612
1693
|
reconnecting: false,
|
|
1613
1694
|
pendingOperations: 0,
|
|
1614
1695
|
lastSyncedAt: null,
|
|
1615
1696
|
lastSuccessfulPush: null,
|
|
1616
1697
|
lastSuccessfulPull: null,
|
|
1617
1698
|
conflicts: 0,
|
|
1618
|
-
clockSkewMs: null
|
|
1699
|
+
clockSkewMs: null,
|
|
1700
|
+
inFlightUploadOperations: 0,
|
|
1701
|
+
hasInFlightDeliveryBatch: false,
|
|
1702
|
+
activeViewId: "",
|
|
1703
|
+
activeViewComplete: false,
|
|
1704
|
+
initialSync: { complete: false, receivedBatches: 0, totalBatches: null, progress: null },
|
|
1705
|
+
deliveryWatermark: 0,
|
|
1706
|
+
serverFrontier: null,
|
|
1707
|
+
blockedFailure: null
|
|
1619
1708
|
},
|
|
1620
1709
|
nodeId: "",
|
|
1621
1710
|
url: config.sync?.url ?? "",
|
|
@@ -1675,8 +1764,15 @@ var AuthSyncCoordinator = class {
|
|
|
1675
1764
|
if (!engine) {
|
|
1676
1765
|
return;
|
|
1677
1766
|
}
|
|
1767
|
+
engine.notifyAuthChanged?.();
|
|
1768
|
+
const authState = await this.authBinding.resolveSyncState?.();
|
|
1769
|
+
if (authState?.state === "loading" || authState?.state === "signed-out") {
|
|
1770
|
+
await engine.stop();
|
|
1771
|
+
await engine.start();
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1678
1774
|
const headers = await this.authBinding.auth();
|
|
1679
|
-
if (!headers.token) {
|
|
1775
|
+
if (!headers.token && authState?.state !== "anonymous") {
|
|
1680
1776
|
await engine.stop();
|
|
1681
1777
|
return;
|
|
1682
1778
|
}
|
|
@@ -1952,6 +2048,14 @@ function createApp(config) {
|
|
|
1952
2048
|
let unsubscribeSync = null;
|
|
1953
2049
|
let unsubscribeAudit = null;
|
|
1954
2050
|
let unsubscribeLocalOperations = null;
|
|
2051
|
+
let currentStoreInfo = {
|
|
2052
|
+
baseName: config.store?.name ?? "kora-db",
|
|
2053
|
+
databaseName: "",
|
|
2054
|
+
authUserId: null,
|
|
2055
|
+
persistence: config.store?.adapter ?? "sqlite-wasm",
|
|
2056
|
+
durable: false,
|
|
2057
|
+
isolationState: "closed"
|
|
2058
|
+
};
|
|
1955
2059
|
const syncState = {
|
|
1956
2060
|
syncEngine: null,
|
|
1957
2061
|
syncStatusBridge: null,
|
|
@@ -1975,6 +2079,7 @@ function createApp(config) {
|
|
|
1975
2079
|
config.blob?.chunkSize,
|
|
1976
2080
|
() => enumerateLiveBlobRefs(init.store, config.schema)
|
|
1977
2081
|
);
|
|
2082
|
+
currentStoreInfo = init.storeInfo;
|
|
1978
2083
|
wireSyncLifecycleAfterReady(config, emitter, syncState, init);
|
|
1979
2084
|
});
|
|
1980
2085
|
const getStore = () => store;
|
|
@@ -1985,9 +2090,24 @@ function createApp(config) {
|
|
|
1985
2090
|
return blobApi;
|
|
1986
2091
|
};
|
|
1987
2092
|
const executeTransaction = createTransactionExecutor(config, ready, getStore);
|
|
2093
|
+
const collections = /* @__PURE__ */ Object.create(
|
|
2094
|
+
null
|
|
2095
|
+
);
|
|
2096
|
+
for (const collectionName of Object.keys(config.schema.collections)) {
|
|
2097
|
+
Object.defineProperty(collections, collectionName, {
|
|
2098
|
+
get() {
|
|
2099
|
+
return createCollectionAccessor(collectionName, getStore);
|
|
2100
|
+
},
|
|
2101
|
+
enumerable: true,
|
|
2102
|
+
configurable: false
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
Object.freeze(collections);
|
|
1988
2106
|
const app = {
|
|
1989
2107
|
ready,
|
|
1990
2108
|
events: emitter,
|
|
2109
|
+
on: emitter.on.bind(emitter),
|
|
2110
|
+
collections,
|
|
1991
2111
|
sync: createSyncControl({ config, ready, state: syncState }),
|
|
1992
2112
|
sequences: createSequencesAccessor(ready, getStore),
|
|
1993
2113
|
blobs: {
|
|
@@ -2031,6 +2151,9 @@ function createApp(config) {
|
|
|
2031
2151
|
getQueryStoreCache() {
|
|
2032
2152
|
return queryStoreCache;
|
|
2033
2153
|
},
|
|
2154
|
+
storeInfo() {
|
|
2155
|
+
return { ...currentStoreInfo };
|
|
2156
|
+
},
|
|
2034
2157
|
transaction(fn) {
|
|
2035
2158
|
return executeTransaction(fn);
|
|
2036
2159
|
},
|
|
@@ -2064,6 +2187,7 @@ function createApp(config) {
|
|
|
2064
2187
|
await store.close();
|
|
2065
2188
|
store = null;
|
|
2066
2189
|
}
|
|
2190
|
+
currentStoreInfo = { ...currentStoreInfo, isolationState: "closed" };
|
|
2067
2191
|
emitter.clear();
|
|
2068
2192
|
},
|
|
2069
2193
|
async exportBackup(options) {
|
|
@@ -2095,7 +2219,9 @@ function createApp(config) {
|
|
|
2095
2219
|
return store.exportAudit(options);
|
|
2096
2220
|
}
|
|
2097
2221
|
};
|
|
2222
|
+
const reservedProperties = new Set(Reflect.ownKeys(app));
|
|
2098
2223
|
for (const collectionName of Object.keys(config.schema.collections)) {
|
|
2224
|
+
if (reservedProperties.has(collectionName)) continue;
|
|
2099
2225
|
Object.defineProperty(app, collectionName, {
|
|
2100
2226
|
get() {
|
|
2101
2227
|
return createCollectionAccessor(collectionName, getStore);
|