@korajs/merge 0.3.1 → 0.4.0
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/index.cjs +650 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +206 -2
- package/dist/index.d.ts +206 -2
- package/dist/index.js +640 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/strategies/lww.ts","../src/strategies/add-wins-set.ts","../src/strategies/yjs-richtext.ts","../src/engine/field-merger.ts","../src/constraints/constraint-checker.ts","../src/constraints/resolvers.ts","../src/engine/merge-engine.ts"],"sourcesContent":["import { HybridLogicalClock } from '@korajs/core'\nimport type { HLCTimestamp } from '@korajs/core'\n\n/**\n * Result of a Last-Write-Wins comparison.\n */\nexport interface LWWResult {\n\t/** The winning value */\n\tvalue: unknown\n\t/** Which side won */\n\twinner: 'local' | 'remote'\n}\n\n/**\n * Last-Write-Wins merge strategy using HLC timestamps.\n *\n * Compares two values by their HLC timestamps and returns the value with the\n * later timestamp. The HLC total order guarantees a deterministic winner even\n * when wall-clock times and logical counters are identical (nodeId tiebreaker).\n *\n * @param localValue - The local field value\n * @param remoteValue - The remote field value\n * @param localTimestamp - HLC timestamp of the local operation\n * @param remoteTimestamp - HLC timestamp of the remote operation\n * @returns The winning value and which side won\n */\nexport function lastWriteWins(\n\tlocalValue: unknown,\n\tremoteValue: unknown,\n\tlocalTimestamp: HLCTimestamp,\n\tremoteTimestamp: HLCTimestamp,\n): LWWResult {\n\tconst comparison = HybridLogicalClock.compare(localTimestamp, remoteTimestamp)\n\t// HLC total order guarantees comparison is never 0 for different nodeIds.\n\t// If comparison >= 0, local wins (local is later or same node).\n\tif (comparison >= 0) {\n\t\treturn { value: localValue, winner: 'local' }\n\t}\n\treturn { value: remoteValue, winner: 'remote' }\n}\n","/**\n * Add-wins set merge strategy for array fields.\n *\n * When two sides concurrently modify an array, this strategy preserves all\n * additions from both sides. An element is only removed from the result if\n * BOTH sides independently removed it. This prevents data loss: if one side\n * adds an element while another removes a different element, both changes\n * are preserved.\n *\n * Algorithm:\n * added_local = local - base\n * added_remote = remote - base\n * removed_local = base - local\n * removed_remote = base - remote\n * result = (base ∪ added_local ∪ added_remote) - (removed_local ∩ removed_remote)\n *\n * Uses JSON.stringify for element comparison to handle primitives and objects.\n *\n * @param localArray - The local array after local modifications\n * @param remoteArray - The remote array after remote modifications\n * @param baseArray - The array state before either modification\n * @returns The merged array\n */\nexport function addWinsSet(\n\tlocalArray: unknown[],\n\tremoteArray: unknown[],\n\tbaseArray: unknown[],\n): unknown[] {\n\tconst serialize = (v: unknown): string => JSON.stringify(v)\n\n\tconst baseSet = new Set(baseArray.map(serialize))\n\tconst localSet = new Set(localArray.map(serialize))\n\tconst remoteSet = new Set(remoteArray.map(serialize))\n\n\t// Elements added by each side (present in their set but not in base)\n\tconst addedLocal = new Set<string>()\n\tfor (const s of localSet) {\n\t\tif (!baseSet.has(s)) {\n\t\t\taddedLocal.add(s)\n\t\t}\n\t}\n\n\tconst addedRemote = new Set<string>()\n\tfor (const s of remoteSet) {\n\t\tif (!baseSet.has(s)) {\n\t\t\taddedRemote.add(s)\n\t\t}\n\t}\n\n\t// Elements removed by each side (present in base but not in their set)\n\tconst removedLocal = new Set<string>()\n\tfor (const s of baseSet) {\n\t\tif (!localSet.has(s)) {\n\t\t\tremovedLocal.add(s)\n\t\t}\n\t}\n\n\tconst removedRemote = new Set<string>()\n\tfor (const s of baseSet) {\n\t\tif (!remoteSet.has(s)) {\n\t\t\tremovedRemote.add(s)\n\t\t}\n\t}\n\n\t// An element is truly removed only if BOTH sides removed it\n\tconst removedByBoth = new Set<string>()\n\tfor (const s of removedLocal) {\n\t\tif (removedRemote.has(s)) {\n\t\t\tremovedByBoth.add(s)\n\t\t}\n\t}\n\n\t// Result = (base ∪ added_local ∪ added_remote) - removed_by_both\n\t// Maintain order: base elements first (preserving order), then local adds, then remote adds\n\tconst resultSerialized = new Set<string>()\n\tconst result: unknown[] = []\n\n\tconst addIfNew = (serialized: string, value: unknown): void => {\n\t\tif (!resultSerialized.has(serialized) && !removedByBoth.has(serialized)) {\n\t\t\tresultSerialized.add(serialized)\n\t\t\tresult.push(value)\n\t\t}\n\t}\n\n\t// Base elements (in original order, minus those removed by both)\n\tfor (const item of baseArray) {\n\t\taddIfNew(serialize(item), item)\n\t}\n\n\t// Local additions (in order they appear in local array)\n\tfor (const item of localArray) {\n\t\tconst s = serialize(item)\n\t\tif (addedLocal.has(s)) {\n\t\t\taddIfNew(s, item)\n\t\t}\n\t}\n\n\t// Remote additions (in order they appear in remote array)\n\tfor (const item of remoteArray) {\n\t\tconst s = serialize(item)\n\t\tif (addedRemote.has(s)) {\n\t\t\taddIfNew(s, item)\n\t\t}\n\t}\n\n\treturn result\n}\n","import * as Y from 'yjs'\n\nexport type RichtextValue = string | Uint8Array | ArrayBuffer | null | undefined\n\nconst TEXT_KEY = 'content'\n\n/**\n * Merges richtext values using Yjs CRDT updates.\n */\nexport function mergeRichtext(\n\tlocalValue: RichtextValue,\n\tremoteValue: RichtextValue,\n\tbaseValue: RichtextValue,\n): Uint8Array {\n\tconst mergedDoc = new Y.Doc()\n\n\tY.applyUpdate(mergedDoc, toYjsUpdate(baseValue))\n\tY.applyUpdate(mergedDoc, toYjsUpdate(localValue))\n\tY.applyUpdate(mergedDoc, toYjsUpdate(remoteValue))\n\n\treturn Y.encodeStateAsUpdate(mergedDoc)\n}\n\n/**\n * Converts a richtext state update to plain text.\n */\nexport function richtextToString(value: RichtextValue): string {\n\tconst doc = new Y.Doc()\n\tY.applyUpdate(doc, toYjsUpdate(value))\n\treturn doc.getText(TEXT_KEY).toString()\n}\n\n/**\n * Converts a plain string to a Yjs state update.\n */\nexport function stringToRichtextUpdate(value: string): Uint8Array {\n\tconst doc = new Y.Doc()\n\tdoc.getText(TEXT_KEY).insert(0, value)\n\treturn Y.encodeStateAsUpdate(doc)\n}\n\nfunction toYjsUpdate(value: RichtextValue): Uint8Array {\n\tif (value === null || value === undefined) {\n\t\treturn Y.encodeStateAsUpdate(new Y.Doc())\n\t}\n\n\tif (typeof value === 'string') {\n\t\treturn stringToRichtextUpdate(value)\n\t}\n\n\tif (value instanceof Uint8Array) {\n\t\treturn value\n\t}\n\n\tif (value instanceof ArrayBuffer) {\n\t\treturn new Uint8Array(value)\n\t}\n\n\tthrow new Error('Richtext value must be a string, Uint8Array, ArrayBuffer, null, or undefined.')\n}\n","import type { CustomResolver, FieldDescriptor, HLCTimestamp, Operation } from '@korajs/core'\nimport type { MergeTrace } from '@korajs/core'\nimport { addWinsSet } from '../strategies/add-wins-set'\nimport { lastWriteWins } from '../strategies/lww'\nimport { mergeRichtext } from '../strategies/yjs-richtext'\nimport type { RichtextValue } from '../strategies/yjs-richtext'\nimport type { FieldMergeResult } from '../types'\n\n/**\n * Merges a single field from two concurrent operations.\n *\n * Dispatches to the appropriate strategy based on field kind:\n * - string, number, boolean, enum, timestamp → LWW (Last-Write-Wins via HLC)\n * - array → add-wins set (union of additions, only mutual removals)\n * - richtext → Yjs CRDT merge\n *\n * If a custom resolver (Tier 3) is provided, it overrides the default strategy.\n *\n * Handles non-conflict cases where only one side modified the field:\n * - Only local changed → take local value\n * - Only remote changed → take remote value\n * - Neither changed → keep base value\n *\n * @param fieldName - Name of the field being merged\n * @param localOp - The local operation\n * @param remoteOp - The remote operation\n * @param baseState - Full record state before either operation\n * @param fieldDescriptor - Schema descriptor for this field\n * @param resolver - Optional Tier 3 custom resolver for this field\n * @returns The merged field value and a trace for DevTools\n */\nexport function mergeField(\n\tfieldName: string,\n\tlocalOp: Operation,\n\tremoteOp: Operation,\n\tbaseState: Record<string, unknown>,\n\tfieldDescriptor: FieldDescriptor,\n\tresolver?: CustomResolver,\n): FieldMergeResult {\n\tconst startTime = Date.now()\n\n\tconst localData = localOp.data ?? {}\n\tconst remoteData = remoteOp.data ?? {}\n\tconst localPrevious = localOp.previousData ?? {}\n\tconst remotePrevious = remoteOp.previousData ?? {}\n\n\tconst localChanged = fieldName in localData\n\tconst remoteChanged = fieldName in remoteData\n\tconst baseValue = baseState[fieldName]\n\n\t// Non-conflict: only one side changed\n\tif (localChanged && !remoteChanged) {\n\t\treturn createResult(\n\t\t\tlocalData[fieldName],\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tlocalData[fieldName],\n\t\t\tbaseValue,\n\t\t\tbaseValue,\n\t\t\t'no-conflict-local',\n\t\t\t1,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\tif (!localChanged && remoteChanged) {\n\t\treturn createResult(\n\t\t\tremoteData[fieldName],\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tbaseValue,\n\t\t\tremoteData[fieldName],\n\t\t\tbaseValue,\n\t\t\t'no-conflict-remote',\n\t\t\t1,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\tif (!localChanged && !remoteChanged) {\n\t\treturn createResult(\n\t\t\tbaseValue,\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tbaseValue,\n\t\t\tbaseValue,\n\t\t\tbaseValue,\n\t\t\t'no-conflict-unchanged',\n\t\t\t1,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\t// Both sides changed this field — conflict resolution needed\n\n\tconst localValue = localData[fieldName]\n\tconst remoteValue = remoteData[fieldName]\n\n\t// Tier 3: Custom resolver takes precedence\n\tif (resolver !== undefined) {\n\t\tconst resolved = resolver(localValue, remoteValue, baseValue)\n\t\treturn createResult(\n\t\t\tresolved,\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tlocalValue,\n\t\t\tremoteValue,\n\t\t\tbaseValue,\n\t\t\t'custom',\n\t\t\t3,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\t// Tier 1: Auto-merge based on field kind\n\treturn autoMerge(\n\t\tfieldName,\n\t\tlocalOp,\n\t\tremoteOp,\n\t\tlocalValue,\n\t\tremoteValue,\n\t\tbaseValue,\n\t\tfieldDescriptor,\n\t\tstartTime,\n\t)\n}\n\nfunction autoMerge(\n\tfieldName: string,\n\tlocalOp: Operation,\n\tremoteOp: Operation,\n\tlocalValue: unknown,\n\tremoteValue: unknown,\n\tbaseValue: unknown,\n\tfieldDescriptor: FieldDescriptor,\n\tstartTime: number,\n): FieldMergeResult {\n\tswitch (fieldDescriptor.kind) {\n\t\tcase 'string':\n\t\tcase 'number':\n\t\tcase 'boolean':\n\t\tcase 'enum':\n\t\tcase 'timestamp': {\n\t\t\tconst lwwResult = lastWriteWins(\n\t\t\t\tlocalValue,\n\t\t\t\tremoteValue,\n\t\t\t\tlocalOp.timestamp,\n\t\t\t\tremoteOp.timestamp,\n\t\t\t)\n\t\t\treturn createResult(\n\t\t\t\tlwwResult.value,\n\t\t\t\tfieldName,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tlocalValue,\n\t\t\t\tremoteValue,\n\t\t\t\tbaseValue,\n\t\t\t\t'lww',\n\t\t\t\t1,\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'array': {\n\t\t\tconst baseArr = Array.isArray(baseValue) ? baseValue : []\n\t\t\tconst localArr = Array.isArray(localValue) ? localValue : []\n\t\t\tconst remoteArr = Array.isArray(remoteValue) ? remoteValue : []\n\n\t\t\tconst merged = addWinsSet(localArr, remoteArr, baseArr)\n\t\t\treturn createResult(\n\t\t\t\tmerged,\n\t\t\t\tfieldName,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tlocalValue,\n\t\t\t\tremoteValue,\n\t\t\t\tbaseValue,\n\t\t\t\t'add-wins-set',\n\t\t\t\t1,\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'richtext': {\n\t\t\tconst merged = mergeRichtext(\n\t\t\t\tlocalValue as RichtextValue,\n\t\t\t\tremoteValue as RichtextValue,\n\t\t\t\tbaseValue as RichtextValue,\n\t\t\t)\n\t\t\treturn createResult(\n\t\t\t\tmerged,\n\t\t\t\tfieldName,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tlocalValue,\n\t\t\t\tremoteValue,\n\t\t\t\tbaseValue,\n\t\t\t\t'crdt-text',\n\t\t\t\t1,\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunction createResult(\n\tvalue: unknown,\n\tfield: string,\n\toperationA: Operation,\n\toperationB: Operation,\n\tinputA: unknown,\n\tinputB: unknown,\n\tbase: unknown | null,\n\tstrategy: string,\n\ttier: 1 | 2 | 3,\n\tstartTime: number,\n): FieldMergeResult {\n\tconst trace: MergeTrace = {\n\t\toperationA,\n\t\toperationB,\n\t\tfield,\n\t\tstrategy,\n\t\tinputA,\n\t\tinputB,\n\t\tbase,\n\t\toutput: value,\n\t\ttier,\n\t\tconstraintViolated: null,\n\t\tduration: Date.now() - startTime,\n\t}\n\treturn { value, trace }\n}\n","import type { CollectionDefinition, Constraint } from '@korajs/core'\nimport type { ConstraintContext, ConstraintViolation } from '../types'\n\n/**\n * Checks all constraints on a collection against a candidate merged record.\n *\n * Called after Tier 1+3 merge produces a candidate state. Each violated\n * constraint is returned as a ConstraintViolation for Tier 2 resolution.\n *\n * @param mergedRecord - The candidate record state after field-level merge\n * @param recordId - ID of the record being merged\n * @param collection - Name of the collection\n * @param collectionDef - Schema definition for the collection\n * @param constraintContext - Pluggable DB lookup interface\n * @returns Array of violated constraints (empty if all pass)\n */\nexport async function checkConstraints(\n\tmergedRecord: Record<string, unknown>,\n\trecordId: string,\n\tcollection: string,\n\tcollectionDef: CollectionDefinition,\n\tconstraintContext: ConstraintContext,\n): Promise<ConstraintViolation[]> {\n\tconst violations: ConstraintViolation[] = []\n\n\tfor (const constraint of collectionDef.constraints) {\n\t\t// For unique and capacity constraints, where clause filters which records\n\t\t// the constraint applies to. For referential constraints, where clause\n\t\t// stores metadata (e.g., the referenced collection), not a record filter.\n\t\tif (\n\t\t\tconstraint.type !== 'referential' &&\n\t\t\tconstraint.where !== undefined &&\n\t\t\t!matchesWhere(mergedRecord, constraint.where)\n\t\t) {\n\t\t\tcontinue\n\t\t}\n\n\t\tconst violation = await checkSingleConstraint(\n\t\t\tconstraint,\n\t\t\tmergedRecord,\n\t\t\trecordId,\n\t\t\tcollection,\n\t\t\tconstraintContext,\n\t\t)\n\t\tif (violation !== null) {\n\t\t\tviolations.push(violation)\n\t\t}\n\t}\n\n\treturn violations\n}\n\nasync function checkSingleConstraint(\n\tconstraint: Constraint,\n\tmergedRecord: Record<string, unknown>,\n\trecordId: string,\n\tcollection: string,\n\tctx: ConstraintContext,\n): Promise<ConstraintViolation | null> {\n\tswitch (constraint.type) {\n\t\tcase 'unique':\n\t\t\treturn checkUniqueConstraint(constraint, mergedRecord, recordId, collection, ctx)\n\t\tcase 'capacity':\n\t\t\treturn checkCapacityConstraint(constraint, mergedRecord, collection, ctx)\n\t\tcase 'referential':\n\t\t\treturn checkReferentialConstraint(constraint, mergedRecord, collection, ctx)\n\t}\n}\n\nasync function checkUniqueConstraint(\n\tconstraint: Constraint,\n\tmergedRecord: Record<string, unknown>,\n\trecordId: string,\n\tcollection: string,\n\tctx: ConstraintContext,\n): Promise<ConstraintViolation | null> {\n\t// Build a where clause from the constraint fields and the merged record values\n\tconst where: Record<string, unknown> = {}\n\tfor (const field of constraint.fields) {\n\t\twhere[field] = mergedRecord[field]\n\t}\n\n\tconst existing = await ctx.queryRecords(collection, where)\n\t// Filter out the current record itself\n\tconst duplicates = existing.filter((r) => r.id !== recordId)\n\n\tif (duplicates.length > 0) {\n\t\treturn {\n\t\t\tconstraint,\n\t\t\tfields: constraint.fields,\n\t\t\tmessage:\n\t\t\t\t`Unique constraint violated on fields [${constraint.fields.join(', ')}] ` +\n\t\t\t\t`in collection \"${collection}\": duplicate value(s) found`,\n\t\t}\n\t}\n\n\treturn null\n}\n\nasync function checkCapacityConstraint(\n\tconstraint: Constraint,\n\tmergedRecord: Record<string, unknown>,\n\tcollection: string,\n\tctx: ConstraintContext,\n): Promise<ConstraintViolation | null> {\n\t// Capacity constraint: the where clause defines the group, fields[0] is the capacity limit field name\n\t// We count records matching the where clause\n\tconst where = constraint.where ?? {}\n\tconst count = await ctx.countRecords(collection, where)\n\n\t// The capacity limit is stored in the first field name as a numeric value\n\t// For capacity constraints, fields represent the fields that define the capacity group\n\t// The capacity limit itself needs to come from somewhere — we use the constraint's where clause\n\t// to scope the group, and check if adding this record exceeds the existing count\n\t// For simplicity: if there's a capacity constraint, the presence of this violation\n\t// means the group is at or over capacity\n\tif (count > 0 && constraint.fields.length > 0) {\n\t\t// Build the group match from constraint fields\n\t\tconst groupWhere: Record<string, unknown> = { ...where }\n\t\tfor (const field of constraint.fields) {\n\t\t\tgroupWhere[field] = mergedRecord[field]\n\t\t}\n\n\t\tconst groupCount = await ctx.countRecords(collection, groupWhere)\n\t\t// Capacity constraints use the where clause to define the limit\n\t\t// If we can't determine a numeric limit, we flag the violation\n\t\t// The resolver will handle the actual resolution logic\n\t\tif (groupCount > 1) {\n\t\t\treturn {\n\t\t\t\tconstraint,\n\t\t\t\tfields: constraint.fields,\n\t\t\t\tmessage:\n\t\t\t\t\t`Capacity constraint violated on fields [${constraint.fields.join(', ')}] ` +\n\t\t\t\t\t`in collection \"${collection}\": group count ${groupCount} exceeds limit`,\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null\n}\n\nasync function checkReferentialConstraint(\n\tconstraint: Constraint,\n\tmergedRecord: Record<string, unknown>,\n\tcollection: string,\n\tctx: ConstraintContext,\n): Promise<ConstraintViolation | null> {\n\t// Referential constraint: the first field is the foreign key field,\n\t// and where.collection (or similar) would specify the referenced collection.\n\t// For a simple check: the field value should reference an existing record.\n\tif (constraint.fields.length === 0) {\n\t\treturn null\n\t}\n\n\tconst fkField = constraint.fields[0]\n\tif (fkField === undefined) {\n\t\treturn null\n\t}\n\n\tconst fkValue = mergedRecord[fkField]\n\tif (fkValue === null || fkValue === undefined) {\n\t\t// Null FK is allowed (the relation is optional)\n\t\treturn null\n\t}\n\n\t// Look up the referenced record. The referenced collection is specified\n\t// in constraint.where.collection (convention for referential constraints).\n\tconst referencedCollection =\n\t\tconstraint.where !== undefined ? (constraint.where.collection as string | undefined) : undefined\n\tif (referencedCollection === undefined) {\n\t\treturn null\n\t}\n\n\tconst referenced = await ctx.queryRecords(referencedCollection, { id: fkValue })\n\tif (referenced.length === 0) {\n\t\treturn {\n\t\t\tconstraint,\n\t\t\tfields: constraint.fields,\n\t\t\tmessage:\n\t\t\t\t`Referential constraint violated on field \"${fkField}\" ` +\n\t\t\t\t`in collection \"${collection}\": referenced record not found ` +\n\t\t\t\t`in \"${referencedCollection}\" with id \"${String(fkValue)}\"`,\n\t\t}\n\t}\n\n\treturn null\n}\n\n/**\n * Check if a record matches a where clause (simple equality check).\n */\nfunction matchesWhere(record: Record<string, unknown>, where: Record<string, unknown>): boolean {\n\tfor (const [key, value] of Object.entries(where)) {\n\t\tif (record[key] !== value) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n","import { HybridLogicalClock } from '@korajs/core'\nimport type { CollectionDefinition, Operation } from '@korajs/core'\nimport type { MergeTrace } from '@korajs/core'\nimport type { ConstraintViolation } from '../types'\n\n/**\n * Result of resolving a constraint violation.\n */\nexport interface ConstraintResolution {\n\t/** The updated record after constraint resolution */\n\tresolvedRecord: Record<string, unknown>\n\t/** Trace of the resolution decision for DevTools */\n\ttrace: MergeTrace\n}\n\n/**\n * Resolves a constraint violation by applying the constraint's onConflict strategy.\n *\n * Strategies:\n * - `last-write-wins`: The operation with the later HLC timestamp wins entirely\n * - `first-write-wins`: The operation with the earlier HLC timestamp wins entirely\n * - `priority-field`: Compares a designated priority field to determine the winner\n * - `server-decides`: Returns a marker indicating deferred server resolution\n * - `custom`: Calls the constraint's resolve function\n *\n * @param violation - The constraint violation to resolve\n * @param mergedRecord - The current candidate record state\n * @param localOp - The local operation\n * @param remoteOp - The remote operation\n * @param baseState - The record state before either operation\n * @returns The resolved record and a trace\n */\nexport function resolveConstraintViolation(\n\tviolation: ConstraintViolation,\n\tmergedRecord: Record<string, unknown>,\n\tlocalOp: Operation,\n\tremoteOp: Operation,\n\tbaseState: Record<string, unknown>,\n): ConstraintResolution {\n\tconst startTime = Date.now()\n\tconst { constraint } = violation\n\n\tswitch (constraint.onConflict) {\n\t\tcase 'last-write-wins': {\n\t\t\tconst comparison = HybridLogicalClock.compare(localOp.timestamp, remoteOp.timestamp)\n\t\t\tconst winner = comparison >= 0 ? localOp : remoteOp\n\t\t\tconst resolvedRecord = applyWinnerFields(mergedRecord, winner, violation.fields)\n\t\t\treturn createResolution(\n\t\t\t\tresolvedRecord,\n\t\t\t\tviolation,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tbaseState,\n\t\t\t\t'constraint-lww',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'first-write-wins': {\n\t\t\tconst comparison = HybridLogicalClock.compare(localOp.timestamp, remoteOp.timestamp)\n\t\t\tconst winner = comparison <= 0 ? localOp : remoteOp\n\t\t\tconst resolvedRecord = applyWinnerFields(mergedRecord, winner, violation.fields)\n\t\t\treturn createResolution(\n\t\t\t\tresolvedRecord,\n\t\t\t\tviolation,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tbaseState,\n\t\t\t\t'constraint-fww',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'priority-field': {\n\t\t\tconst priorityField = constraint.priorityField\n\t\t\tif (priorityField === undefined) {\n\t\t\t\t// Fallback to LWW if no priority field specified\n\t\t\t\tconst comparison = HybridLogicalClock.compare(localOp.timestamp, remoteOp.timestamp)\n\t\t\t\tconst winner = comparison >= 0 ? localOp : remoteOp\n\t\t\t\tconst resolvedRecord = applyWinnerFields(mergedRecord, winner, violation.fields)\n\t\t\t\treturn createResolution(\n\t\t\t\t\tresolvedRecord,\n\t\t\t\t\tviolation,\n\t\t\t\t\tlocalOp,\n\t\t\t\t\tremoteOp,\n\t\t\t\t\tbaseState,\n\t\t\t\t\t'constraint-priority-fallback-lww',\n\t\t\t\t\tstartTime,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst localPriority = getFieldValue(localOp, priorityField, mergedRecord)\n\t\t\tconst remotePriority = getFieldValue(remoteOp, priorityField, mergedRecord)\n\n\t\t\t// Higher priority value wins (numeric or string comparison)\n\t\t\tconst winner = comparePriority(localPriority, remotePriority) >= 0 ? localOp : remoteOp\n\t\t\tconst resolvedRecord = applyWinnerFields(mergedRecord, winner, violation.fields)\n\t\t\treturn createResolution(\n\t\t\t\tresolvedRecord,\n\t\t\t\tviolation,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tbaseState,\n\t\t\t\t'constraint-priority',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'server-decides': {\n\t\t\t// Mark the record for server-side resolution. The sync layer handles this.\n\t\t\t// We keep the current merged record but flag it.\n\t\t\tconst resolvedRecord = {\n\t\t\t\t...mergedRecord,\n\t\t\t\t_pendingServerResolution: true,\n\t\t\t}\n\t\t\treturn createResolution(\n\t\t\t\tresolvedRecord,\n\t\t\t\tviolation,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tbaseState,\n\t\t\t\t'constraint-server-decides',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'custom': {\n\t\t\tif (constraint.resolve === undefined) {\n\t\t\t\t// No custom resolver provided — fallback to LWW\n\t\t\t\tconst comparison = HybridLogicalClock.compare(localOp.timestamp, remoteOp.timestamp)\n\t\t\t\tconst winner = comparison >= 0 ? localOp : remoteOp\n\t\t\t\tconst resolvedRecord = applyWinnerFields(mergedRecord, winner, violation.fields)\n\t\t\t\treturn createResolution(\n\t\t\t\t\tresolvedRecord,\n\t\t\t\t\tviolation,\n\t\t\t\t\tlocalOp,\n\t\t\t\t\tremoteOp,\n\t\t\t\t\tbaseState,\n\t\t\t\t\t'constraint-custom-fallback-lww',\n\t\t\t\t\tstartTime,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// For each violated field, call the custom resolver with the local, remote, and base values\n\t\t\tconst resolvedRecord = { ...mergedRecord }\n\t\t\tfor (const field of violation.fields) {\n\t\t\t\tconst localVal = getFieldValue(localOp, field, mergedRecord)\n\t\t\t\tconst remoteVal = getFieldValue(remoteOp, field, mergedRecord)\n\t\t\t\tconst baseVal = baseState[field]\n\t\t\t\tresolvedRecord[field] = constraint.resolve(localVal, remoteVal, baseVal)\n\t\t\t}\n\t\t\treturn createResolution(\n\t\t\t\tresolvedRecord,\n\t\t\t\tviolation,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tbaseState,\n\t\t\t\t'constraint-custom',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\t}\n}\n\n/**\n * Apply the winning operation's field values to the merged record\n * for the specific fields involved in the constraint violation.\n */\nfunction applyWinnerFields(\n\tmergedRecord: Record<string, unknown>,\n\twinner: Operation,\n\tfields: string[],\n): Record<string, unknown> {\n\tconst result = { ...mergedRecord }\n\tconst winnerData = winner.data ?? {}\n\tfor (const field of fields) {\n\t\tif (field in winnerData) {\n\t\t\tresult[field] = winnerData[field]\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Get a field value from an operation's data, falling back to the merged record.\n */\nfunction getFieldValue(\n\top: Operation,\n\tfield: string,\n\tmergedRecord: Record<string, unknown>,\n): unknown {\n\tconst data = op.data ?? {}\n\tif (field in data) {\n\t\treturn data[field]\n\t}\n\treturn mergedRecord[field]\n}\n\n/**\n * Compare two priority values. Supports numbers and strings.\n * Returns positive if a > b, negative if a < b, zero if equal.\n */\nfunction comparePriority(a: unknown, b: unknown): number {\n\tif (typeof a === 'number' && typeof b === 'number') {\n\t\treturn a - b\n\t}\n\tif (typeof a === 'string' && typeof b === 'string') {\n\t\treturn a < b ? -1 : a > b ? 1 : 0\n\t}\n\t// Mixed types: convert to string for comparison\n\treturn String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0\n}\n\nfunction createResolution(\n\tresolvedRecord: Record<string, unknown>,\n\tviolation: ConstraintViolation,\n\tlocalOp: Operation,\n\tremoteOp: Operation,\n\tbaseState: Record<string, unknown>,\n\tstrategy: string,\n\tstartTime: number,\n): ConstraintResolution {\n\tconst field = violation.fields.join(', ')\n\tconst trace: MergeTrace = {\n\t\toperationA: localOp,\n\t\toperationB: remoteOp,\n\t\tfield,\n\t\tstrategy,\n\t\tinputA: extractFieldValues(localOp, violation.fields),\n\t\tinputB: extractFieldValues(remoteOp, violation.fields),\n\t\tbase: extractFields(baseState, violation.fields),\n\t\toutput: extractFields(resolvedRecord, violation.fields),\n\t\ttier: 2,\n\t\tconstraintViolated: violation.message,\n\t\tduration: Date.now() - startTime,\n\t}\n\treturn { resolvedRecord, trace }\n}\n\nfunction extractFieldValues(op: Operation, fields: string[]): Record<string, unknown> {\n\tconst data = op.data ?? {}\n\tconst result: Record<string, unknown> = {}\n\tfor (const field of fields) {\n\t\tresult[field] = data[field]\n\t}\n\treturn result\n}\n\nfunction extractFields(record: Record<string, unknown>, fields: string[]): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {}\n\tfor (const field of fields) {\n\t\tresult[field] = record[field]\n\t}\n\treturn result\n}\n","import { HybridLogicalClock } from '@korajs/core'\nimport type { Operation } from '@korajs/core'\nimport type { MergeTrace } from '@korajs/core'\nimport { checkConstraints } from '../constraints/constraint-checker'\nimport { resolveConstraintViolation } from '../constraints/resolvers'\nimport type { ConstraintContext, MergeInput, MergeResult } from '../types'\nimport { mergeField } from './field-merger'\n\n/**\n * Three-tier merge engine for resolving concurrent operations.\n *\n * Tier 1: Auto-merge per field kind (LWW, add-wins set, CRDT)\n * Tier 3: Custom resolvers override Tier 1 for specific fields\n * Tier 2: Constraint validation against the candidate merged state\n *\n * Tier 3 runs BEFORE Tier 2 so that constraints validate the final merged state\n * including any custom resolver outputs.\n *\n * @example\n * ```typescript\n * const engine = new MergeEngine()\n * const result = await engine.merge({\n * local: localOp,\n * remote: remoteOp,\n * baseState: { title: 'old', completed: false },\n * collectionDef: schema.collections.todos,\n * })\n * ```\n */\nexport class MergeEngine {\n\t/**\n\t * Merge two concurrent operations with all three tiers.\n\t *\n\t * Flow:\n\t * 1. Determine which fields conflict (both ops modified the same field)\n\t * 2. For non-conflicting fields: take the changed value from whichever op modified it\n\t * 3. For conflicting fields: Tier 3 custom resolver if exists, else Tier 1 auto-merge\n\t * 4. Assemble candidate merged record\n\t * 5. If constraintContext provided: run Tier 2 constraint checks and resolve violations\n\t * 6. Return MergeResult with all traces\n\t *\n\t * @param input - The two operations, base state, and collection definition\n\t * @param constraintContext - Optional DB lookup interface for Tier 2 constraints\n\t * @returns The merged data and traces for DevTools\n\t */\n\tasync merge(input: MergeInput, constraintContext?: ConstraintContext): Promise<MergeResult> {\n\t\t// Handle delete vs delete: both agree, no merge needed\n\t\tif (input.local.type === 'delete' && input.remote.type === 'delete') {\n\t\t\treturn {\n\t\t\t\tmergedData: {},\n\t\t\t\ttraces: [],\n\t\t\t\tappliedOperation: 'merged',\n\t\t\t}\n\t\t}\n\n\t\t// Handle update vs delete (or delete vs update)\n\t\tif (input.local.type === 'delete' || input.remote.type === 'delete') {\n\t\t\treturn this.mergeWithDelete(input)\n\t\t}\n\n\t\t// Insert vs insert or update vs update: field-level merge\n\t\tconst fieldResult = this.mergeFields(input)\n\n\t\t// Tier 2: Constraint checking (requires async DB lookups)\n\t\tif (constraintContext !== undefined && input.collectionDef.constraints.length > 0) {\n\t\t\tconst recordWithId = { id: input.local.recordId, ...fieldResult.mergedData }\n\t\t\tconst violations = await checkConstraints(\n\t\t\t\trecordWithId,\n\t\t\t\tinput.local.recordId,\n\t\t\t\tinput.local.collection,\n\t\t\t\tinput.collectionDef,\n\t\t\t\tconstraintContext,\n\t\t\t)\n\n\t\t\tlet mergedData = fieldResult.mergedData\n\t\t\tconst allTraces = [...fieldResult.traces]\n\n\t\t\tfor (const violation of violations) {\n\t\t\t\tconst resolution = resolveConstraintViolation(\n\t\t\t\t\tviolation,\n\t\t\t\t\tmergedData,\n\t\t\t\t\tinput.local,\n\t\t\t\t\tinput.remote,\n\t\t\t\t\tinput.baseState,\n\t\t\t\t)\n\t\t\t\tmergedData = resolution.resolvedRecord\n\t\t\t\tallTraces.push(resolution.trace)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tmergedData,\n\t\t\t\ttraces: allTraces,\n\t\t\t\tappliedOperation: determineAppliedOperation(allTraces),\n\t\t\t}\n\t\t}\n\n\t\treturn fieldResult\n\t}\n\n\t/**\n\t * Synchronous field-level merge (Tier 1 + Tier 3 only).\n\t *\n\t * Useful when constraint context is unavailable or not needed.\n\t * Skips Tier 2 constraint checking entirely.\n\t *\n\t * @param input - The two operations, base state, and collection definition\n\t * @returns The merged data and traces for DevTools\n\t */\n\tmergeFields(input: MergeInput): MergeResult {\n\t\tconst { local, remote, baseState, collectionDef } = input\n\n\t\t// Collect all field names that either operation touches\n\t\tconst allFields = collectAffectedFields(local, remote, baseState, collectionDef)\n\n\t\tconst mergedData: Record<string, unknown> = {}\n\t\tconst traces: MergeTrace[] = []\n\n\t\tfor (const fieldName of allFields) {\n\t\t\tconst fieldDef = collectionDef.fields[fieldName]\n\t\t\tif (fieldDef === undefined) {\n\t\t\t\t// Field not in schema — skip (could be a removed field from migration)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst resolver = collectionDef.resolvers[fieldName]\n\t\t\tconst result = mergeField(fieldName, local, remote, baseState, fieldDef, resolver)\n\n\t\t\tmergedData[fieldName] = result.value\n\n\t\t\t// Only include traces for actual conflicts (not no-conflict cases)\n\t\t\tif (\n\t\t\t\tresult.trace.strategy !== 'no-conflict-local' &&\n\t\t\t\tresult.trace.strategy !== 'no-conflict-remote' &&\n\t\t\t\tresult.trace.strategy !== 'no-conflict-unchanged'\n\t\t\t) {\n\t\t\t\ttraces.push(result.trace)\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tmergedData,\n\t\t\ttraces,\n\t\t\tappliedOperation: determineAppliedOperation(traces),\n\t\t}\n\t}\n\n\t/**\n\t * Handle merge when one operation is a delete.\n\t * Default: delete wins (LWW on the record level).\n\t */\n\tprivate mergeWithDelete(input: MergeInput): MergeResult {\n\t\tconst { local, remote } = input\n\n\t\t// LWW at the record level: later operation wins\n\t\tconst comparison = HybridLogicalClock.compare(local.timestamp, remote.timestamp)\n\n\t\tif (comparison >= 0) {\n\t\t\t// Local is later\n\t\t\tif (local.type === 'delete') {\n\t\t\t\treturn { mergedData: {}, traces: [], appliedOperation: 'local' }\n\t\t\t}\n\t\t\t// Local is an update that's later than remote delete → local wins\n\t\t\treturn {\n\t\t\t\tmergedData: { ...input.baseState, ...(local.data ?? {}) },\n\t\t\t\ttraces: [],\n\t\t\t\tappliedOperation: 'local',\n\t\t\t}\n\t\t}\n\n\t\t// Remote is later\n\t\tif (remote.type === 'delete') {\n\t\t\treturn { mergedData: {}, traces: [], appliedOperation: 'remote' }\n\t\t}\n\t\t// Remote is an update that's later than local delete → remote wins\n\t\treturn {\n\t\t\tmergedData: { ...input.baseState, ...(remote.data ?? {}) },\n\t\t\ttraces: [],\n\t\t\tappliedOperation: 'remote',\n\t\t}\n\t}\n}\n\n/**\n * Collect all field names affected by either operation or present in the base state.\n */\nfunction collectAffectedFields(\n\tlocal: Operation,\n\tremote: Operation,\n\tbaseState: Record<string, unknown>,\n\tcollectionDef: { fields: Record<string, unknown> },\n): Set<string> {\n\tconst fields = new Set<string>()\n\n\t// Fields from the schema definition\n\tfor (const fieldName of Object.keys(collectionDef.fields)) {\n\t\tfields.add(fieldName)\n\t}\n\n\t// Fields from local operation\n\tif (local.data !== null) {\n\t\tfor (const fieldName of Object.keys(local.data)) {\n\t\t\tfields.add(fieldName)\n\t\t}\n\t}\n\n\t// Fields from remote operation\n\tif (remote.data !== null) {\n\t\tfor (const fieldName of Object.keys(remote.data)) {\n\t\t\tfields.add(fieldName)\n\t\t}\n\t}\n\n\t// Fields from base state\n\tfor (const fieldName of Object.keys(baseState)) {\n\t\tfields.add(fieldName)\n\t}\n\n\treturn fields\n}\n\n/**\n * Determine which operation's values dominate overall.\n * If all conflict traces went the same way, report that side.\n * Otherwise, report 'merged'.\n */\nfunction determineAppliedOperation(traces: MergeTrace[]): 'local' | 'remote' | 'merged' {\n\tif (traces.length === 0) {\n\t\treturn 'merged'\n\t}\n\n\tlet allLocal = true\n\tlet allRemote = true\n\n\tfor (const trace of traces) {\n\t\tif (trace.strategy === 'lww' || trace.strategy === 'constraint-lww') {\n\t\t\t// Check if local or remote value was the output\n\t\t\tif (trace.output === trace.inputA) {\n\t\t\t\tallRemote = false\n\t\t\t} else if (trace.output === trace.inputB) {\n\t\t\t\tallLocal = false\n\t\t\t} else {\n\t\t\t\tallLocal = false\n\t\t\t\tallRemote = false\n\t\t\t}\n\t\t} else {\n\t\t\t// For non-LWW strategies (add-wins-set, custom, etc.), it's a merge\n\t\t\tallLocal = false\n\t\t\tallRemote = false\n\t\t}\n\t}\n\n\tif (allLocal) return 'local'\n\tif (allRemote) return 'remote'\n\treturn 'merged'\n}\n"],"mappings":"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","names":["HybridLogicalClock","winner","resolvedRecord","HybridLogicalClock","HybridLogicalClock"]}
|
|
1
|
+
{"version":3,"sources":["../src/strategies/lww.ts","../src/strategies/add-wins-set.ts","../src/strategies/atomic-merge.ts","../src/strategies/yjs-richtext.ts","../src/strategies/schema-strategies.ts","../src/engine/field-merger.ts","../src/constraints/constraint-checker.ts","../src/constraints/resolvers.ts","../src/constraints/referential-integrity.ts","../src/engine/merge-engine.ts","../src/constraints/state-machine-constraint.ts"],"sourcesContent":["import { HybridLogicalClock } from '@korajs/core'\nimport type { HLCTimestamp } from '@korajs/core'\n\n/**\n * Result of a Last-Write-Wins comparison.\n */\nexport interface LWWResult {\n\t/** The winning value */\n\tvalue: unknown\n\t/** Which side won */\n\twinner: 'local' | 'remote'\n}\n\n/**\n * Last-Write-Wins merge strategy using HLC timestamps.\n *\n * Compares two values by their HLC timestamps and returns the value with the\n * later timestamp. The HLC total order guarantees a deterministic winner even\n * when wall-clock times and logical counters are identical (nodeId tiebreaker).\n *\n * @param localValue - The local field value\n * @param remoteValue - The remote field value\n * @param localTimestamp - HLC timestamp of the local operation\n * @param remoteTimestamp - HLC timestamp of the remote operation\n * @returns The winning value and which side won\n */\nexport function lastWriteWins(\n\tlocalValue: unknown,\n\tremoteValue: unknown,\n\tlocalTimestamp: HLCTimestamp,\n\tremoteTimestamp: HLCTimestamp,\n): LWWResult {\n\tconst comparison = HybridLogicalClock.compare(localTimestamp, remoteTimestamp)\n\t// HLC total order guarantees comparison is never 0 for different nodeIds.\n\t// If comparison >= 0, local wins (local is later or same node).\n\tif (comparison >= 0) {\n\t\treturn { value: localValue, winner: 'local' }\n\t}\n\treturn { value: remoteValue, winner: 'remote' }\n}\n","/**\n * Add-wins set merge strategy for array fields.\n *\n * When two sides concurrently modify an array, this strategy preserves all\n * additions from both sides. An element is only removed from the result if\n * BOTH sides independently removed it. This prevents data loss: if one side\n * adds an element while another removes a different element, both changes\n * are preserved.\n *\n * Algorithm:\n * added_local = local - base\n * added_remote = remote - base\n * removed_local = base - local\n * removed_remote = base - remote\n * result = (base ∪ added_local ∪ added_remote) - (removed_local ∩ removed_remote)\n *\n * Uses JSON.stringify for element comparison to handle primitives and objects.\n *\n * @param localArray - The local array after local modifications\n * @param remoteArray - The remote array after remote modifications\n * @param baseArray - The array state before either modification\n * @returns The merged array\n */\nexport function addWinsSet(\n\tlocalArray: unknown[],\n\tremoteArray: unknown[],\n\tbaseArray: unknown[],\n): unknown[] {\n\tconst serialize = (v: unknown): string => JSON.stringify(v)\n\n\tconst baseSet = new Set(baseArray.map(serialize))\n\tconst localSet = new Set(localArray.map(serialize))\n\tconst remoteSet = new Set(remoteArray.map(serialize))\n\n\t// Elements added by each side (present in their set but not in base)\n\tconst addedLocal = new Set<string>()\n\tfor (const s of localSet) {\n\t\tif (!baseSet.has(s)) {\n\t\t\taddedLocal.add(s)\n\t\t}\n\t}\n\n\tconst addedRemote = new Set<string>()\n\tfor (const s of remoteSet) {\n\t\tif (!baseSet.has(s)) {\n\t\t\taddedRemote.add(s)\n\t\t}\n\t}\n\n\t// Elements removed by each side (present in base but not in their set)\n\tconst removedLocal = new Set<string>()\n\tfor (const s of baseSet) {\n\t\tif (!localSet.has(s)) {\n\t\t\tremovedLocal.add(s)\n\t\t}\n\t}\n\n\tconst removedRemote = new Set<string>()\n\tfor (const s of baseSet) {\n\t\tif (!remoteSet.has(s)) {\n\t\t\tremovedRemote.add(s)\n\t\t}\n\t}\n\n\t// An element is truly removed only if BOTH sides removed it\n\tconst removedByBoth = new Set<string>()\n\tfor (const s of removedLocal) {\n\t\tif (removedRemote.has(s)) {\n\t\t\tremovedByBoth.add(s)\n\t\t}\n\t}\n\n\t// Result = (base ∪ added_local ∪ added_remote) - removed_by_both\n\t// Maintain order: base elements first (preserving order), then local adds, then remote adds\n\tconst resultSerialized = new Set<string>()\n\tconst result: unknown[] = []\n\n\tconst addIfNew = (serialized: string, value: unknown): void => {\n\t\tif (!resultSerialized.has(serialized) && !removedByBoth.has(serialized)) {\n\t\t\tresultSerialized.add(serialized)\n\t\t\tresult.push(value)\n\t\t}\n\t}\n\n\t// Base elements (in original order, minus those removed by both)\n\tfor (const item of baseArray) {\n\t\taddIfNew(serialize(item), item)\n\t}\n\n\t// Local additions (in order they appear in local array)\n\tfor (const item of localArray) {\n\t\tconst s = serialize(item)\n\t\tif (addedLocal.has(s)) {\n\t\t\taddIfNew(s, item)\n\t\t}\n\t}\n\n\t// Remote additions (in order they appear in remote array)\n\tfor (const item of remoteArray) {\n\t\tconst s = serialize(item)\n\t\tif (addedRemote.has(s)) {\n\t\t\taddIfNew(s, item)\n\t\t}\n\t}\n\n\treturn result\n}\n","import type { AtomicOp } from '@korajs/core'\n\n/**\n * Result of merging two atomic operations on the same field.\n * If `merged` is false, the caller should fall back to LWW on the resolved values.\n */\nexport interface AtomicMergeResult {\n\tmerged: true\n\tvalue: unknown\n\tstrategy: string\n}\n\n/**\n * Sentinel returned when atomic ops cannot be composed (e.g., mismatched types).\n */\nexport interface AtomicMergeFallback {\n\tmerged: false\n}\n\n/**\n * Compose two concurrent atomic operations on the same field.\n *\n * When both operations expressed an atomic intent (e.g., both used op.increment()),\n * their intents can be composed to produce a correct merged result without LWW:\n *\n * - increment + increment → sum of both deltas applied to base\n * - max + max → max of all values (base, local, remote)\n * - min + min → min of all values (base, local, remote)\n * - append + append → base array with both appended items\n * - remove + remove → base array with both items removed\n *\n * Falls back (returns { merged: false }) when atomic op types differ,\n * since there is no meaningful composition for mixed intents.\n *\n * @param localAtomicOp - The atomic op from the local operation\n * @param remoteAtomicOp - The atomic op from the remote operation\n * @param baseValue - The field value before either operation was applied\n * @returns The composed result, or a fallback signal to use LWW\n */\nexport function mergeAtomicOps(\n\tlocalAtomicOp: AtomicOp,\n\tremoteAtomicOp: AtomicOp,\n\tbaseValue: unknown,\n): AtomicMergeResult | AtomicMergeFallback {\n\t// Both must be the same type to compose\n\tif (localAtomicOp.type !== remoteAtomicOp.type) {\n\t\treturn { merged: false }\n\t}\n\n\tswitch (localAtomicOp.type) {\n\t\tcase 'increment': {\n\t\t\t// Sum of both deltas applied to base\n\t\t\tconst base = typeof baseValue === 'number' ? baseValue : 0\n\t\t\tconst localDelta = localAtomicOp.value as number\n\t\t\tconst remoteDelta = remoteAtomicOp.value as number\n\t\t\treturn {\n\t\t\t\tmerged: true,\n\t\t\t\tvalue: base + localDelta + remoteDelta,\n\t\t\t\tstrategy: 'atomic-increment',\n\t\t\t}\n\t\t}\n\n\t\tcase 'max': {\n\t\t\t// Take the maximum of base, local operand, and remote operand\n\t\t\tconst base = typeof baseValue === 'number' ? baseValue : Number.NEGATIVE_INFINITY\n\t\t\tconst localVal = localAtomicOp.value as number\n\t\t\tconst remoteVal = remoteAtomicOp.value as number\n\t\t\treturn {\n\t\t\t\tmerged: true,\n\t\t\t\tvalue: Math.max(base, localVal, remoteVal),\n\t\t\t\tstrategy: 'atomic-max',\n\t\t\t}\n\t\t}\n\n\t\tcase 'min': {\n\t\t\t// Take the minimum of base, local operand, and remote operand\n\t\t\tconst base = typeof baseValue === 'number' ? baseValue : Number.POSITIVE_INFINITY\n\t\t\tconst localVal = localAtomicOp.value as number\n\t\t\tconst remoteVal = remoteAtomicOp.value as number\n\t\t\treturn {\n\t\t\t\tmerged: true,\n\t\t\t\tvalue: Math.min(base, localVal, remoteVal),\n\t\t\t\tstrategy: 'atomic-min',\n\t\t\t}\n\t\t}\n\n\t\tcase 'append': {\n\t\t\t// Include both appended items\n\t\t\tconst base = Array.isArray(baseValue) ? [...baseValue] : []\n\t\t\tbase.push(localAtomicOp.value)\n\t\t\tbase.push(remoteAtomicOp.value)\n\t\t\treturn {\n\t\t\t\tmerged: true,\n\t\t\t\tvalue: base,\n\t\t\t\tstrategy: 'atomic-append',\n\t\t\t}\n\t\t}\n\n\t\tcase 'remove': {\n\t\t\t// Remove both items from the base\n\t\t\tconst base = Array.isArray(baseValue) ? [...baseValue] : []\n\t\t\tconst localItem = localAtomicOp.value\n\t\t\tconst remoteItem = remoteAtomicOp.value\n\t\t\tconst result = base.filter((item) => item !== localItem && item !== remoteItem)\n\t\t\treturn {\n\t\t\t\tmerged: true,\n\t\t\t\tvalue: result,\n\t\t\t\tstrategy: 'atomic-remove',\n\t\t\t}\n\t\t}\n\n\t\tdefault:\n\t\t\treturn { merged: false }\n\t}\n}\n","import * as Y from 'yjs'\n\nexport type RichtextValue = string | Uint8Array | ArrayBuffer | null | undefined\n\nconst TEXT_KEY = 'content'\n\n/**\n * Merges richtext values using Yjs CRDT updates.\n */\nexport function mergeRichtext(\n\tlocalValue: RichtextValue,\n\tremoteValue: RichtextValue,\n\tbaseValue: RichtextValue,\n): Uint8Array {\n\tconst mergedDoc = new Y.Doc()\n\n\tY.applyUpdate(mergedDoc, toYjsUpdate(baseValue))\n\tY.applyUpdate(mergedDoc, toYjsUpdate(localValue))\n\tY.applyUpdate(mergedDoc, toYjsUpdate(remoteValue))\n\n\treturn Y.encodeStateAsUpdate(mergedDoc)\n}\n\n/**\n * Converts a richtext state update to plain text.\n */\nexport function richtextToString(value: RichtextValue): string {\n\tconst doc = new Y.Doc()\n\tY.applyUpdate(doc, toYjsUpdate(value))\n\treturn doc.getText(TEXT_KEY).toString()\n}\n\n/**\n * Converts a plain string to a Yjs state update.\n */\nexport function stringToRichtextUpdate(value: string): Uint8Array {\n\tconst doc = new Y.Doc()\n\tdoc.getText(TEXT_KEY).insert(0, value)\n\treturn Y.encodeStateAsUpdate(doc)\n}\n\nfunction toYjsUpdate(value: RichtextValue): Uint8Array {\n\tif (value === null || value === undefined) {\n\t\treturn Y.encodeStateAsUpdate(new Y.Doc())\n\t}\n\n\tif (typeof value === 'string') {\n\t\treturn stringToRichtextUpdate(value)\n\t}\n\n\tif (value instanceof Uint8Array) {\n\t\treturn value\n\t}\n\n\tif (value instanceof ArrayBuffer) {\n\t\treturn new Uint8Array(value)\n\t}\n\n\tthrow new Error('Richtext value must be a string, Uint8Array, ArrayBuffer, null, or undefined.')\n}\n","import { HybridLogicalClock } from '@korajs/core'\nimport type { FieldMergeStrategy, HLCTimestamp, Operation } from '@korajs/core'\n\n/**\n * Schema-declared merge strategies.\n *\n * These are invoked when a field has an explicit `mergeStrategy` declared in the schema\n * via `t.number().merge('counter')` etc. They override the default kind-based auto-merge.\n */\n\n/**\n * Counter merge: sum of deltas from base.\n * Both sides' changes are additive relative to the base value.\n *\n * Example: base=10, local=15, remote=12 → result = 10 + 5 + 2 = 17\n */\nexport function counterMerge(\n\tlocalValue: unknown,\n\tremoteValue: unknown,\n\tbaseValue: unknown,\n): unknown {\n\tconst base = typeof baseValue === 'number' ? baseValue : 0\n\tconst local = typeof localValue === 'number' ? localValue : base\n\tconst remote = typeof remoteValue === 'number' ? remoteValue : base\n\n\tconst localDelta = local - base\n\tconst remoteDelta = remote - base\n\treturn base + localDelta + remoteDelta\n}\n\n/**\n * Max merge: keep the maximum of all concurrent values.\n * Works for numbers and timestamps.\n */\nexport function maxMerge(localValue: unknown, remoteValue: unknown, baseValue: unknown): unknown {\n\tconst vals: number[] = []\n\tif (typeof baseValue === 'number') vals.push(baseValue)\n\tif (typeof localValue === 'number') vals.push(localValue)\n\tif (typeof remoteValue === 'number') vals.push(remoteValue)\n\n\tif (vals.length === 0) return baseValue\n\treturn Math.max(...vals)\n}\n\n/**\n * Min merge: keep the minimum of all concurrent values.\n * Works for numbers and timestamps.\n */\nexport function minMerge(localValue: unknown, remoteValue: unknown, baseValue: unknown): unknown {\n\tconst vals: number[] = []\n\tif (typeof baseValue === 'number') vals.push(baseValue)\n\tif (typeof localValue === 'number') vals.push(localValue)\n\tif (typeof remoteValue === 'number') vals.push(remoteValue)\n\n\tif (vals.length === 0) return baseValue\n\treturn Math.min(...vals)\n}\n\n/**\n * Append-only merge for arrays: concatenate additions from both sides.\n * Unlike add-wins-set, removals are ignored — items can only be added, never removed.\n *\n * Result = base + local additions + remote additions (preserving order)\n */\nexport function appendOnlyMerge(\n\tlocalValue: unknown,\n\tremoteValue: unknown,\n\tbaseValue: unknown,\n): unknown[] {\n\tconst base = Array.isArray(baseValue) ? baseValue : []\n\tconst local = Array.isArray(localValue) ? localValue : []\n\tconst remote = Array.isArray(remoteValue) ? remoteValue : []\n\n\tconst serialize = (v: unknown): string => JSON.stringify(v)\n\tconst baseSet = new Set(base.map(serialize))\n\n\t// Additions from each side (present in their array but not in base)\n\tconst result = [...base]\n\tconst resultSet = new Set(base.map(serialize))\n\n\tfor (const item of local) {\n\t\tconst s = serialize(item)\n\t\tif (!baseSet.has(s) && !resultSet.has(s)) {\n\t\t\tresult.push(item)\n\t\t\tresultSet.add(s)\n\t\t}\n\t}\n\n\tfor (const item of remote) {\n\t\tconst s = serialize(item)\n\t\tif (!baseSet.has(s) && !resultSet.has(s)) {\n\t\t\tresult.push(item)\n\t\t\tresultSet.add(s)\n\t\t}\n\t}\n\n\treturn result\n}\n\n/**\n * Server-authoritative merge: always prefer the remote (server) value.\n */\nexport function serverAuthoritativeMerge(\n\t_localValue: unknown,\n\tremoteValue: unknown,\n\t_baseValue: unknown,\n): unknown {\n\treturn remoteValue\n}\n\n/**\n * Dispatch to the appropriate schema-declared strategy.\n *\n * @returns The merged value, or null if the strategy is not handled here\n * (e.g., 'lww' and 'union' which are handled by the default autoMerge).\n */\nexport function applySchemaStrategy(\n\tstrategy: FieldMergeStrategy,\n\tlocalValue: unknown,\n\tremoteValue: unknown,\n\tbaseValue: unknown,\n\tlocalTimestamp: HLCTimestamp,\n\tremoteTimestamp: HLCTimestamp,\n): { value: unknown; strategyName: string } | null {\n\tswitch (strategy) {\n\t\tcase 'counter':\n\t\t\treturn {\n\t\t\t\tvalue: counterMerge(localValue, remoteValue, baseValue),\n\t\t\t\tstrategyName: 'schema-counter',\n\t\t\t}\n\t\tcase 'max':\n\t\t\treturn { value: maxMerge(localValue, remoteValue, baseValue), strategyName: 'schema-max' }\n\t\tcase 'min':\n\t\t\treturn { value: minMerge(localValue, remoteValue, baseValue), strategyName: 'schema-min' }\n\t\tcase 'append-only':\n\t\t\treturn {\n\t\t\t\tvalue: appendOnlyMerge(localValue, remoteValue, baseValue),\n\t\t\t\tstrategyName: 'schema-append-only',\n\t\t\t}\n\t\tcase 'server-authoritative':\n\t\t\treturn {\n\t\t\t\tvalue: serverAuthoritativeMerge(localValue, remoteValue, baseValue),\n\t\t\t\tstrategyName: 'schema-server-authoritative',\n\t\t\t}\n\t\tcase 'lww':\n\t\tcase 'union':\n\t\t\t// These are handled by the default autoMerge (same behavior)\n\t\t\treturn null\n\t\tdefault:\n\t\t\treturn null\n\t}\n}\n","import type {\n\tAtomicOp,\n\tCustomResolver,\n\tFieldDescriptor,\n\tHLCTimestamp,\n\tOperation,\n} from '@korajs/core'\nimport type { MergeTrace } from '@korajs/core'\nimport { addWinsSet } from '../strategies/add-wins-set'\nimport { mergeAtomicOps } from '../strategies/atomic-merge'\nimport { lastWriteWins } from '../strategies/lww'\nimport { applySchemaStrategy } from '../strategies/schema-strategies'\nimport { mergeRichtext } from '../strategies/yjs-richtext'\nimport type { RichtextValue } from '../strategies/yjs-richtext'\nimport type { FieldMergeResult } from '../types'\n\n/**\n * Merges a single field from two concurrent operations.\n *\n * Dispatches to the appropriate strategy based on field kind:\n * - string, number, boolean, enum, timestamp → LWW (Last-Write-Wins via HLC)\n * - array → add-wins set (union of additions, only mutual removals)\n * - richtext → Yjs CRDT merge\n *\n * If a custom resolver (Tier 3) is provided, it overrides the default strategy.\n *\n * Handles non-conflict cases where only one side modified the field:\n * - Only local changed → take local value\n * - Only remote changed → take remote value\n * - Neither changed → keep base value\n *\n * @param fieldName - Name of the field being merged\n * @param localOp - The local operation\n * @param remoteOp - The remote operation\n * @param baseState - Full record state before either operation\n * @param fieldDescriptor - Schema descriptor for this field\n * @param resolver - Optional Tier 3 custom resolver for this field\n * @returns The merged field value and a trace for DevTools\n */\nexport function mergeField(\n\tfieldName: string,\n\tlocalOp: Operation,\n\tremoteOp: Operation,\n\tbaseState: Record<string, unknown>,\n\tfieldDescriptor: FieldDescriptor,\n\tresolver?: CustomResolver,\n): FieldMergeResult {\n\tconst startTime = Date.now()\n\n\tconst localData = localOp.data ?? {}\n\tconst remoteData = remoteOp.data ?? {}\n\tconst localPrevious = localOp.previousData ?? {}\n\tconst remotePrevious = remoteOp.previousData ?? {}\n\n\tconst localChanged = fieldName in localData\n\tconst remoteChanged = fieldName in remoteData\n\tconst baseValue = baseState[fieldName]\n\n\t// Non-conflict: only one side changed\n\tif (localChanged && !remoteChanged) {\n\t\treturn createResult(\n\t\t\tlocalData[fieldName],\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tlocalData[fieldName],\n\t\t\tbaseValue,\n\t\t\tbaseValue,\n\t\t\t'no-conflict-local',\n\t\t\t1,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\tif (!localChanged && remoteChanged) {\n\t\treturn createResult(\n\t\t\tremoteData[fieldName],\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tbaseValue,\n\t\t\tremoteData[fieldName],\n\t\t\tbaseValue,\n\t\t\t'no-conflict-remote',\n\t\t\t1,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\tif (!localChanged && !remoteChanged) {\n\t\treturn createResult(\n\t\t\tbaseValue,\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tbaseValue,\n\t\t\tbaseValue,\n\t\t\tbaseValue,\n\t\t\t'no-conflict-unchanged',\n\t\t\t1,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\t// Both sides changed this field — conflict resolution needed\n\n\tconst localValue = localData[fieldName]\n\tconst remoteValue = remoteData[fieldName]\n\n\t// Tier 3: Custom resolver takes precedence\n\tif (resolver !== undefined) {\n\t\tconst resolved = resolver(localValue, remoteValue, baseValue)\n\t\treturn createResult(\n\t\t\tresolved,\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tlocalValue,\n\t\t\tremoteValue,\n\t\t\tbaseValue,\n\t\t\t'custom',\n\t\t\t3,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\t// Atomic ops: when both sides used atomic operations on this field,\n\t// compose the intents instead of falling through to LWW.\n\tconst localAtomicOp = localOp.atomicOps?.[fieldName]\n\tconst remoteAtomicOp = remoteOp.atomicOps?.[fieldName]\n\n\tif (localAtomicOp !== undefined && remoteAtomicOp !== undefined) {\n\t\tconst atomicResult = mergeAtomicOps(\n\t\t\tlocalAtomicOp as AtomicOp,\n\t\t\tremoteAtomicOp as AtomicOp,\n\t\t\tbaseValue,\n\t\t)\n\t\tif (atomicResult.merged) {\n\t\t\treturn createResult(\n\t\t\t\tatomicResult.value,\n\t\t\t\tfieldName,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tlocalValue,\n\t\t\t\tremoteValue,\n\t\t\t\tbaseValue,\n\t\t\t\tatomicResult.strategy,\n\t\t\t\t1,\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\t\t// If atomic merge fell back (mismatched types), continue to auto-merge\n\t}\n\n\t// Schema-declared merge strategy: overrides default kind-based auto-merge\n\tif (fieldDescriptor.mergeStrategy !== null && fieldDescriptor.mergeStrategy !== undefined) {\n\t\tconst schemaResult = applySchemaStrategy(\n\t\t\tfieldDescriptor.mergeStrategy,\n\t\t\tlocalValue,\n\t\t\tremoteValue,\n\t\t\tbaseValue,\n\t\t\tlocalOp.timestamp,\n\t\t\tremoteOp.timestamp,\n\t\t)\n\t\tif (schemaResult !== null) {\n\t\t\treturn createResult(\n\t\t\t\tschemaResult.value,\n\t\t\t\tfieldName,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tlocalValue,\n\t\t\t\tremoteValue,\n\t\t\t\tbaseValue,\n\t\t\t\tschemaResult.strategyName,\n\t\t\t\t1,\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\t\t// 'lww' and 'union' fall through to autoMerge (same behavior as defaults)\n\t}\n\n\t// Tier 1: Auto-merge based on field kind\n\treturn autoMerge(\n\t\tfieldName,\n\t\tlocalOp,\n\t\tremoteOp,\n\t\tlocalValue,\n\t\tremoteValue,\n\t\tbaseValue,\n\t\tfieldDescriptor,\n\t\tstartTime,\n\t)\n}\n\nfunction autoMerge(\n\tfieldName: string,\n\tlocalOp: Operation,\n\tremoteOp: Operation,\n\tlocalValue: unknown,\n\tremoteValue: unknown,\n\tbaseValue: unknown,\n\tfieldDescriptor: FieldDescriptor,\n\tstartTime: number,\n): FieldMergeResult {\n\tswitch (fieldDescriptor.kind) {\n\t\tcase 'string':\n\t\tcase 'number':\n\t\tcase 'boolean':\n\t\tcase 'enum':\n\t\tcase 'timestamp': {\n\t\t\tconst lwwResult = lastWriteWins(\n\t\t\t\tlocalValue,\n\t\t\t\tremoteValue,\n\t\t\t\tlocalOp.timestamp,\n\t\t\t\tremoteOp.timestamp,\n\t\t\t)\n\t\t\treturn createResult(\n\t\t\t\tlwwResult.value,\n\t\t\t\tfieldName,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tlocalValue,\n\t\t\t\tremoteValue,\n\t\t\t\tbaseValue,\n\t\t\t\t'lww',\n\t\t\t\t1,\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'array': {\n\t\t\tconst baseArr = Array.isArray(baseValue) ? baseValue : []\n\t\t\tconst localArr = Array.isArray(localValue) ? localValue : []\n\t\t\tconst remoteArr = Array.isArray(remoteValue) ? remoteValue : []\n\n\t\t\tconst merged = addWinsSet(localArr, remoteArr, baseArr)\n\t\t\treturn createResult(\n\t\t\t\tmerged,\n\t\t\t\tfieldName,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tlocalValue,\n\t\t\t\tremoteValue,\n\t\t\t\tbaseValue,\n\t\t\t\t'add-wins-set',\n\t\t\t\t1,\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'richtext': {\n\t\t\tconst merged = mergeRichtext(\n\t\t\t\tlocalValue as RichtextValue,\n\t\t\t\tremoteValue as RichtextValue,\n\t\t\t\tbaseValue as RichtextValue,\n\t\t\t)\n\t\t\treturn createResult(\n\t\t\t\tmerged,\n\t\t\t\tfieldName,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tlocalValue,\n\t\t\t\tremoteValue,\n\t\t\t\tbaseValue,\n\t\t\t\t'crdt-text',\n\t\t\t\t1,\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunction createResult(\n\tvalue: unknown,\n\tfield: string,\n\toperationA: Operation,\n\toperationB: Operation,\n\tinputA: unknown,\n\tinputB: unknown,\n\tbase: unknown | null,\n\tstrategy: string,\n\ttier: 1 | 2 | 3,\n\tstartTime: number,\n): FieldMergeResult {\n\tconst trace: MergeTrace = {\n\t\toperationA,\n\t\toperationB,\n\t\tfield,\n\t\tstrategy,\n\t\tinputA,\n\t\tinputB,\n\t\tbase,\n\t\toutput: value,\n\t\ttier,\n\t\tconstraintViolated: null,\n\t\tduration: Date.now() - startTime,\n\t}\n\treturn { value, trace }\n}\n","import type { CollectionDefinition, Constraint } from '@korajs/core'\nimport type { ConstraintContext, ConstraintViolation } from '../types'\n\n/**\n * Checks all constraints on a collection against a candidate merged record.\n *\n * Called after Tier 1+3 merge produces a candidate state. Each violated\n * constraint is returned as a ConstraintViolation for Tier 2 resolution.\n *\n * @param mergedRecord - The candidate record state after field-level merge\n * @param recordId - ID of the record being merged\n * @param collection - Name of the collection\n * @param collectionDef - Schema definition for the collection\n * @param constraintContext - Pluggable DB lookup interface\n * @returns Array of violated constraints (empty if all pass)\n */\nexport async function checkConstraints(\n\tmergedRecord: Record<string, unknown>,\n\trecordId: string,\n\tcollection: string,\n\tcollectionDef: CollectionDefinition,\n\tconstraintContext: ConstraintContext,\n): Promise<ConstraintViolation[]> {\n\tconst violations: ConstraintViolation[] = []\n\n\tfor (const constraint of collectionDef.constraints) {\n\t\t// For unique and capacity constraints, where clause filters which records\n\t\t// the constraint applies to. For referential constraints, where clause\n\t\t// stores metadata (e.g., the referenced collection), not a record filter.\n\t\tif (\n\t\t\tconstraint.type !== 'referential' &&\n\t\t\tconstraint.where !== undefined &&\n\t\t\t!matchesWhere(mergedRecord, constraint.where)\n\t\t) {\n\t\t\tcontinue\n\t\t}\n\n\t\tconst violation = await checkSingleConstraint(\n\t\t\tconstraint,\n\t\t\tmergedRecord,\n\t\t\trecordId,\n\t\t\tcollection,\n\t\t\tconstraintContext,\n\t\t)\n\t\tif (violation !== null) {\n\t\t\tviolations.push(violation)\n\t\t}\n\t}\n\n\treturn violations\n}\n\nasync function checkSingleConstraint(\n\tconstraint: Constraint,\n\tmergedRecord: Record<string, unknown>,\n\trecordId: string,\n\tcollection: string,\n\tctx: ConstraintContext,\n): Promise<ConstraintViolation | null> {\n\tswitch (constraint.type) {\n\t\tcase 'unique':\n\t\t\treturn checkUniqueConstraint(constraint, mergedRecord, recordId, collection, ctx)\n\t\tcase 'capacity':\n\t\t\treturn checkCapacityConstraint(constraint, mergedRecord, collection, ctx)\n\t\tcase 'referential':\n\t\t\treturn checkReferentialConstraint(constraint, mergedRecord, collection, ctx)\n\t}\n}\n\nasync function checkUniqueConstraint(\n\tconstraint: Constraint,\n\tmergedRecord: Record<string, unknown>,\n\trecordId: string,\n\tcollection: string,\n\tctx: ConstraintContext,\n): Promise<ConstraintViolation | null> {\n\t// Build a where clause from the constraint fields and the merged record values\n\tconst where: Record<string, unknown> = {}\n\tfor (const field of constraint.fields) {\n\t\twhere[field] = mergedRecord[field]\n\t}\n\n\tconst existing = await ctx.queryRecords(collection, where)\n\t// Filter out the current record itself\n\tconst duplicates = existing.filter((r) => r.id !== recordId)\n\n\tif (duplicates.length > 0) {\n\t\treturn {\n\t\t\tconstraint,\n\t\t\tfields: constraint.fields,\n\t\t\tmessage:\n\t\t\t\t`Unique constraint violated on fields [${constraint.fields.join(', ')}] ` +\n\t\t\t\t`in collection \"${collection}\": duplicate value(s) found`,\n\t\t}\n\t}\n\n\treturn null\n}\n\nasync function checkCapacityConstraint(\n\tconstraint: Constraint,\n\tmergedRecord: Record<string, unknown>,\n\tcollection: string,\n\tctx: ConstraintContext,\n): Promise<ConstraintViolation | null> {\n\t// Capacity constraint: the where clause defines the group, fields[0] is the capacity limit field name\n\t// We count records matching the where clause\n\tconst where = constraint.where ?? {}\n\tconst count = await ctx.countRecords(collection, where)\n\n\t// The capacity limit is stored in the first field name as a numeric value\n\t// For capacity constraints, fields represent the fields that define the capacity group\n\t// The capacity limit itself needs to come from somewhere — we use the constraint's where clause\n\t// to scope the group, and check if adding this record exceeds the existing count\n\t// For simplicity: if there's a capacity constraint, the presence of this violation\n\t// means the group is at or over capacity\n\tif (count > 0 && constraint.fields.length > 0) {\n\t\t// Build the group match from constraint fields\n\t\tconst groupWhere: Record<string, unknown> = { ...where }\n\t\tfor (const field of constraint.fields) {\n\t\t\tgroupWhere[field] = mergedRecord[field]\n\t\t}\n\n\t\tconst groupCount = await ctx.countRecords(collection, groupWhere)\n\t\t// Capacity constraints use the where clause to define the limit\n\t\t// If we can't determine a numeric limit, we flag the violation\n\t\t// The resolver will handle the actual resolution logic\n\t\tif (groupCount > 1) {\n\t\t\treturn {\n\t\t\t\tconstraint,\n\t\t\t\tfields: constraint.fields,\n\t\t\t\tmessage:\n\t\t\t\t\t`Capacity constraint violated on fields [${constraint.fields.join(', ')}] ` +\n\t\t\t\t\t`in collection \"${collection}\": group count ${groupCount} exceeds limit`,\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null\n}\n\nasync function checkReferentialConstraint(\n\tconstraint: Constraint,\n\tmergedRecord: Record<string, unknown>,\n\tcollection: string,\n\tctx: ConstraintContext,\n): Promise<ConstraintViolation | null> {\n\t// Referential constraint: the first field is the foreign key field,\n\t// and where.collection (or similar) would specify the referenced collection.\n\t// For a simple check: the field value should reference an existing record.\n\tif (constraint.fields.length === 0) {\n\t\treturn null\n\t}\n\n\tconst fkField = constraint.fields[0]\n\tif (fkField === undefined) {\n\t\treturn null\n\t}\n\n\tconst fkValue = mergedRecord[fkField]\n\tif (fkValue === null || fkValue === undefined) {\n\t\t// Null FK is allowed (the relation is optional)\n\t\treturn null\n\t}\n\n\t// Look up the referenced record. The referenced collection is specified\n\t// in constraint.where.collection (convention for referential constraints).\n\tconst referencedCollection =\n\t\tconstraint.where !== undefined ? (constraint.where.collection as string | undefined) : undefined\n\tif (referencedCollection === undefined) {\n\t\treturn null\n\t}\n\n\tconst referenced = await ctx.queryRecords(referencedCollection, { id: fkValue })\n\tif (referenced.length === 0) {\n\t\treturn {\n\t\t\tconstraint,\n\t\t\tfields: constraint.fields,\n\t\t\tmessage:\n\t\t\t\t`Referential constraint violated on field \"${fkField}\" ` +\n\t\t\t\t`in collection \"${collection}\": referenced record not found ` +\n\t\t\t\t`in \"${referencedCollection}\" with id \"${String(fkValue)}\"`,\n\t\t}\n\t}\n\n\treturn null\n}\n\n/**\n * Check if a record matches a where clause (simple equality check).\n */\nfunction matchesWhere(record: Record<string, unknown>, where: Record<string, unknown>): boolean {\n\tfor (const [key, value] of Object.entries(where)) {\n\t\tif (record[key] !== value) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n","import { HybridLogicalClock } from '@korajs/core'\nimport type { CollectionDefinition, Operation } from '@korajs/core'\nimport type { MergeTrace } from '@korajs/core'\nimport type { ConstraintViolation } from '../types'\n\n/**\n * Result of resolving a constraint violation.\n */\nexport interface ConstraintResolution {\n\t/** The updated record after constraint resolution */\n\tresolvedRecord: Record<string, unknown>\n\t/** Trace of the resolution decision for DevTools */\n\ttrace: MergeTrace\n}\n\n/**\n * Resolves a constraint violation by applying the constraint's onConflict strategy.\n *\n * Strategies:\n * - `last-write-wins`: The operation with the later HLC timestamp wins entirely\n * - `first-write-wins`: The operation with the earlier HLC timestamp wins entirely\n * - `priority-field`: Compares a designated priority field to determine the winner\n * - `server-decides`: Returns a marker indicating deferred server resolution\n * - `custom`: Calls the constraint's resolve function\n *\n * @param violation - The constraint violation to resolve\n * @param mergedRecord - The current candidate record state\n * @param localOp - The local operation\n * @param remoteOp - The remote operation\n * @param baseState - The record state before either operation\n * @returns The resolved record and a trace\n */\nexport function resolveConstraintViolation(\n\tviolation: ConstraintViolation,\n\tmergedRecord: Record<string, unknown>,\n\tlocalOp: Operation,\n\tremoteOp: Operation,\n\tbaseState: Record<string, unknown>,\n): ConstraintResolution {\n\tconst startTime = Date.now()\n\tconst { constraint } = violation\n\n\tswitch (constraint.onConflict) {\n\t\tcase 'last-write-wins': {\n\t\t\tconst comparison = HybridLogicalClock.compare(localOp.timestamp, remoteOp.timestamp)\n\t\t\tconst winner = comparison >= 0 ? localOp : remoteOp\n\t\t\tconst resolvedRecord = applyWinnerFields(mergedRecord, winner, violation.fields)\n\t\t\treturn createResolution(\n\t\t\t\tresolvedRecord,\n\t\t\t\tviolation,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tbaseState,\n\t\t\t\t'constraint-lww',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'first-write-wins': {\n\t\t\tconst comparison = HybridLogicalClock.compare(localOp.timestamp, remoteOp.timestamp)\n\t\t\tconst winner = comparison <= 0 ? localOp : remoteOp\n\t\t\tconst resolvedRecord = applyWinnerFields(mergedRecord, winner, violation.fields)\n\t\t\treturn createResolution(\n\t\t\t\tresolvedRecord,\n\t\t\t\tviolation,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tbaseState,\n\t\t\t\t'constraint-fww',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'priority-field': {\n\t\t\tconst priorityField = constraint.priorityField\n\t\t\tif (priorityField === undefined) {\n\t\t\t\t// Fallback to LWW if no priority field specified\n\t\t\t\tconst comparison = HybridLogicalClock.compare(localOp.timestamp, remoteOp.timestamp)\n\t\t\t\tconst winner = comparison >= 0 ? localOp : remoteOp\n\t\t\t\tconst resolvedRecord = applyWinnerFields(mergedRecord, winner, violation.fields)\n\t\t\t\treturn createResolution(\n\t\t\t\t\tresolvedRecord,\n\t\t\t\t\tviolation,\n\t\t\t\t\tlocalOp,\n\t\t\t\t\tremoteOp,\n\t\t\t\t\tbaseState,\n\t\t\t\t\t'constraint-priority-fallback-lww',\n\t\t\t\t\tstartTime,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst localPriority = getFieldValue(localOp, priorityField, mergedRecord)\n\t\t\tconst remotePriority = getFieldValue(remoteOp, priorityField, mergedRecord)\n\n\t\t\t// Higher priority value wins (numeric or string comparison)\n\t\t\tconst winner = comparePriority(localPriority, remotePriority) >= 0 ? localOp : remoteOp\n\t\t\tconst resolvedRecord = applyWinnerFields(mergedRecord, winner, violation.fields)\n\t\t\treturn createResolution(\n\t\t\t\tresolvedRecord,\n\t\t\t\tviolation,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tbaseState,\n\t\t\t\t'constraint-priority',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'server-decides': {\n\t\t\t// Mark the record for server-side resolution. The sync layer handles this.\n\t\t\t// We keep the current merged record but flag it.\n\t\t\tconst resolvedRecord = {\n\t\t\t\t...mergedRecord,\n\t\t\t\t_pendingServerResolution: true,\n\t\t\t}\n\t\t\treturn createResolution(\n\t\t\t\tresolvedRecord,\n\t\t\t\tviolation,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tbaseState,\n\t\t\t\t'constraint-server-decides',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\n\t\tcase 'custom': {\n\t\t\tif (constraint.resolve === undefined) {\n\t\t\t\t// No custom resolver provided — fallback to LWW\n\t\t\t\tconst comparison = HybridLogicalClock.compare(localOp.timestamp, remoteOp.timestamp)\n\t\t\t\tconst winner = comparison >= 0 ? localOp : remoteOp\n\t\t\t\tconst resolvedRecord = applyWinnerFields(mergedRecord, winner, violation.fields)\n\t\t\t\treturn createResolution(\n\t\t\t\t\tresolvedRecord,\n\t\t\t\t\tviolation,\n\t\t\t\t\tlocalOp,\n\t\t\t\t\tremoteOp,\n\t\t\t\t\tbaseState,\n\t\t\t\t\t'constraint-custom-fallback-lww',\n\t\t\t\t\tstartTime,\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// For each violated field, call the custom resolver with the local, remote, and base values\n\t\t\tconst resolvedRecord = { ...mergedRecord }\n\t\t\tfor (const field of violation.fields) {\n\t\t\t\tconst localVal = getFieldValue(localOp, field, mergedRecord)\n\t\t\t\tconst remoteVal = getFieldValue(remoteOp, field, mergedRecord)\n\t\t\t\tconst baseVal = baseState[field]\n\t\t\t\tresolvedRecord[field] = constraint.resolve(localVal, remoteVal, baseVal)\n\t\t\t}\n\t\t\treturn createResolution(\n\t\t\t\tresolvedRecord,\n\t\t\t\tviolation,\n\t\t\t\tlocalOp,\n\t\t\t\tremoteOp,\n\t\t\t\tbaseState,\n\t\t\t\t'constraint-custom',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t}\n\t}\n}\n\n/**\n * Apply the winning operation's field values to the merged record\n * for the specific fields involved in the constraint violation.\n */\nfunction applyWinnerFields(\n\tmergedRecord: Record<string, unknown>,\n\twinner: Operation,\n\tfields: string[],\n): Record<string, unknown> {\n\tconst result = { ...mergedRecord }\n\tconst winnerData = winner.data ?? {}\n\tfor (const field of fields) {\n\t\tif (field in winnerData) {\n\t\t\tresult[field] = winnerData[field]\n\t\t}\n\t}\n\treturn result\n}\n\n/**\n * Get a field value from an operation's data, falling back to the merged record.\n */\nfunction getFieldValue(\n\top: Operation,\n\tfield: string,\n\tmergedRecord: Record<string, unknown>,\n): unknown {\n\tconst data = op.data ?? {}\n\tif (field in data) {\n\t\treturn data[field]\n\t}\n\treturn mergedRecord[field]\n}\n\n/**\n * Compare two priority values. Supports numbers and strings.\n * Returns positive if a > b, negative if a < b, zero if equal.\n */\nfunction comparePriority(a: unknown, b: unknown): number {\n\tif (typeof a === 'number' && typeof b === 'number') {\n\t\treturn a - b\n\t}\n\tif (typeof a === 'string' && typeof b === 'string') {\n\t\treturn a < b ? -1 : a > b ? 1 : 0\n\t}\n\t// Mixed types: convert to string for comparison\n\treturn String(a) < String(b) ? -1 : String(a) > String(b) ? 1 : 0\n}\n\nfunction createResolution(\n\tresolvedRecord: Record<string, unknown>,\n\tviolation: ConstraintViolation,\n\tlocalOp: Operation,\n\tremoteOp: Operation,\n\tbaseState: Record<string, unknown>,\n\tstrategy: string,\n\tstartTime: number,\n): ConstraintResolution {\n\tconst field = violation.fields.join(', ')\n\tconst trace: MergeTrace = {\n\t\toperationA: localOp,\n\t\toperationB: remoteOp,\n\t\tfield,\n\t\tstrategy,\n\t\tinputA: extractFieldValues(localOp, violation.fields),\n\t\tinputB: extractFieldValues(remoteOp, violation.fields),\n\t\tbase: extractFields(baseState, violation.fields),\n\t\toutput: extractFields(resolvedRecord, violation.fields),\n\t\ttier: 2,\n\t\tconstraintViolated: violation.message,\n\t\tduration: Date.now() - startTime,\n\t}\n\treturn { resolvedRecord, trace }\n}\n\nfunction extractFieldValues(op: Operation, fields: string[]): Record<string, unknown> {\n\tconst data = op.data ?? {}\n\tconst result: Record<string, unknown> = {}\n\tfor (const field of fields) {\n\t\tresult[field] = data[field]\n\t}\n\treturn result\n}\n\nfunction extractFields(record: Record<string, unknown>, fields: string[]): Record<string, unknown> {\n\tconst result: Record<string, unknown> = {}\n\tfor (const field of fields) {\n\t\tresult[field] = record[field]\n\t}\n\treturn result\n}\n","import { HybridLogicalClock } from '@korajs/core'\nimport type {\n\tMergeTrace,\n\tOnDeleteAction,\n\tOperation,\n\tRelationDefinition,\n\tSchemaDefinition,\n} from '@korajs/core'\n\n/**\n * Describes an incoming relation to a target collection.\n * Used by the delete-side referential integrity checker to know which\n * source collections reference the collection being deleted from.\n */\nexport interface MergeIncomingRelation {\n\t/** Name of the relation in the schema (e.g., 'todoBelongsToProject') */\n\trelationName: string\n\t/** The collection that holds the foreign key (source of the relation) */\n\tsourceCollection: string\n\t/** The foreign key field in the source collection */\n\tforeignKeyField: string\n\t/** What to do when the referenced record is deleted */\n\tonDelete: OnDeleteAction\n}\n\n/**\n * Pluggable context for querying the database during referential integrity checks.\n * The store layer provides the implementation; the merge package only depends on this interface.\n */\nexport interface ReferentialMergeContext {\n\t/** Query records in a collection matching a where clause */\n\tqueryRecords(\n\t\tcollection: string,\n\t\twhere: Record<string, unknown>,\n\t): Promise<Record<string, unknown>[]>\n\t/** Check if a record exists in a collection */\n\trecordExists(collection: string, recordId: string): Promise<boolean>\n}\n\n/**\n * Result of a referential integrity check on a delete operation.\n */\nexport interface ReferentialCheckResult {\n\t/** Whether the delete operation is allowed to proceed */\n\tallowed: boolean\n\t/** Side-effect operations generated by cascade/set-null policies */\n\tsideEffectOps: SideEffectOp[]\n\t/** MergeTraces for every decision made (feeds DevTools) */\n\ttraces: MergeTrace[]\n}\n\n/**\n * A side-effect operation generated during referential integrity enforcement.\n * These are not full Operations; they describe mutations the caller must apply.\n */\nexport interface SideEffectOp {\n\t/** The type of mutation to apply */\n\ttype: 'delete' | 'update'\n\t/** The collection containing the affected record */\n\tcollection: string\n\t/** The ID of the affected record */\n\trecordId: string\n\t/** For updates: the new field values. null for deletes. */\n\tdata: Record<string, unknown> | null\n\t/** For updates: the previous field values. null for deletes. */\n\tpreviousData: Record<string, unknown> | null\n\t/** The onDelete policy that produced this side effect */\n\tpolicy: OnDeleteAction\n\t/** The relation that triggered this side effect */\n\trelationName: string\n}\n\n/**\n * Result of resolving a concurrent delete-vs-insert conflict.\n */\nexport interface DeleteVsInsertResolution {\n\t/** Whether to block the delete or allow it */\n\taction: 'block-delete' | 'allow-delete'\n\t/** Any side-effect operations (for cascade/set-null after allowing delete) */\n\tsideEffects: SideEffectOp[]\n\t/** Trace of the resolution decision, or null if no trace needed */\n\ttrace: MergeTrace | null\n}\n\n/**\n * Builds a lookup map from target collection to all incoming relations.\n *\n * This pre-computes which collections reference a given collection via foreign keys,\n * so that delete operations can efficiently find all relations that need enforcement.\n *\n * @param schema - The full schema definition with relations\n * @returns Map from target collection name to array of incoming relations, sorted by relation name for determinism\n *\n * @example\n * ```typescript\n * const lookup = buildMergeRelationLookup(schema)\n * // lookup.get('projects') => [{ relationName: 'todoBelongsToProject', sourceCollection: 'todos', ... }]\n * ```\n */\nexport function buildMergeRelationLookup(\n\tschema: SchemaDefinition,\n): Map<string, MergeIncomingRelation[]> {\n\tconst lookup = new Map<string, MergeIncomingRelation[]>()\n\n\t// Collect all relation names and sort them for deterministic processing order\n\tconst relationNames = Object.keys(schema.relations).sort()\n\n\tfor (const relationName of relationNames) {\n\t\tconst relation = schema.relations[relationName] as RelationDefinition | undefined\n\t\tif (relation === undefined) {\n\t\t\tcontinue\n\t\t}\n\n\t\tconst targetCollection = relation.to\n\t\tconst existing = lookup.get(targetCollection) ?? []\n\t\texisting.push({\n\t\t\trelationName,\n\t\t\tsourceCollection: relation.from,\n\t\t\tforeignKeyField: relation.field,\n\t\t\tonDelete: relation.onDelete,\n\t\t})\n\t\tlookup.set(targetCollection, existing)\n\t}\n\n\t// Sort each list by relation name for deterministic processing order.\n\t// buildMergeRelationLookup already inserts in sorted order because we\n\t// sort relationNames above, but this guarantees correctness regardless.\n\tfor (const [, relations] of lookup) {\n\t\trelations.sort((a, b) =>\n\t\t\ta.relationName < b.relationName ? -1 : a.relationName > b.relationName ? 1 : 0,\n\t\t)\n\t}\n\n\treturn lookup\n}\n\n/**\n * Checks referential integrity when a delete operation is being applied.\n *\n * For each relation that targets the deleted record's collection, queries\n * for referencing records and applies the relation's onDelete policy:\n * - `restrict`: Blocks the delete if any references exist\n * - `cascade`: Allows the delete and generates cascaded delete SideEffectOps\n * - `set-null`: Allows the delete and generates update SideEffectOps to null out FKs\n * - `no-action`: Allows the delete with no side effects (dangling references permitted)\n *\n * Processing is deterministic: relations are processed in sorted order by name,\n * and referencing records within each relation are processed in sorted order by ID.\n *\n * @param deleteOp - The delete operation being evaluated\n * @param schema - The full schema definition\n * @param ctx - Database lookup context\n * @param relationLookup - Pre-built relation lookup (optional; will be built if not provided)\n * @returns Whether the delete is allowed, any side-effect ops, and traces for DevTools\n */\nexport async function checkReferentialIntegrityOnDelete(\n\tdeleteOp: Operation,\n\tschema: SchemaDefinition,\n\tctx: ReferentialMergeContext,\n\trelationLookup?: Map<string, MergeIncomingRelation[]>,\n): Promise<ReferentialCheckResult> {\n\tconst lookup = relationLookup ?? buildMergeRelationLookup(schema)\n\tconst incomingRelations = lookup.get(deleteOp.collection)\n\n\t// No relations target this collection — delete is always allowed\n\tif (incomingRelations === undefined || incomingRelations.length === 0) {\n\t\treturn { allowed: true, sideEffectOps: [], traces: [] }\n\t}\n\n\tconst allSideEffects: SideEffectOp[] = []\n\tconst allTraces: MergeTrace[] = []\n\n\t// Process each incoming relation in deterministic order (already sorted by buildMergeRelationLookup)\n\tfor (const relation of incomingRelations) {\n\t\tconst startTime = Date.now()\n\n\t\t// Find all records in the source collection that reference the deleted record\n\t\tconst referencingRecords = await ctx.queryRecords(relation.sourceCollection, {\n\t\t\t[relation.foreignKeyField]: deleteOp.recordId,\n\t\t})\n\n\t\t// Sort referencing records by ID for deterministic processing\n\t\tconst sortedRecords = [...referencingRecords].sort((a, b) => {\n\t\t\tconst idA = String(a.id ?? '')\n\t\t\tconst idB = String(b.id ?? '')\n\t\t\treturn idA < idB ? -1 : idA > idB ? 1 : 0\n\t\t})\n\n\t\tif (sortedRecords.length === 0) {\n\t\t\t// No references — this relation does not block the delete.\n\t\t\t// Create a trace recording that we checked and found nothing.\n\t\t\tconst trace = createReferentialTrace(\n\t\t\t\tdeleteOp,\n\t\t\t\trelation,\n\t\t\t\t`referential-${relation.onDelete}`,\n\t\t\t\tnull,\n\t\t\t\tnull,\n\t\t\t\ttrue,\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t\tallTraces.push(trace)\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch (relation.onDelete) {\n\t\t\tcase 'restrict': {\n\t\t\t\t// Block the delete — references exist\n\t\t\t\tconst trace = createReferentialTrace(\n\t\t\t\t\tdeleteOp,\n\t\t\t\t\trelation,\n\t\t\t\t\t'referential-restrict',\n\t\t\t\t\tsortedRecords.map((r) => String(r.id ?? '')),\n\t\t\t\t\tnull,\n\t\t\t\t\tfalse,\n\t\t\t\t\tstartTime,\n\t\t\t\t)\n\t\t\t\tallTraces.push(trace)\n\n\t\t\t\t// Restrict means we immediately deny — no need to process further relations\n\t\t\t\treturn {\n\t\t\t\t\tallowed: false,\n\t\t\t\t\tsideEffectOps: [],\n\t\t\t\t\ttraces: allTraces,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcase 'cascade': {\n\t\t\t\t// Generate delete SideEffectOps for each referencing record\n\t\t\t\tfor (const record of sortedRecords) {\n\t\t\t\t\tconst recordId = String(record.id ?? '')\n\t\t\t\t\tallSideEffects.push({\n\t\t\t\t\t\ttype: 'delete',\n\t\t\t\t\t\tcollection: relation.sourceCollection,\n\t\t\t\t\t\trecordId,\n\t\t\t\t\t\tdata: null,\n\t\t\t\t\t\tpreviousData: null,\n\t\t\t\t\t\tpolicy: 'cascade',\n\t\t\t\t\t\trelationName: relation.relationName,\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tconst trace = createReferentialTrace(\n\t\t\t\t\tdeleteOp,\n\t\t\t\t\trelation,\n\t\t\t\t\t'referential-cascade',\n\t\t\t\t\tsortedRecords.map((r) => String(r.id ?? '')),\n\t\t\t\t\tsortedRecords.map((r) => ({ type: 'delete' as const, recordId: String(r.id ?? '') })),\n\t\t\t\t\ttrue,\n\t\t\t\t\tstartTime,\n\t\t\t\t)\n\t\t\t\tallTraces.push(trace)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'set-null': {\n\t\t\t\t// Generate update SideEffectOps setting the FK field to null\n\t\t\t\tfor (const record of sortedRecords) {\n\t\t\t\t\tconst recordId = String(record.id ?? '')\n\t\t\t\t\tconst previousValue = record[relation.foreignKeyField]\n\t\t\t\t\tallSideEffects.push({\n\t\t\t\t\t\ttype: 'update',\n\t\t\t\t\t\tcollection: relation.sourceCollection,\n\t\t\t\t\t\trecordId,\n\t\t\t\t\t\tdata: { [relation.foreignKeyField]: null },\n\t\t\t\t\t\tpreviousData: { [relation.foreignKeyField]: previousValue },\n\t\t\t\t\t\tpolicy: 'set-null',\n\t\t\t\t\t\trelationName: relation.relationName,\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tconst trace = createReferentialTrace(\n\t\t\t\t\tdeleteOp,\n\t\t\t\t\trelation,\n\t\t\t\t\t'referential-set-null',\n\t\t\t\t\tsortedRecords.map((r) => String(r.id ?? '')),\n\t\t\t\t\tsortedRecords.map((r) => ({\n\t\t\t\t\t\ttype: 'update' as const,\n\t\t\t\t\t\trecordId: String(r.id ?? ''),\n\t\t\t\t\t\tfield: relation.foreignKeyField,\n\t\t\t\t\t\tnewValue: null,\n\t\t\t\t\t})),\n\t\t\t\t\ttrue,\n\t\t\t\t\tstartTime,\n\t\t\t\t)\n\t\t\t\tallTraces.push(trace)\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcase 'no-action': {\n\t\t\t\t// Allow the delete, do nothing about dangling references\n\t\t\t\tconst trace = createReferentialTrace(\n\t\t\t\t\tdeleteOp,\n\t\t\t\t\trelation,\n\t\t\t\t\t'referential-no-action',\n\t\t\t\t\tsortedRecords.map((r) => String(r.id ?? '')),\n\t\t\t\t\tnull,\n\t\t\t\t\ttrue,\n\t\t\t\t\tstartTime,\n\t\t\t\t)\n\t\t\t\tallTraces.push(trace)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tallowed: true,\n\t\tsideEffectOps: allSideEffects,\n\t\ttraces: allTraces,\n\t}\n}\n\n/**\n * Resolves a concurrent delete-vs-insert conflict on a referenced record.\n *\n * When one node deletes a record while another concurrently inserts a record\n * that references the deleted record, we need to decide what wins.\n *\n * Resolution strategy depends on the relation's onDelete policy:\n * - `restrict`: Block the delete (the insert wins — data integrity preserved)\n * - `cascade`: Allow the delete and cascade-delete the newly inserted record\n * - `set-null`: Allow the delete and null out the FK on the newly inserted record\n * - `no-action`: Allow the delete (dangling reference is acceptable)\n *\n * @param deleteOp - The delete operation on the referenced record\n * @param insertOp - The concurrent insert operation that references the deleted record\n * @param relation - The relation definition connecting the two collections\n * @returns The resolution action, side effects, and trace\n */\nexport function resolveDeleteVsInsertConflict(\n\tdeleteOp: Operation,\n\tinsertOp: Operation,\n\trelation: MergeIncomingRelation,\n): DeleteVsInsertResolution {\n\tconst startTime = Date.now()\n\n\tswitch (relation.onDelete) {\n\t\tcase 'restrict': {\n\t\t\t// The insert wins — block the delete to preserve referential integrity\n\t\t\tconst trace = createDeleteVsInsertTrace(\n\t\t\t\tdeleteOp,\n\t\t\t\tinsertOp,\n\t\t\t\trelation,\n\t\t\t\t'referential-restrict',\n\t\t\t\t'block-delete',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t\treturn {\n\t\t\t\taction: 'block-delete',\n\t\t\t\tsideEffects: [],\n\t\t\t\ttrace,\n\t\t\t}\n\t\t}\n\n\t\tcase 'cascade': {\n\t\t\t// The delete wins — cascade-delete the inserted record\n\t\t\tconst sideEffect: SideEffectOp = {\n\t\t\t\ttype: 'delete',\n\t\t\t\tcollection: relation.sourceCollection,\n\t\t\t\trecordId: insertOp.recordId,\n\t\t\t\tdata: null,\n\t\t\t\tpreviousData: null,\n\t\t\t\tpolicy: 'cascade',\n\t\t\t\trelationName: relation.relationName,\n\t\t\t}\n\t\t\tconst trace = createDeleteVsInsertTrace(\n\t\t\t\tdeleteOp,\n\t\t\t\tinsertOp,\n\t\t\t\trelation,\n\t\t\t\t'referential-cascade',\n\t\t\t\t'allow-delete',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t\treturn {\n\t\t\t\taction: 'allow-delete',\n\t\t\t\tsideEffects: [sideEffect],\n\t\t\t\ttrace,\n\t\t\t}\n\t\t}\n\n\t\tcase 'set-null': {\n\t\t\t// The delete wins — null out the FK on the inserted record\n\t\t\tconst fkValue = insertOp.data !== null ? insertOp.data[relation.foreignKeyField] : undefined\n\t\t\tconst sideEffect: SideEffectOp = {\n\t\t\t\ttype: 'update',\n\t\t\t\tcollection: relation.sourceCollection,\n\t\t\t\trecordId: insertOp.recordId,\n\t\t\t\tdata: { [relation.foreignKeyField]: null },\n\t\t\t\tpreviousData: { [relation.foreignKeyField]: fkValue ?? null },\n\t\t\t\tpolicy: 'set-null',\n\t\t\t\trelationName: relation.relationName,\n\t\t\t}\n\t\t\tconst trace = createDeleteVsInsertTrace(\n\t\t\t\tdeleteOp,\n\t\t\t\tinsertOp,\n\t\t\t\trelation,\n\t\t\t\t'referential-set-null',\n\t\t\t\t'allow-delete',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t\treturn {\n\t\t\t\taction: 'allow-delete',\n\t\t\t\tsideEffects: [sideEffect],\n\t\t\t\ttrace,\n\t\t\t}\n\t\t}\n\n\t\tcase 'no-action': {\n\t\t\t// Allow the delete — dangling reference is acceptable\n\t\t\tconst trace = createDeleteVsInsertTrace(\n\t\t\t\tdeleteOp,\n\t\t\t\tinsertOp,\n\t\t\t\trelation,\n\t\t\t\t'referential-no-action',\n\t\t\t\t'allow-delete',\n\t\t\t\tstartTime,\n\t\t\t)\n\t\t\treturn {\n\t\t\t\taction: 'allow-delete',\n\t\t\t\tsideEffects: [],\n\t\t\t\ttrace,\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Creates a MergeTrace for a referential integrity decision during delete processing.\n */\nfunction createReferentialTrace(\n\tdeleteOp: Operation,\n\trelation: MergeIncomingRelation,\n\tstrategy: string,\n\treferencingRecordIds: string[] | null,\n\tsideEffects: unknown | null,\n\tallowed: boolean,\n\tstartTime: number,\n): MergeTrace {\n\treturn {\n\t\toperationA: deleteOp,\n\t\t// For referential integrity checks, operationB is the same as operationA\n\t\t// because we are checking constraints against the delete operation itself,\n\t\t// not merging two conflicting operations.\n\t\toperationB: deleteOp,\n\t\tfield: `${relation.sourceCollection}.${relation.foreignKeyField}`,\n\t\tstrategy,\n\t\tinputA: { recordId: deleteOp.recordId, collection: deleteOp.collection },\n\t\tinputB: referencingRecordIds,\n\t\tbase: null,\n\t\toutput: { allowed, sideEffects },\n\t\ttier: 2,\n\t\tconstraintViolated: `referential:${relation.relationName}`,\n\t\tduration: Date.now() - startTime,\n\t}\n}\n\n/**\n * Creates a MergeTrace for a delete-vs-insert conflict resolution.\n */\nfunction createDeleteVsInsertTrace(\n\tdeleteOp: Operation,\n\tinsertOp: Operation,\n\trelation: MergeIncomingRelation,\n\tstrategy: string,\n\taction: string,\n\tstartTime: number,\n): MergeTrace {\n\treturn {\n\t\toperationA: deleteOp,\n\t\toperationB: insertOp,\n\t\tfield: `${relation.sourceCollection}.${relation.foreignKeyField}`,\n\t\tstrategy,\n\t\tinputA: { type: 'delete', recordId: deleteOp.recordId, collection: deleteOp.collection },\n\t\tinputB: { type: 'insert', recordId: insertOp.recordId, collection: insertOp.collection },\n\t\tbase: null,\n\t\toutput: { action },\n\t\ttier: 2,\n\t\tconstraintViolated: `referential:${relation.relationName}`,\n\t\tduration: Date.now() - startTime,\n\t}\n}\n","import { HybridLogicalClock } from '@korajs/core'\nimport type { Operation } from '@korajs/core'\nimport type { MergeTrace } from '@korajs/core'\nimport { checkConstraints } from '../constraints/constraint-checker'\nimport { resolveConstraintViolation } from '../constraints/resolvers'\nimport {\n\tisStateMachineField,\n\tresolveStateMachineMerge,\n} from '../constraints/state-machine-constraint'\nimport type { ConstraintContext, MergeInput, MergeResult } from '../types'\nimport { mergeField } from './field-merger'\n\n/**\n * Three-tier merge engine for resolving concurrent operations.\n *\n * Tier 1: Auto-merge per field kind (LWW, add-wins set, CRDT)\n * Tier 3: Custom resolvers override Tier 1 for specific fields\n * Tier 2: Constraint validation against the candidate merged state\n *\n * Tier 3 runs BEFORE Tier 2 so that constraints validate the final merged state\n * including any custom resolver outputs.\n *\n * @example\n * ```typescript\n * const engine = new MergeEngine()\n * const result = await engine.merge({\n * local: localOp,\n * remote: remoteOp,\n * baseState: { title: 'old', completed: false },\n * collectionDef: schema.collections.todos,\n * })\n * ```\n */\nexport class MergeEngine {\n\t/**\n\t * Merge two concurrent operations with all three tiers.\n\t *\n\t * Flow:\n\t * 1. Determine which fields conflict (both ops modified the same field)\n\t * 2. For non-conflicting fields: take the changed value from whichever op modified it\n\t * 3. For conflicting fields: Tier 3 custom resolver if exists, else Tier 1 auto-merge\n\t * 4. Assemble candidate merged record\n\t * 5. If constraintContext provided: run Tier 2 constraint checks and resolve violations\n\t * 6. Return MergeResult with all traces\n\t *\n\t * @param input - The two operations, base state, and collection definition\n\t * @param constraintContext - Optional DB lookup interface for Tier 2 constraints\n\t * @returns The merged data and traces for DevTools\n\t */\n\tasync merge(input: MergeInput, constraintContext?: ConstraintContext): Promise<MergeResult> {\n\t\t// Handle delete vs delete: both agree, no merge needed\n\t\tif (input.local.type === 'delete' && input.remote.type === 'delete') {\n\t\t\treturn {\n\t\t\t\tmergedData: {},\n\t\t\t\ttraces: [],\n\t\t\t\tappliedOperation: 'merged',\n\t\t\t}\n\t\t}\n\n\t\t// Handle update vs delete (or delete vs update)\n\t\tif (input.local.type === 'delete' || input.remote.type === 'delete') {\n\t\t\treturn this.mergeWithDelete(input)\n\t\t}\n\n\t\t// Insert vs insert or update vs update: field-level merge\n\t\tconst fieldResult = this.mergeFields(input)\n\n\t\t// Tier 2: Constraint checking (requires async DB lookups)\n\t\tif (constraintContext !== undefined && input.collectionDef.constraints.length > 0) {\n\t\t\tconst recordWithId = { id: input.local.recordId, ...fieldResult.mergedData }\n\t\t\tconst violations = await checkConstraints(\n\t\t\t\trecordWithId,\n\t\t\t\tinput.local.recordId,\n\t\t\t\tinput.local.collection,\n\t\t\t\tinput.collectionDef,\n\t\t\t\tconstraintContext,\n\t\t\t)\n\n\t\t\tlet mergedData = fieldResult.mergedData\n\t\t\tconst allTraces = [...fieldResult.traces]\n\n\t\t\tfor (const violation of violations) {\n\t\t\t\tconst resolution = resolveConstraintViolation(\n\t\t\t\t\tviolation,\n\t\t\t\t\tmergedData,\n\t\t\t\t\tinput.local,\n\t\t\t\t\tinput.remote,\n\t\t\t\t\tinput.baseState,\n\t\t\t\t)\n\t\t\t\tmergedData = resolution.resolvedRecord\n\t\t\t\tallTraces.push(resolution.trace)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tmergedData,\n\t\t\t\ttraces: allTraces,\n\t\t\t\tappliedOperation: determineAppliedOperation(allTraces),\n\t\t\t}\n\t\t}\n\n\t\treturn fieldResult\n\t}\n\n\t/**\n\t * Synchronous field-level merge (Tier 1 + Tier 3 only).\n\t *\n\t * Useful when constraint context is unavailable or not needed.\n\t * Skips Tier 2 constraint checking entirely.\n\t *\n\t * @param input - The two operations, base state, and collection definition\n\t * @returns The merged data and traces for DevTools\n\t */\n\tmergeFields(input: MergeInput): MergeResult {\n\t\tconst { local, remote, baseState, collectionDef } = input\n\n\t\t// Collect all field names that either operation touches\n\t\tconst allFields = collectAffectedFields(local, remote, baseState, collectionDef)\n\n\t\tconst mergedData: Record<string, unknown> = {}\n\t\tconst traces: MergeTrace[] = []\n\n\t\tfor (const fieldName of allFields) {\n\t\t\tconst fieldDef = collectionDef.fields[fieldName]\n\t\t\tif (fieldDef === undefined) {\n\t\t\t\t// Field not in schema -- skip (could be a removed field from migration)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// State machine fields use dedicated merge logic that validates\n\t\t\t// transitions from the base state rather than plain LWW.\n\t\t\tif (\n\t\t\t\tcollectionDef.stateMachine !== undefined &&\n\t\t\t\tisStateMachineField(collectionDef, fieldName)\n\t\t\t) {\n\t\t\t\tconst smResult = resolveStateMachineMerge(\n\t\t\t\t\tfieldName,\n\t\t\t\t\tlocal,\n\t\t\t\t\tremote,\n\t\t\t\t\tbaseState,\n\t\t\t\t\tcollectionDef.stateMachine,\n\t\t\t\t)\n\t\t\t\tmergedData[fieldName] = smResult.value\n\n\t\t\t\t// Include trace unless it was a no-conflict-unchanged case\n\t\t\t\tif (smResult.trace.strategy !== 'state-machine-no-conflict-unchanged') {\n\t\t\t\t\ttraces.push(smResult.trace)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst resolver = collectionDef.resolvers[fieldName]\n\t\t\tconst result = mergeField(fieldName, local, remote, baseState, fieldDef, resolver)\n\n\t\t\tmergedData[fieldName] = result.value\n\n\t\t\t// Only include traces for actual conflicts (not no-conflict cases)\n\t\t\tif (\n\t\t\t\tresult.trace.strategy !== 'no-conflict-local' &&\n\t\t\t\tresult.trace.strategy !== 'no-conflict-remote' &&\n\t\t\t\tresult.trace.strategy !== 'no-conflict-unchanged'\n\t\t\t) {\n\t\t\t\ttraces.push(result.trace)\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tmergedData,\n\t\t\ttraces,\n\t\t\tappliedOperation: determineAppliedOperation(traces),\n\t\t}\n\t}\n\n\t/**\n\t * Handle merge when one operation is a delete.\n\t * Default: delete wins (LWW on the record level).\n\t */\n\tprivate mergeWithDelete(input: MergeInput): MergeResult {\n\t\tconst { local, remote } = input\n\n\t\t// LWW at the record level: later operation wins\n\t\tconst comparison = HybridLogicalClock.compare(local.timestamp, remote.timestamp)\n\n\t\tif (comparison >= 0) {\n\t\t\t// Local is later\n\t\t\tif (local.type === 'delete') {\n\t\t\t\treturn { mergedData: {}, traces: [], appliedOperation: 'local' }\n\t\t\t}\n\t\t\t// Local is an update that's later than remote delete → local wins\n\t\t\treturn {\n\t\t\t\tmergedData: { ...input.baseState, ...(local.data ?? {}) },\n\t\t\t\ttraces: [],\n\t\t\t\tappliedOperation: 'local',\n\t\t\t}\n\t\t}\n\n\t\t// Remote is later\n\t\tif (remote.type === 'delete') {\n\t\t\treturn { mergedData: {}, traces: [], appliedOperation: 'remote' }\n\t\t}\n\t\t// Remote is an update that's later than local delete → remote wins\n\t\treturn {\n\t\t\tmergedData: { ...input.baseState, ...(remote.data ?? {}) },\n\t\t\ttraces: [],\n\t\t\tappliedOperation: 'remote',\n\t\t}\n\t}\n}\n\n/**\n * Collect all field names affected by either operation or present in the base state.\n */\nfunction collectAffectedFields(\n\tlocal: Operation,\n\tremote: Operation,\n\tbaseState: Record<string, unknown>,\n\tcollectionDef: { fields: Record<string, unknown> },\n): Set<string> {\n\tconst fields = new Set<string>()\n\n\t// Fields from the schema definition\n\tfor (const fieldName of Object.keys(collectionDef.fields)) {\n\t\tfields.add(fieldName)\n\t}\n\n\t// Fields from local operation\n\tif (local.data !== null) {\n\t\tfor (const fieldName of Object.keys(local.data)) {\n\t\t\tfields.add(fieldName)\n\t\t}\n\t}\n\n\t// Fields from remote operation\n\tif (remote.data !== null) {\n\t\tfor (const fieldName of Object.keys(remote.data)) {\n\t\t\tfields.add(fieldName)\n\t\t}\n\t}\n\n\t// Fields from base state\n\tfor (const fieldName of Object.keys(baseState)) {\n\t\tfields.add(fieldName)\n\t}\n\n\treturn fields\n}\n\n/**\n * Determine which operation's values dominate overall.\n * If all conflict traces went the same way, report that side.\n * Otherwise, report 'merged'.\n */\nfunction determineAppliedOperation(traces: MergeTrace[]): 'local' | 'remote' | 'merged' {\n\tif (traces.length === 0) {\n\t\treturn 'merged'\n\t}\n\n\tlet allLocal = true\n\tlet allRemote = true\n\n\tfor (const trace of traces) {\n\t\tif (trace.strategy === 'lww' || trace.strategy === 'constraint-lww') {\n\t\t\t// Check if local or remote value was the output\n\t\t\tif (trace.output === trace.inputA) {\n\t\t\t\tallRemote = false\n\t\t\t} else if (trace.output === trace.inputB) {\n\t\t\t\tallLocal = false\n\t\t\t} else {\n\t\t\t\tallLocal = false\n\t\t\t\tallRemote = false\n\t\t\t}\n\t\t} else {\n\t\t\t// For non-LWW strategies (add-wins-set, custom, etc.), it's a merge\n\t\t\tallLocal = false\n\t\t\tallRemote = false\n\t\t}\n\t}\n\n\tif (allLocal) return 'local'\n\tif (allRemote) return 'remote'\n\treturn 'merged'\n}\n","import { HybridLogicalClock, validateTransition } from '@korajs/core'\nimport type {\n\tCollectionDefinition,\n\tOperation,\n\tStateMachineConstraint,\n\tStateMachineDefinition,\n} from '@korajs/core'\nimport type { MergeTrace } from '@korajs/core'\n\n/**\n * Helper: check if a transition is valid for a given state machine definition.\n * Wraps validateTransition to return a simple boolean.\n */\nfunction isValid(sm: StateMachineDefinition, from: string, to: string): boolean {\n\tconst constraint: StateMachineConstraint = {\n\t\tfield: sm.field,\n\t\tcollection: '',\n\t\ttransitions: sm.transitions,\n\t}\n\treturn validateTransition(constraint, from, to).valid\n}\n\n/**\n * Result of state machine merge validation.\n */\nexport interface StateMachineMergeResult {\n\t/** The resolved value for the state field */\n\tvalue: string\n\t/** Trace for DevTools */\n\ttrace: MergeTrace\n}\n\n/**\n * Validates and resolves concurrent state machine transitions during merge.\n *\n * When two operations concurrently modify a state-machine-controlled field,\n * this function determines the correct resolved value:\n *\n * 1. Both transitions valid from base state: use LWW (HLC timestamp) to pick the winner\n * 2. One transition valid, one invalid: the valid transition wins\n * 3. Both transitions invalid: keep the base state and report the constraint violation\n *\n * @param fieldName - The state machine field name\n * @param localOp - The local operation\n * @param remoteOp - The remote operation\n * @param baseState - The record state before either operation (contains the base state value)\n * @param stateMachine - The state machine definition\n * @returns The resolved state value and a trace\n */\nexport function resolveStateMachineMerge(\n\tfieldName: string,\n\tlocalOp: Operation,\n\tremoteOp: Operation,\n\tbaseState: Record<string, unknown>,\n\tstateMachine: StateMachineDefinition,\n): StateMachineMergeResult {\n\tconst startTime = Date.now()\n\tconst baseValue = baseState[fieldName]\n\n\tconst localData = localOp.data ?? {}\n\tconst remoteData = remoteOp.data ?? {}\n\n\tconst localValue = localData[fieldName]\n\tconst remoteValue = remoteData[fieldName]\n\n\t// Determine base state string\n\tconst baseStateStr = typeof baseValue === 'string' ? baseValue : ''\n\tconst localStr = typeof localValue === 'string' ? localValue : baseStateStr\n\tconst remoteStr = typeof remoteValue === 'string' ? remoteValue : baseStateStr\n\n\tconst localChanged = fieldName in localData\n\tconst remoteChanged = fieldName in remoteData\n\n\t// Non-conflict: only one side changed\n\tif (localChanged && !remoteChanged) {\n\t\tconst valid = isValid(stateMachine, baseStateStr, localStr)\n\t\treturn makeResult(\n\t\t\tvalid ? localStr : baseStateStr,\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tlocalValue,\n\t\t\tbaseValue,\n\t\t\tbaseValue,\n\t\t\tvalid ? 'state-machine-no-conflict-local' : 'state-machine-invalid-local',\n\t\t\tvalid ? null : `Invalid transition from \"${baseStateStr}\" to \"${localStr}\"`,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\tif (!localChanged && remoteChanged) {\n\t\tconst valid = isValid(stateMachine, baseStateStr, remoteStr)\n\t\treturn makeResult(\n\t\t\tvalid ? remoteStr : baseStateStr,\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tbaseValue,\n\t\t\tremoteValue,\n\t\t\tbaseValue,\n\t\t\tvalid ? 'state-machine-no-conflict-remote' : 'state-machine-invalid-remote',\n\t\t\tvalid ? null : `Invalid transition from \"${baseStateStr}\" to \"${remoteStr}\"`,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\tif (!localChanged && !remoteChanged) {\n\t\treturn makeResult(\n\t\t\tbaseStateStr,\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tbaseValue,\n\t\t\tbaseValue,\n\t\t\tbaseValue,\n\t\t\t'state-machine-no-conflict-unchanged',\n\t\t\tnull,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\t// Both sides changed -- validate both transitions from base state\n\tconst localValid = isValid(stateMachine, baseStateStr, localStr)\n\tconst remoteValid = isValid(stateMachine, baseStateStr, remoteStr)\n\n\tif (localValid && remoteValid) {\n\t\t// Both valid: LWW by HLC timestamp decides the winner\n\t\tconst comparison = HybridLogicalClock.compare(localOp.timestamp, remoteOp.timestamp)\n\t\tconst winner = comparison >= 0 ? localStr : remoteStr\n\t\treturn makeResult(\n\t\t\twinner,\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tlocalValue,\n\t\t\tremoteValue,\n\t\t\tbaseValue,\n\t\t\t'state-machine-lww',\n\t\t\tnull,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\tif (localValid && !remoteValid) {\n\t\t// Only local is valid: local wins regardless of timestamp\n\t\treturn makeResult(\n\t\t\tlocalStr,\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tlocalValue,\n\t\t\tremoteValue,\n\t\t\tbaseValue,\n\t\t\t'state-machine-valid-wins',\n\t\t\t`Remote transition from \"${baseStateStr}\" to \"${remoteStr}\" is invalid; local \"${localStr}\" wins`,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\tif (!localValid && remoteValid) {\n\t\t// Only remote is valid: remote wins regardless of timestamp\n\t\treturn makeResult(\n\t\t\tremoteStr,\n\t\t\tfieldName,\n\t\t\tlocalOp,\n\t\t\tremoteOp,\n\t\t\tlocalValue,\n\t\t\tremoteValue,\n\t\t\tbaseValue,\n\t\t\t'state-machine-valid-wins',\n\t\t\t`Local transition from \"${baseStateStr}\" to \"${localStr}\" is invalid; remote \"${remoteStr}\" wins`,\n\t\t\tstartTime,\n\t\t)\n\t}\n\n\t// Both invalid: keep base state\n\treturn makeResult(\n\t\tbaseStateStr,\n\t\tfieldName,\n\t\tlocalOp,\n\t\tremoteOp,\n\t\tlocalValue,\n\t\tremoteValue,\n\t\tbaseValue,\n\t\t'state-machine-both-invalid',\n\t\t`Both transitions invalid from \"${baseStateStr}\": local to \"${localStr}\", remote to \"${remoteStr}\". Keeping base state.`,\n\t\tstartTime,\n\t)\n}\n\n/**\n * Checks whether a collection has a state machine defined and whether the given\n * field is the state machine field. Used by the merge engine to intercept\n * field-level merges for state machine fields.\n */\nexport function isStateMachineField(\n\tcollectionDef: CollectionDefinition,\n\tfieldName: string,\n): boolean {\n\treturn collectionDef.stateMachine !== undefined && collectionDef.stateMachine.field === fieldName\n}\n\nfunction makeResult(\n\tvalue: string,\n\tfield: string,\n\toperationA: Operation,\n\toperationB: Operation,\n\tinputA: unknown,\n\tinputB: unknown,\n\tbase: unknown,\n\tstrategy: string,\n\tconstraintViolated: string | null,\n\tstartTime: number,\n): StateMachineMergeResult {\n\tconst trace: MergeTrace = {\n\t\toperationA,\n\t\toperationB,\n\t\tfield,\n\t\tstrategy,\n\t\tinputA,\n\t\tinputB,\n\t\tbase,\n\t\toutput: value,\n\t\ttier: 2,\n\t\tconstraintViolated,\n\t\tduration: Date.now() - startTime,\n\t}\n\treturn { value, trace }\n}\n"],"mappings":"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","names":["HybridLogicalClock","winner","resolvedRecord","HybridLogicalClock","HybridLogicalClock","HybridLogicalClock"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@korajs/merge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Three-tier conflict resolution engine for Kora.js (LWW, constraints, custom resolvers)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"yjs": "^13.6.30",
|
|
26
|
-
"@korajs/core": "0.
|
|
26
|
+
"@korajs/core": "0.4.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@fast-check/vitest": "0.2.0",
|