@korajs/merge 0.4.0 → 0.5.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.d.cts CHANGED
@@ -1,4 +1,126 @@
1
- import { Constraint, MergeTrace, Operation, CollectionDefinition, HLCTimestamp, AtomicOp, FieldMergeStrategy, FieldDescriptor, CustomResolver, OnDeleteAction, SchemaDefinition } from '@korajs/core';
1
+ import { OnDeleteAction, MergeTrace, SchemaDefinition, Operation, Constraint, CollectionDefinition, HLCTimestamp, AtomicOp, FieldMergeStrategy, FieldDescriptor, CustomResolver } from '@korajs/core';
2
+
3
+ /**
4
+ * Describes an incoming relation to a target collection.
5
+ * Used by the delete-side referential integrity checker to know which
6
+ * source collections reference the collection being deleted from.
7
+ */
8
+ interface MergeIncomingRelation {
9
+ /** Name of the relation in the schema (e.g., 'todoBelongsToProject') */
10
+ relationName: string;
11
+ /** The collection that holds the foreign key (source of the relation) */
12
+ sourceCollection: string;
13
+ /** The foreign key field in the source collection */
14
+ foreignKeyField: string;
15
+ /** What to do when the referenced record is deleted */
16
+ onDelete: OnDeleteAction;
17
+ }
18
+ /**
19
+ * Pluggable context for querying the database during referential integrity checks.
20
+ * The store layer provides the implementation; the merge package only depends on this interface.
21
+ */
22
+ interface ReferentialMergeContext {
23
+ /** Query records in a collection matching a where clause */
24
+ queryRecords(collection: string, where: Record<string, unknown>): Promise<Record<string, unknown>[]>;
25
+ /** Check if a record exists in a collection */
26
+ recordExists(collection: string, recordId: string): Promise<boolean>;
27
+ }
28
+ /**
29
+ * Result of a referential integrity check on a delete operation.
30
+ */
31
+ interface ReferentialCheckResult {
32
+ /** Whether the delete operation is allowed to proceed */
33
+ allowed: boolean;
34
+ /** Side-effect operations generated by cascade/set-null policies */
35
+ sideEffectOps: SideEffectOp[];
36
+ /** MergeTraces for every decision made (feeds DevTools) */
37
+ traces: MergeTrace[];
38
+ }
39
+ /**
40
+ * A side-effect operation generated during referential integrity enforcement.
41
+ * These are not full Operations; they describe mutations the caller must apply.
42
+ */
43
+ interface SideEffectOp {
44
+ /** The type of mutation to apply */
45
+ type: 'delete' | 'update';
46
+ /** The collection containing the affected record */
47
+ collection: string;
48
+ /** The ID of the affected record */
49
+ recordId: string;
50
+ /** For updates: the new field values. null for deletes. */
51
+ data: Record<string, unknown> | null;
52
+ /** For updates: the previous field values. null for deletes. */
53
+ previousData: Record<string, unknown> | null;
54
+ /** The onDelete policy that produced this side effect */
55
+ policy: OnDeleteAction;
56
+ /** The relation that triggered this side effect */
57
+ relationName: string;
58
+ }
59
+ /**
60
+ * Result of resolving a concurrent delete-vs-insert conflict.
61
+ */
62
+ interface DeleteVsInsertResolution {
63
+ /** Whether to block the delete or allow it */
64
+ action: 'block-delete' | 'allow-delete';
65
+ /** Any side-effect operations (for cascade/set-null after allowing delete) */
66
+ sideEffects: SideEffectOp[];
67
+ /** Trace of the resolution decision, or null if no trace needed */
68
+ trace: MergeTrace | null;
69
+ }
70
+ /**
71
+ * Builds a lookup map from target collection to all incoming relations.
72
+ *
73
+ * This pre-computes which collections reference a given collection via foreign keys,
74
+ * so that delete operations can efficiently find all relations that need enforcement.
75
+ *
76
+ * @param schema - The full schema definition with relations
77
+ * @returns Map from target collection name to array of incoming relations, sorted by relation name for determinism
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const lookup = buildMergeRelationLookup(schema)
82
+ * // lookup.get('projects') => [{ relationName: 'todoBelongsToProject', sourceCollection: 'todos', ... }]
83
+ * ```
84
+ */
85
+ declare function buildMergeRelationLookup(schema: SchemaDefinition): Map<string, MergeIncomingRelation[]>;
86
+ /**
87
+ * Checks referential integrity when a delete operation is being applied.
88
+ *
89
+ * For each relation that targets the deleted record's collection, queries
90
+ * for referencing records and applies the relation's onDelete policy:
91
+ * - `restrict`: Blocks the delete if any references exist
92
+ * - `cascade`: Allows the delete and generates cascaded delete SideEffectOps
93
+ * - `set-null`: Allows the delete and generates update SideEffectOps to null out FKs
94
+ * - `no-action`: Allows the delete with no side effects (dangling references permitted)
95
+ *
96
+ * Processing is deterministic: relations are processed in sorted order by name,
97
+ * and referencing records within each relation are processed in sorted order by ID.
98
+ *
99
+ * @param deleteOp - The delete operation being evaluated
100
+ * @param schema - The full schema definition
101
+ * @param ctx - Database lookup context
102
+ * @param relationLookup - Pre-built relation lookup (optional; will be built if not provided)
103
+ * @returns Whether the delete is allowed, any side-effect ops, and traces for DevTools
104
+ */
105
+ declare function checkReferentialIntegrityOnDelete(deleteOp: Operation, schema: SchemaDefinition, ctx: ReferentialMergeContext, relationLookup?: Map<string, MergeIncomingRelation[]>): Promise<ReferentialCheckResult>;
106
+ /**
107
+ * Resolves a concurrent delete-vs-insert conflict on a referenced record.
108
+ *
109
+ * When one node deletes a record while another concurrently inserts a record
110
+ * that references the deleted record, we need to decide what wins.
111
+ *
112
+ * Resolution strategy depends on the relation's onDelete policy:
113
+ * - `restrict`: Block the delete (the insert wins — data integrity preserved)
114
+ * - `cascade`: Allow the delete and cascade-delete the newly inserted record
115
+ * - `set-null`: Allow the delete and null out the FK on the newly inserted record
116
+ * - `no-action`: Allow the delete (dangling reference is acceptable)
117
+ *
118
+ * @param deleteOp - The delete operation on the referenced record
119
+ * @param insertOp - The concurrent insert operation that references the deleted record
120
+ * @param relation - The relation definition connecting the two collections
121
+ * @returns The resolution action, side effects, and trace
122
+ */
123
+ declare function resolveDeleteVsInsertConflict(deleteOp: Operation, insertOp: Operation, relation: MergeIncomingRelation): DeleteVsInsertResolution;
2
124
 
