korajs 1.0.0-beta.4 → 1.0.0-beta.5
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-5FWGKTEO.js → chunk-CHGDHGEI.js} +68 -26
- package/dist/chunk-CHGDHGEI.js.map +1 -0
- package/dist/index.cjs +61 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/testing.cjs +61 -24
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +19 -0
- package/dist/testing.d.ts +19 -0
- package/dist/testing.js +1 -1
- package/package.json +13 -13
- package/dist/chunk-5FWGKTEO.js.map +0 -1
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// src/apply-pipeline.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
HybridLogicalClock,
|
|
4
|
+
KoraError,
|
|
5
|
+
createOperation as createOperation2,
|
|
6
|
+
replayOperationsForRecord
|
|
7
|
+
} from "@korajs/core";
|
|
3
8
|
import { topologicalSort } from "@korajs/core/internal";
|
|
4
9
|
import { buildMergeRelationLookup, checkReferentialIntegrityOnDelete } from "@korajs/merge";
|
|
5
10
|
import { OptimisticLockError, richtextStatesEqual } from "@korajs/store";
|
|
@@ -402,35 +407,22 @@ var ApplyPipeline = class {
|
|
|
402
407
|
if (!snapshot?.deleted) {
|
|
403
408
|
return null;
|
|
404
409
|
}
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
410
|
+
const priorOps = await this.deps.store.getOperationsForRecord(op.collection, op.recordId);
|
|
411
|
+
const ops = [...priorOps, op].sort(
|
|
412
|
+
(a, b) => HybridLogicalClock.compare(a.timestamp, b.timestamp)
|
|
408
413
|
);
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const mergeResult = await this.deps.mergeEngine.merge({
|
|
413
|
-
local: localOp,
|
|
414
|
-
remote: op,
|
|
415
|
-
baseState: op.previousData ?? {},
|
|
416
|
-
collectionDef
|
|
417
|
-
});
|
|
418
|
-
if (mergeResult.appliedOperation === "local") {
|
|
419
|
-
this.emitMergeLifecycle(op, localOp, mergeResult);
|
|
420
|
-
return "skipped";
|
|
414
|
+
const folded = replayOperationsForRecord(ops);
|
|
415
|
+
if (!folded) {
|
|
416
|
+
return this.deps.store.applyRemoteOperation(op, { logOnly: true });
|
|
421
417
|
}
|
|
422
|
-
const
|
|
418
|
+
const newestTimestamp = ops[ops.length - 1].timestamp;
|
|
419
|
+
return this.deps.store.applyRemoteOperation(op, {
|
|
423
420
|
reactivateIfDeleted: true,
|
|
424
421
|
forceMaterialize: true,
|
|
425
|
-
materializeData:
|
|
426
|
-
materializeTimestamp: maxTimestamp(op.timestamp,
|
|
422
|
+
materializeData: folded,
|
|
423
|
+
materializeTimestamp: maxTimestamp(op.timestamp, newestTimestamp),
|
|
427
424
|
guardRowState: guard
|
|
428
425
|
});
|
|
429
|
-
this.emitMergeLifecycle(op, localOp, mergeResult);
|
|
430
|
-
if (applyResult === "applied" && mergeResult.sideEffects.length > 0) {
|
|
431
|
-
await applySideEffectOps(this.deps.store, mergeResult.sideEffects, op.id);
|
|
432
|
-
}
|
|
433
|
-
return applyResult;
|
|
434
426
|
}
|
|
435
427
|
async applyMergedUpdate(op, collectionDef, currentRecord, baseState, guard, applyOptions) {
|
|
436
428
|
const latestLocal = await this.deps.store.getLatestLocalOperationForRecord(
|
|
@@ -461,10 +453,11 @@ var ApplyPipeline = class {
|
|
|
461
453
|
},
|
|
462
454
|
constraintContext
|
|
463
455
|
);
|
|
456
|
+
const mergedData = await this.materializeAtomicFieldsFromLog(op, mergeResult.mergedData);
|
|
464
457
|
const applyResult = await this.deps.store.applyRemoteOperation(op, {
|
|
465
458
|
...applyOptions,
|
|
466
459
|
forceMaterialize: true,
|
|
467
|
-
materializeData:
|
|
460
|
+
materializeData: mergedData,
|
|
468
461
|
materializeTimestamp: maxTimestamp(op.timestamp, localTimestamp),
|
|
469
462
|
guardRowState: guard
|
|
470
463
|
});
|
|
@@ -474,6 +467,34 @@ var ApplyPipeline = class {
|
|
|
474
467
|
}
|
|
475
468
|
return applyResult;
|
|
476
469
|
}
|
|
470
|
+
/**
|
|
471
|
+
* Materialize the operation's atomic-op fields by folding the record's full
|
|
472
|
+
* operation log (in HLC order, including this not-yet-appended op) through the
|
|
473
|
+
* shared atomic-aware replay — the same fold the server uses. Non-atomic fields
|
|
474
|
+
* keep their merge-engine result. This composes a same-type atomic chain and
|
|
475
|
+
* last-write-wins for anything else, so all replicas converge regardless of how
|
|
476
|
+
* many concurrent atomic writers there were or which device authored them.
|
|
477
|
+
*/
|
|
478
|
+
async materializeAtomicFieldsFromLog(op, mergedData) {
|
|
479
|
+
if (!op.atomicOps || Object.keys(op.atomicOps).length === 0) {
|
|
480
|
+
return mergedData;
|
|
481
|
+
}
|
|
482
|
+
const priorOps = await this.deps.store.getOperationsForRecord(op.collection, op.recordId);
|
|
483
|
+
const ops = [...priorOps, op].sort(
|
|
484
|
+
(a, b) => HybridLogicalClock.compare(a.timestamp, b.timestamp)
|
|
485
|
+
);
|
|
486
|
+
const folded = replayOperationsForRecord(ops);
|
|
487
|
+
if (!folded) {
|
|
488
|
+
return mergedData;
|
|
489
|
+
}
|
|
490
|
+
const result = { ...mergedData };
|
|
491
|
+
for (const field of Object.keys(op.atomicOps)) {
|
|
492
|
+
if (field in folded) {
|
|
493
|
+
result[field] = folded[field];
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
477
498
|
async applyRemoteInsert(op) {
|
|
478
499
|
const collectionDef = this.deps.store.getSchema().collections[op.collection];
|
|
479
500
|
if (!collectionDef || !op.data) {
|
|
@@ -792,6 +813,15 @@ var MergeAwareSyncStore = class {
|
|
|
792
813
|
async getOperationRange(nodeId, fromSeq, toSeq) {
|
|
793
814
|
return this.store.getOperationRange(nodeId, fromSeq, toSeq);
|
|
794
815
|
}
|
|
816
|
+
/**
|
|
817
|
+
* Read a record's current field values (including a soft-deleted row) so the sync
|
|
818
|
+
* engine can backfill scope / query-subset fields a partial update or delete does
|
|
819
|
+
* not restate, and thus never wrongly drop an in-scope edit from sync.
|
|
820
|
+
*/
|
|
821
|
+
async readRecordFields(collection, recordId) {
|
|
822
|
+
const snapshot = await this.store.findMaterializedRow(collection, recordId);
|
|
823
|
+
return snapshot ? snapshot.record : null;
|
|
824
|
+
}
|
|
795
825
|
async applyRemoteOperation(op) {
|
|
796
826
|
return this.pipeline.applyRemote(op);
|
|
797
827
|
}
|
|
@@ -879,6 +909,18 @@ var StoreSyncStatePersistence = class {
|
|
|
879
909
|
async saveDeltaCursor(cursor) {
|
|
880
910
|
await this.store.saveDeltaCursor(cursor ? encodeDeltaCursor(cursor) : null);
|
|
881
911
|
}
|
|
912
|
+
loadDeliveryWatermark(signature) {
|
|
913
|
+
return this.store.loadDeliveryWatermark(signature);
|
|
914
|
+
}
|
|
915
|
+
async saveDeliveryWatermark(signature, watermark) {
|
|
916
|
+
await this.store.saveDeliveryWatermark(signature, watermark);
|
|
917
|
+
}
|
|
918
|
+
loadAllDeliveryWatermarks() {
|
|
919
|
+
return this.store.loadAllDeliveryWatermarks();
|
|
920
|
+
}
|
|
921
|
+
async deleteDeliveryWatermark(signature) {
|
|
922
|
+
await this.store.deleteDeliveryWatermark(signature);
|
|
923
|
+
}
|
|
882
924
|
};
|
|
883
925
|
|
|
884
926
|
export {
|
|
@@ -890,4 +932,4 @@ export {
|
|
|
890
932
|
StoreQueueStorage,
|
|
891
933
|
StoreSyncStatePersistence
|
|
892
934
|
};
|
|
893
|
-
//# sourceMappingURL=chunk-
|
|
935
|
+
//# sourceMappingURL=chunk-CHGDHGEI.js.map
|
|
@@ -0,0 +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":"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","names":["createOperation","operation","version","opInsert","createOperation","ctx","serializeOperation"]}
|
package/dist/index.cjs
CHANGED
|
@@ -639,35 +639,22 @@ var ApplyPipeline = class {
|
|
|
639
639
|
if (!snapshot?.deleted) {
|
|
640
640
|
return null;
|
|
641
641
|
}
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
642
|
+
const priorOps = await this.deps.store.getOperationsForRecord(op2.collection, op2.recordId);
|
|
643
|
+
const ops = [...priorOps, op2].sort(
|
|
644
|
+
(a, b) => import_core3.HybridLogicalClock.compare(a.timestamp, b.timestamp)
|
|
645
645
|
);
|
|
646
|
-
|
|
647
|
-
|
|
646
|
+
const folded = (0, import_core3.replayOperationsForRecord)(ops);
|
|
647
|
+
if (!folded) {
|
|
648
|
+
return this.deps.store.applyRemoteOperation(op2, { logOnly: true });
|
|
648
649
|
}
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
remote: op2,
|
|
652
|
-
baseState: op2.previousData ?? {},
|
|
653
|
-
collectionDef
|
|
654
|
-
});
|
|
655
|
-
if (mergeResult.appliedOperation === "local") {
|
|
656
|
-
this.emitMergeLifecycle(op2, localOp, mergeResult);
|
|
657
|
-
return "skipped";
|
|
658
|
-
}
|
|
659
|
-
const applyResult = await this.deps.store.applyRemoteOperation(op2, {
|
|
650
|
+
const newestTimestamp = ops[ops.length - 1].timestamp;
|
|
651
|
+
return this.deps.store.applyRemoteOperation(op2, {
|
|
660
652
|
reactivateIfDeleted: true,
|
|
661
653
|
forceMaterialize: true,
|
|
662
|
-
materializeData:
|
|
663
|
-
materializeTimestamp: maxTimestamp(op2.timestamp,
|
|
654
|
+
materializeData: folded,
|
|
655
|
+
materializeTimestamp: maxTimestamp(op2.timestamp, newestTimestamp),
|
|
664
656
|
guardRowState: guard
|
|
665
657
|
});
|
|
666
|
-
this.emitMergeLifecycle(op2, localOp, mergeResult);
|
|
667
|
-
if (applyResult === "applied" && mergeResult.sideEffects.length > 0) {
|
|
668
|
-
await applySideEffectOps(this.deps.store, mergeResult.sideEffects, op2.id);
|
|
669
|
-
}
|
|
670
|
-
return applyResult;
|
|
671
658
|
}
|
|
672
659
|
async applyMergedUpdate(op2, collectionDef, currentRecord, baseState, guard, applyOptions) {
|
|
673
660
|
const latestLocal = await this.deps.store.getLatestLocalOperationForRecord(
|
|
@@ -698,10 +685,11 @@ var ApplyPipeline = class {
|
|
|
698
685
|
},
|
|
699
686
|
constraintContext
|
|
700
687
|
);
|
|
688
|
+
const mergedData = await this.materializeAtomicFieldsFromLog(op2, mergeResult.mergedData);
|
|
701
689
|
const applyResult = await this.deps.store.applyRemoteOperation(op2, {
|
|
702
690
|
...applyOptions,
|
|
703
691
|
forceMaterialize: true,
|
|
704
|
-
materializeData:
|
|
692
|
+
materializeData: mergedData,
|
|
705
693
|
materializeTimestamp: maxTimestamp(op2.timestamp, localTimestamp),
|
|
706
694
|
guardRowState: guard
|
|
707
695
|
});
|
|
@@ -711,6 +699,34 @@ var ApplyPipeline = class {
|
|
|
711
699
|
}
|
|
712
700
|
return applyResult;
|
|
713
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* Materialize the operation's atomic-op fields by folding the record's full
|
|
704
|
+
* operation log (in HLC order, including this not-yet-appended op) through the
|
|
705
|
+
* shared atomic-aware replay — the same fold the server uses. Non-atomic fields
|
|
706
|
+
* keep their merge-engine result. This composes a same-type atomic chain and
|
|
707
|
+
* last-write-wins for anything else, so all replicas converge regardless of how
|
|
708
|
+
* many concurrent atomic writers there were or which device authored them.
|
|
709
|
+
*/
|
|
710
|
+
async materializeAtomicFieldsFromLog(op2, mergedData) {
|
|
711
|
+
if (!op2.atomicOps || Object.keys(op2.atomicOps).length === 0) {
|
|
712
|
+
return mergedData;
|
|
713
|
+
}
|
|
714
|
+
const priorOps = await this.deps.store.getOperationsForRecord(op2.collection, op2.recordId);
|
|
715
|
+
const ops = [...priorOps, op2].sort(
|
|
716
|
+
(a, b) => import_core3.HybridLogicalClock.compare(a.timestamp, b.timestamp)
|
|
717
|
+
);
|
|
718
|
+
const folded = (0, import_core3.replayOperationsForRecord)(ops);
|
|
719
|
+
if (!folded) {
|
|
720
|
+
return mergedData;
|
|
721
|
+
}
|
|
722
|
+
const result = { ...mergedData };
|
|
723
|
+
for (const field of Object.keys(op2.atomicOps)) {
|
|
724
|
+
if (field in folded) {
|
|
725
|
+
result[field] = folded[field];
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return result;
|
|
729
|
+
}
|
|
714
730
|
async applyRemoteInsert(op2) {
|
|
715
731
|
const collectionDef = this.deps.store.getSchema().collections[op2.collection];
|
|
716
732
|
if (!collectionDef || !op2.data) {
|
|
@@ -1061,6 +1077,15 @@ var MergeAwareSyncStore = class {
|
|
|
1061
1077
|
async getOperationRange(nodeId, fromSeq, toSeq) {
|
|
1062
1078
|
return this.store.getOperationRange(nodeId, fromSeq, toSeq);
|
|
1063
1079
|
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Read a record's current field values (including a soft-deleted row) so the sync
|
|
1082
|
+
* engine can backfill scope / query-subset fields a partial update or delete does
|
|
1083
|
+
* not restate, and thus never wrongly drop an in-scope edit from sync.
|
|
1084
|
+
*/
|
|
1085
|
+
async readRecordFields(collection, recordId) {
|
|
1086
|
+
const snapshot = await this.store.findMaterializedRow(collection, recordId);
|
|
1087
|
+
return snapshot ? snapshot.record : null;
|
|
1088
|
+
}
|
|
1064
1089
|
async applyRemoteOperation(op2) {
|
|
1065
1090
|
return this.pipeline.applyRemote(op2);
|
|
1066
1091
|
}
|
|
@@ -1192,6 +1217,18 @@ var StoreSyncStatePersistence = class {
|
|
|
1192
1217
|
async saveDeltaCursor(cursor) {
|
|
1193
1218
|
await this.store.saveDeltaCursor(cursor ? (0, import_sync2.encodeDeltaCursor)(cursor) : null);
|
|
1194
1219
|
}
|
|
1220
|
+
loadDeliveryWatermark(signature) {
|
|
1221
|
+
return this.store.loadDeliveryWatermark(signature);
|
|
1222
|
+
}
|
|
1223
|
+
async saveDeliveryWatermark(signature, watermark) {
|
|
1224
|
+
await this.store.saveDeliveryWatermark(signature, watermark);
|
|
1225
|
+
}
|
|
1226
|
+
loadAllDeliveryWatermarks() {
|
|
1227
|
+
return this.store.loadAllDeliveryWatermarks();
|
|
1228
|
+
}
|
|
1229
|
+
async deleteDeliveryWatermark(signature) {
|
|
1230
|
+
await this.store.deleteDeliveryWatermark(signature);
|
|
1231
|
+
}
|
|
1195
1232
|
};
|
|
1196
1233
|
|
|
1197
1234
|
// src/sync-query-bridge.ts
|