3
125
  /**
4
126
  * Input to the merge engine when two concurrent operations conflict.
@@ -23,6 +145,12 @@ interface MergeResult {
23
145
  traces: MergeTrace[];
24
146
  /** Which operation's values dominate overall, or 'merged' if mixed */
25
147
  appliedOperation: 'local' | 'remote' | 'merged';
148
+ /**
149
+ * Side-effect operations generated during constraint resolution
150
+ * (e.g., cascade deletes, set-null updates from referential integrity).
151
+ * These must be applied by the caller after the merge.
152
+ */
153
+ sideEffects: SideEffectOp[];
26
154
  }
27
155
  /**
28
156
  * Output of a single field-level merge decision.
@@ -249,6 +377,12 @@ interface ConstraintResolution {
249
377
  resolvedRecord: Record<string, unknown>;
250
378
  /** Trace of the resolution decision for DevTools */
251
379
  trace: MergeTrace;
380
+ /**
381
+ * Side-effect operations generated during resolution
382
+ * (e.g., referring record updates for referential integrity).
383
+ * Empty unless the constraint resolver generates cascading actions.
384
+ */
385
+ sideEffects?: SideEffectOp[];
252
386
  }
253
387
  /**
254
388
  * Resolves a constraint violation by applying the constraint's onConflict strategy.
@@ -269,128 +403,6 @@ interface ConstraintResolution {
269
403
  */
270
404
  declare function resolveConstraintViolation(violation: ConstraintViolation, mergedRecord: Record<string, unknown>, localOp: Operation, remoteOp: Operation, baseState: Record<string, unknown>): ConstraintResolution;
271
405
 
272
- /**
273
- * Describes an incoming relation to a target collection.
274
- * Used by the delete-side referential integrity checker to know which
275
- * source collections reference the collection being deleted from.
276
- */
277
- interface MergeIncomingRelation {
278
- /** Name of the relation in the schema (e.g., 'todoBelongsToProject') */
279
- relationName: string;
280
- /** The collection that holds the foreign key (source of the relation) */
281
- sourceCollection: string;
282
- /** The foreign key field in the source collection */
283
- foreignKeyField: string;
284
- /** What to do when the referenced record is deleted */
285
- onDelete: OnDeleteAction;
286
- }
287
- /**
288
- * Pluggable context for querying the database during referential integrity checks.
289
- * The store layer provides the implementation; the merge package only depends on this interface.
290
- */
291
- interface ReferentialMergeContext {
292
- /** Query records in a collection matching a where clause */
293
- queryRecords(collection: string, where: Record<string, unknown>): Promise<Record<string, unknown>[]>;
294
- /** Check if a record exists in a collection */
295
- recordExists(collection: string, recordId: string): Promise<boolean>;
296
- }
297
- /**
298
- * Result of a referential integrity check on a delete operation.
299
- */
300
- interface ReferentialCheckResult {
301
- /** Whether the delete operation is allowed to proceed */
302
- allowed: boolean;
303
- /** Side-effect operations generated by cascade/set-null policies */
304
- sideEffectOps: SideEffectOp[];
305
- /** MergeTraces for every decision made (feeds DevTools) */
306
- traces: MergeTrace[];
307
- }
308
- /**
309
- * A side-effect operation generated during referential integrity enforcement.
310
- * These are not full Operations; they describe mutations the caller must apply.
311
- */
312
- interface SideEffectOp {
313
- /** The type of mutation to apply */
314
- type: 'delete' | 'update';
315
- /** The collection containing the affected record */
316
- collection: string;
317
- /** The ID of the affected record */
318
- recordId: string;
319
- /** For updates: the new field values. null for deletes. */
320
- data: Record<string, unknown> | null;
321
- /** For updates: the previous field values. null for deletes. */
322
- previousData: Record<string, unknown> | null;
323
- /** The onDelete policy that produced this side effect */
324
- policy: OnDeleteAction;
325
- /** The relation that triggered this side effect */
326
- relationName: string;
327
- }
328
- /**
329
- * Result of resolving a concurrent delete-vs-insert conflict.
330
- */
331
- interface DeleteVsInsertResolution {
332
- /** Whether to block the delete or allow it */
333
- action: 'block-delete' | 'allow-delete';
334
- /** Any side-effect operations (for cascade/set-null after allowing delete) */
335
- sideEffects: SideEffectOp[];
336
- /** Trace of the resolution decision, or null if no trace needed */
337
- trace: MergeTrace | null;
338
- }
339
- /**
340
- * Builds a lookup map from target collection to all incoming relations.
341
- *
342
- * This pre-computes which collections reference a given collection via foreign keys,
343
- * so that delete operations can efficiently find all relations that need enforcement.
344
- *
345
- * @param schema - The full schema definition with relations
346
- * @returns Map from target collection name to array of incoming relations, sorted by relation name for determinism
347
- *
348
- * @example
349
- * ```typescript
350
- * const lookup = buildMergeRelationLookup(schema)
351
- * // lookup.get('projects') => [{ relationName: 'todoBelongsToProject', sourceCollection: 'todos', ... }]
352
- * ```
353
- */
354
- declare function buildMergeRelationLookup(schema: SchemaDefinition): Map<string, MergeIncomingRelation[]>;
355
- /**
356
- * Checks referential integrity when a delete operation is being applied.
357
- *
358
- * For each relation that targets the deleted record's collection, queries
359
- * for referencing records and applies the relation's onDelete policy:
360
- * - `restrict`: Blocks the delete if any references exist
361
- * - `cascade`: Allows the delete and generates cascaded delete SideEffectOps
362
- * - `set-null`: Allows the delete and generates update SideEffectOps to null out FKs
363
- * - `no-action`: Allows the delete with no side effects (dangling references permitted)
364
- *
365
- * Processing is deterministic: relations are processed in sorted order by name,
366
- * and referencing records within each relation are processed in sorted order by ID.
367
- *
368
- * @param deleteOp - The delete operation being evaluated
369
- * @param schema - The full schema definition
370
- * @param ctx - Database lookup context
371
- * @param relationLookup - Pre-built relation lookup (optional; will be built if not provided)
372
- * @returns Whether the delete is allowed, any side-effect ops, and traces for DevTools
373
- */
374
- declare function checkReferentialIntegrityOnDelete(deleteOp: Operation, schema: SchemaDefinition, ctx: ReferentialMergeContext, relationLookup?: Map<string, MergeIncomingRelation[]>): Promise<ReferentialCheckResult>;
375
- /**
376
- * Resolves a concurrent delete-vs-insert conflict on a referenced record.
377
- *
378
- * When one node deletes a record while another concurrently inserts a record
379
- * that references the deleted record, we need to decide what wins.
380
- *
381
- * Resolution strategy depends on the relation's onDelete policy:
382
- * - `restrict`: Block the delete (the insert wins — data integrity preserved)
383
- * - `cascade`: Allow the delete and cascade-delete the newly inserted record
384
- * - `set-null`: Allow the delete and null out the FK on the newly inserted record
385
- * - `no-action`: Allow the delete (dangling reference is acceptable)
386
- *
387
- * @param deleteOp - The delete operation on the referenced record
388
- * @param insertOp - The concurrent insert operation that references the deleted record
389
- * @param relation - The relation definition connecting the two collections
390
- * @returns The resolution action, side effects, and trace
391
- */
392
- declare function resolveDeleteVsInsertConflict(deleteOp: Operation, insertOp: Operation, relation: MergeIncomingRelation): DeleteVsInsertResolution;
393
-
394
406
  /**
395
407
  * Three-tier merge engine for resolving concurrent operations.
396
408
  *
package/dist/index.d.ts CHANGED
@@ -1,4 +1,126 @@
1
- import { Constraint, MergeTrace, Operation, CollectionDefinition, HLCTimestamp, AtomicOp, FieldMergeStrategy, FieldDescriptor, CustomResolver, OnDeleteAction, SchemaDefinition } from '@korajs/core';
1
+ import { OnDeleteAction, MergeTrace, SchemaDefinition, Operation, Constraint, CollectionDefinition, HLCTimestamp, AtomicOp, FieldMergeStrategy, FieldDescriptor, CustomResolver } from '@korajs/core';
2
+
3
+ /**
4
+ * Describes an incoming relation to a target collection.
5
+ * Used by the delete-side referential integrity checker to know which
6
+ * source collections reference the collection being deleted from.
7
+ */
8
+ interface MergeIncomingRelation {
9
+ /** Name of the relation in the schema (e.g., 'todoBelongsToProject') */
10
+ relationName: string;
11
+ /** The collection that holds the foreign key (source of the relation) */
12
+ sourceCollection: string;
13
+ /** The foreign key field in the source collection */
14
+ foreignKeyField: string;
15
+ /** What to do when the referenced record is deleted */
16
+ onDelete: OnDeleteAction;
17
+ }
18
+ /**
19
+ * Pluggable context for querying the database during referential integrity checks.
20
+ * The store layer provides the implementation; the merge package only depends on this interface.
21
+ */
22
+ interface ReferentialMergeContext {
23
+ /** Query records in a collection matching a where clause */
24
+ queryRecords(collection: string, where: Record<string, unknown>): Promise<Record<string, unknown>[]>;
25
+ /** Check if a record exists in a collection */
26
+ recordExists(collection: string, recordId: string): Promise<boolean>;
27
+ }
28
+ /**
29
+ * Result of a referential integrity check on a delete operation.
30
+ */
31
+ interface ReferentialCheckResult {
32
+ /** Whether the delete operation is allowed to proceed */
33
+ allowed: boolean;
34
+ /** Side-effect operations generated by cascade/set-null policies */
35
+ sideEffectOps: SideEffectOp[];
36
+ /** MergeTraces for every decision made (feeds DevTools) */
37
+ traces: MergeTrace[];
38
+ }
39
+ /**
40
+ * A side-effect operation generated during referential integrity enforcement.
41
+ * These are not full Operations; they describe mutations the caller must apply.
42
+ */
43
+ interface SideEffectOp {
44
+ /** The type of mutation to apply */
45
+ type: 'delete' | 'update';
46
+ /** The collection containing the affected record */
47
+ collection: string;
48
+ /** The ID of the affected record */
49
+ recordId: string;
50
+ /** For updates: the new field values. null for deletes. */
51
+ data: Record<string, unknown> | null;
52
+ /** For updates: the previous field values. null for deletes. */
53
+ previousData: Record<string, unknown> | null;
54
+ /** The onDelete policy that produced this side effect */
55
+ policy: OnDeleteAction;
56
+ /** The relation that triggered this side effect */
57
+ relationName: string;
58
+ }
59
+ /**
60
+ * Result of resolving a concurrent delete-vs-insert conflict.
61
+ */
62
+ interface DeleteVsInsertResolution {
63
+ /** Whether to block the delete or allow it */
64
+ action: 'block-delete' | 'allow-delete';
65
+ /** Any side-effect operations (for cascade/set-null after allowing delete) */
66
+ sideEffects: SideEffectOp[];
67
+ /** Trace of the resolution decision, or null if no trace needed */
68
+ trace: MergeTrace | null;
69
+ }
70
+ /**
71
+ * Builds a lookup map from target collection to all incoming relations.
72
+ *
73
+ * This pre-computes which collections reference a given collection via foreign keys,
74
+ * so that delete operations can efficiently find all relations that need enforcement.
75
+ *
76
+ * @param schema - The full schema definition with relations
77
+ * @returns Map from target collection name to array of incoming relations, sorted by relation name for determinism
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const lookup = buildMergeRelationLookup(schema)
82
+ * // lookup.get('projects') => [{ relationName: 'todoBelongsToProject', sourceCollection: 'todos', ... }]
83
+ * ```
84
+ */
85
+ declare function buildMergeRelationLookup(schema: SchemaDefinition): Map<string, MergeIncomingRelation[]>;
86
+ /**
87
+ * Checks referential integrity when a delete operation is being applied.
88
+ *
89
+ * For each relation that targets the deleted record's collection, queries
90
+ * for referencing records and applies the relation's onDelete policy:
91
+ * - `restrict`: Blocks the delete if any references exist
92
+ * - `cascade`: Allows the delete and generates cascaded delete SideEffectOps
93
+ * - `set-null`: Allows the delete and generates update SideEffectOps to null out FKs
94
+ * - `no-action`: Allows the delete with no side effects (dangling references permitted)
95
+ *
96
+ * Processing is deterministic: relations are processed in sorted order by name,
97
+ * and referencing records within each relation are processed in sorted order by ID.
98
+ *
99
+ * @param deleteOp - The delete operation being evaluated
100
+ * @param schema - The full schema definition
101
+ * @param ctx - Database lookup context
102
+ * @param relationLookup - Pre-built relation lookup (optional; will be built if not provided)
103
+ * @returns Whether the delete is allowed, any side-effect ops, and traces for DevTools
104
+ */
105
+ declare function checkReferentialIntegrityOnDelete(deleteOp: Operation, schema: SchemaDefinition, ctx: ReferentialMergeContext, relationLookup?: Map<string, MergeIncomingRelation[]>): Promise<ReferentialCheckResult>;
106
+ /**
107
+ * Resolves a concurrent delete-vs-insert conflict on a referenced record.
108
+ *
109
+ * When one node deletes a record while another concurrently inserts a record
110
+ * that references the deleted record, we need to decide what wins.
111
+ *
112
+ * Resolution strategy depends on the relation's onDelete policy:
113
+ * - `restrict`: Block the delete (the insert wins — data integrity preserved)
114
+ * - `cascade`: Allow the delete and cascade-delete the newly inserted record
115
+ * - `set-null`: Allow the delete and null out the FK on the newly inserted record
116
+ * - `no-action`: Allow the delete (dangling reference is acceptable)
117
+ *
118
+ * @param deleteOp - The delete operation on the referenced record
119
+ * @param insertOp - The concurrent insert operation that references the deleted record
120
+ * @param relation - The relation definition connecting the two collections
121
+ * @returns The resolution action, side effects, and trace
122
+ */
123
+ declare function resolveDeleteVsInsertConflict(deleteOp: Operation, insertOp: Operation, relation: MergeIncomingRelation): DeleteVsInsertResolution;
2
124
 
3
125
  /**
4
126
  * Input to the merge engine when two concurrent operations conflict.
@@ -23,6 +145,12 @@ interface MergeResult {
23
145
  traces: MergeTrace[];
24
146
  /** Which operation's values dominate overall, or 'merged' if mixed */
25
147
  appliedOperation: 'local' | 'remote' | 'merged';
148
+ /**
149
+ * Side-effect operations generated during constraint resolution
150
+ * (e.g., cascade deletes, set-null updates from referential integrity).
151
+ * These must be applied by the caller after the merge.
152
+ */
153
+ sideEffects: SideEffectOp[];
26
154
  }
27
155
  /**
28
156
  * Output of a single field-level merge decision.
@@ -249,6 +377,12 @@ interface ConstraintResolution {
249
377
  resolvedRecord: Record<string, unknown>;
250
378
  /** Trace of the resolution decision for DevTools */
251
379
  trace: MergeTrace;
380
+ /**
381
+ * Side-effect operations generated during resolution
382
+ * (e.g., referring record updates for referential integrity).
383
+ * Empty unless the constraint resolver generates cascading actions.
384
+ */
385
+ sideEffects?: SideEffectOp[];
252
386
  }
253
387
  /**
254
388
  * Resolves a constraint violation by applying the constraint's onConflict strategy.
@@ -269,128 +403,6 @@ interface ConstraintResolution {
269
403
  */
270
404
  declare function resolveConstraintViolation(violation: ConstraintViolation, mergedRecord: Record<string, unknown>, localOp: Operation, remoteOp: Operation, baseState: Record<string, unknown>): ConstraintResolution;
271
405
 
272
- /**
273
- * Describes an incoming relation to a target collection.
274
- * Used by the delete-side referential integrity checker to know which
275
- * source collections reference the collection being deleted from.
276
- */
277
- interface MergeIncomingRelation {
278
- /** Name of the relation in the schema (e.g., 'todoBelongsToProject') */
279
- relationName: string;
280
- /** The collection that holds the foreign key (source of the relation) */
281
- sourceCollection: string;
282
- /** The foreign key field in the source collection */
283
- foreignKeyField: string;
284
- /** What to do when the referenced record is deleted */
285
- onDelete: OnDeleteAction;
286
- }
287
- /**
288
- * Pluggable context for querying the database during referential integrity checks.
289
- * The store layer provides the implementation; the merge package only depends on this interface.
290
- */
291
- interface ReferentialMergeContext {
292
- /** Query records in a collection matching a where clause */
293
- queryRecords(collection: string, where: Record<string, unknown>): Promise<Record<string, unknown>[]>;
294
- /** Check if a record exists in a collection */
295
- recordExists(collection: string, recordId: string): Promise<boolean>;
296
- }
297
- /**
298
- * Result of a referential integrity check on a delete operation.
299
- */
300
- interface ReferentialCheckResult {
301
- /** Whether the delete operation is allowed to proceed */
302
- allowed: boolean;
303
- /** Side-effect operations generated by cascade/set-null policies */
304
- sideEffectOps: SideEffectOp[];
305
- /** MergeTraces for every decision made (feeds DevTools) */
306
- traces: MergeTrace[];
307
- }
308
- /**
309
- * A side-effect operation generated during referential integrity enforcement.
310
- * These are not full Operations; they describe mutations the caller must apply.
311
- */
312
- interface SideEffectOp {
313
- /** The type of mutation to apply */
314
- type: 'delete' | 'update';
315
- /** The collection containing the affected record */
316
- collection: string;
317
- /** The ID of the affected record */
318
- recordId: string;
319
- /** For updates: the new field values. null for deletes. */
320
- data: Record<string, unknown> | null;
321
- /** For updates: the previous field values. null for deletes. */
322
- previousData: Record<string, unknown> | null;
323
- /** The onDelete policy that produced this side effect */
324
- policy: OnDeleteAction;
325
- /** The relation that triggered this side effect */
326
- relationName: string;
327
- }
328
- /**
329
- * Result of resolving a concurrent delete-vs-insert conflict.
330
- */
331
- interface DeleteVsInsertResolution {
332
- /** Whether to block the delete or allow it */
333
- action: 'block-delete' | 'allow-delete';
334
- /** Any side-effect operations (for cascade/set-null after allowing delete) */
335
- sideEffects: SideEffectOp[];
336
- /** Trace of the resolution decision, or null if no trace needed */
337
- trace: MergeTrace | null;
338
- }
339
- /**
340
- * Builds a lookup map from target collection to all incoming relations.
341
- *
342
- * This pre-computes which collections reference a given collection via foreign keys,
343
- * so that delete operations can efficiently find all relations that need enforcement.
344
- *
345
- * @param schema - The full schema definition with relations
346
- * @returns Map from target collection name to array of incoming relations, sorted by relation name for determinism
347
- *
348
- * @example
349
- * ```typescript
350
- * const lookup = buildMergeRelationLookup(schema)
351
- * // lookup.get('projects') => [{ relationName: 'todoBelongsToProject', sourceCollection: 'todos', ... }]
352
- * ```
353
- */
354
- declare function buildMergeRelationLookup(schema: SchemaDefinition): Map<string, MergeIncomingRelation[]>;
355
- /**
356
- * Checks referential integrity when a delete operation is being applied.
357
- *
358
- * For each relation that targets the deleted record's collection, queries
359
- * for referencing records and applies the relation's onDelete policy:
360
- * - `restrict`: Blocks the delete if any references exist
361
- * - `cascade`: Allows the delete and generates cascaded delete SideEffectOps
362
- * - `set-null`: Allows the delete and generates update SideEffectOps to null out FKs
363
- * - `no-action`: Allows the delete with no side effects (dangling references permitted)
364
- *
365
- * Processing is deterministic: relations are processed in sorted order by name,
366
- * and referencing records within each relation are processed in sorted order by ID.
367
- *
368
- * @param deleteOp - The delete operation being evaluated
369
- * @param schema - The full schema definition
370
- * @param ctx - Database lookup context
371
- * @param relationLookup - Pre-built relation lookup (optional; will be built if not provided)
372
- * @returns Whether the delete is allowed, any side-effect ops, and traces for DevTools
373
- */
374
- declare function checkReferentialIntegrityOnDelete(deleteOp: Operation, schema: SchemaDefinition, ctx: ReferentialMergeContext, relationLookup?: Map<string, MergeIncomingRelation[]>): Promise<ReferentialCheckResult>;
375
- /**
376
- * Resolves a concurrent delete-vs-insert conflict on a referenced record.
377
- *
378
- * When one node deletes a record while another concurrently inserts a record
379
- * that references the deleted record, we need to decide what wins.
380
- *
381
- * Resolution strategy depends on the relation's onDelete policy:
382
- * - `restrict`: Block the delete (the insert wins — data integrity preserved)
383
- * - `cascade`: Allow the delete and cascade-delete the newly inserted record
384
- * - `set-null`: Allow the delete and null out the FK on the newly inserted record
385
- * - `no-action`: Allow the delete (dangling reference is acceptable)
386
- *
387
- * @param deleteOp - The delete operation on the referenced record
388
- * @param insertOp - The concurrent insert operation that references the deleted record
389
- * @param relation - The relation definition connecting the two collections
390
- * @returns The resolution action, side effects, and trace
391
- */
392
- declare function resolveDeleteVsInsertConflict(deleteOp: Operation, insertOp: Operation, relation: MergeIncomingRelation): DeleteVsInsertResolution;
393
-
394
406
  /**
395
407
  * Three-tier merge engine for resolving concurrent operations.
396
408
  *
package/dist/index.js CHANGED
@@ -1172,7 +1172,8 @@ var MergeEngine = class {
1172
1172
  return {
1173
1173
  mergedData: {},
1174
1174
  traces: [],
1175
- appliedOperation: "merged"
1175
+ appliedOperation: "merged",
1176
+ sideEffects: []
1176
1177
  };
1177
1178
  }
1178
1179
  if (input.local.type === "delete" || input.remote.type === "delete") {
@@ -1190,6 +1191,7 @@ var MergeEngine = class {
1190
1191
  );
1191
1192
  let mergedData = fieldResult.mergedData;
1192
1193
  const allTraces = [...fieldResult.traces];
1194
+ const allSideEffects = [...fieldResult.sideEffects];
1193
1195
  for (const violation of violations) {
1194
1196
  const resolution = resolveConstraintViolation(
1195
1197
  violation,
@@ -1200,11 +1202,15 @@ var MergeEngine = class {
1200
1202
  );
1201
1203
  mergedData = resolution.resolvedRecord;
1202
1204
  allTraces.push(resolution.trace);
1205
+ if (resolution.sideEffects) {
1206
+ allSideEffects.push(...resolution.sideEffects);
1207
+ }
1203
1208
  }
1204
1209
  return {
1205
1210
  mergedData,
1206
1211
  traces: allTraces,
1207
- appliedOperation: determineAppliedOperation(allTraces)
1212
+ appliedOperation: determineAppliedOperation(allTraces),
1213
+ sideEffects: allSideEffects
1208
1214
  };
1209
1215
  }
1210
1216
  return fieldResult;
@@ -1252,7 +1258,8 @@ var MergeEngine = class {
1252
1258
  return {
1253
1259
  mergedData,
1254
1260
  traces,
1255
- appliedOperation: determineAppliedOperation(traces)
1261
+ appliedOperation: determineAppliedOperation(traces),
1262
+ sideEffects: []
1256
1263
  };
1257
1264
  }
1258
1265
  /**
@@ -1264,21 +1271,23 @@ var MergeEngine = class {
1264
1271
  const comparison = HybridLogicalClock6.compare(local.timestamp, remote.timestamp);
1265
1272
  if (comparison >= 0) {
1266
1273
  if (local.type === "delete") {
1267
- return { mergedData: {}, traces: [], appliedOperation: "local" };
1274
+ return { mergedData: {}, traces: [], appliedOperation: "local", sideEffects: [] };
1268
1275
  }
1269
1276
  return {
1270
1277
  mergedData: { ...input.baseState, ...local.data ?? {} },
1271
1278
  traces: [],
1272
- appliedOperation: "local"
1279
+ appliedOperation: "local",
1280
+ sideEffects: []
1273
1281
  };
1274
1282
  }
1275
1283
  if (remote.type === "delete") {
1276
- return { mergedData: {}, traces: [], appliedOperation: "remote" };
1284
+ return { mergedData: {}, traces: [], appliedOperation: "remote", sideEffects: [] };
1277
1285
  }
1278
1286
  return {
1279
1287
  mergedData: { ...input.baseState, ...remote.data ?? {} },
1280
1288
  traces: [],
1281
- appliedOperation: "remote"
1289
+ appliedOperation: "remote",
1290
+ sideEffects: []
1282
1291
  };
1283
1292
  }
1284
1293
  };