@korajs/core 0.1.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/chunk-ZMUY7AVV.js +421 -0
- package/dist/chunk-ZMUY7AVV.js.map +1 -0
- package/dist/events-D_kDPDC9.d.cts +324 -0
- package/dist/events-D_kDPDC9.d.ts +324 -0
- package/dist/index.cjs +1153 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +401 -0
- package/dist/index.d.ts +401 -0
- package/dist/index.js +707 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.cjs +397 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +58 -0
- package/dist/internal.d.ts +58 -0
- package/dist/internal.js +55 -0
- package/dist/internal.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid Logical Clock timestamp.
|
|
3
|
+
* Provides a total order that respects causality without requiring synchronized clocks.
|
|
4
|
+
*/
|
|
5
|
+
interface HLCTimestamp {
|
|
6
|
+
/** Physical wall-clock time in milliseconds */
|
|
7
|
+
wallTime: number;
|
|
8
|
+
/** Logical counter. Increments when wallTime hasn't changed since last event. */
|
|
9
|
+
logical: number;
|
|
10
|
+
/** Node ID for tie-breaking. Ensures total order even with identical wall+logical. */
|
|
11
|
+
nodeId: string;
|
|
12
|
+
}
|
|
13
|
+
/** The three mutation types an operation can represent */
|
|
14
|
+
type OperationType = 'insert' | 'update' | 'delete';
|
|
15
|
+
/**
|
|
16
|
+
* The atomic unit of the entire system. Every mutation produces an Operation.
|
|
17
|
+
* Operations are IMMUTABLE and CONTENT-ADDRESSED.
|
|
18
|
+
*/
|
|
19
|
+
interface Operation {
|
|
20
|
+
/** SHA-256 hash of (type + collection + recordId + data + timestamp + nodeId). Content-addressed. */
|
|
21
|
+
id: string;
|
|
22
|
+
/** UUID v7 of the originating device. Time-sortable. */
|
|
23
|
+
nodeId: string;
|
|
24
|
+
/** What happened */
|
|
25
|
+
type: OperationType;
|
|
26
|
+
/** Which collection (from schema) */
|
|
27
|
+
collection: string;
|
|
28
|
+
/** ID of the affected record. UUID v7 for inserts, existing ID for update/delete. */
|
|
29
|
+
recordId: string;
|
|
30
|
+
/** Field values. null for delete. For updates, contains ONLY changed fields. */
|
|
31
|
+
data: Record<string, unknown> | null;
|
|
32
|
+
/** For updates: previous values of changed fields (enables 3-way merge). null for insert/delete. */
|
|
33
|
+
previousData: Record<string, unknown> | null;
|
|
34
|
+
/** Hybrid Logical Clock timestamp. Used for causal ordering. */
|
|
35
|
+
timestamp: HLCTimestamp;
|
|
36
|
+
/** Monotonically increasing per node. Used in version vectors. */
|
|
37
|
+
sequenceNumber: number;
|
|
38
|
+
/** Operation IDs this operation causally depends on (direct parents in the DAG). */
|
|
39
|
+
causalDeps: string[];
|
|
40
|
+
/** Schema version at time of creation. Used for migration transforms. */
|
|
41
|
+
schemaVersion: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Input for creating an operation (before id and timestamp are assigned).
|
|
45
|
+
*/
|
|
46
|
+
interface OperationInput {
|
|
47
|
+
nodeId: string;
|
|
48
|
+
type: OperationType;
|
|
49
|
+
collection: string;
|
|
50
|
+
recordId: string;
|
|
51
|
+
data: Record<string, unknown> | null;
|
|
52
|
+
previousData: Record<string, unknown> | null;
|
|
53
|
+
sequenceNumber: number;
|
|
54
|
+
causalDeps: string[];
|
|
55
|
+
schemaVersion: number;
|
|
56
|
+
}
|
|
57
|
+
/** Version vector: maps nodeId to the max sequence number seen from that node */
|
|
58
|
+
type VersionVector = Map<string, number>;
|
|
59
|
+
/** Field kinds supported by the schema system */
|
|
60
|
+
type FieldKind = 'string' | 'number' | 'boolean' | 'timestamp' | 'richtext' | 'enum' | 'array';
|
|
61
|
+
/**
|
|
62
|
+
* Descriptor produced by the type builder (t.string(), t.number(), etc.).
|
|
63
|
+
* Represents a fully configured field definition.
|
|
64
|
+
*/
|
|
65
|
+
interface FieldDescriptor {
|
|
66
|
+
kind: FieldKind;
|
|
67
|
+
required: boolean;
|
|
68
|
+
defaultValue: unknown;
|
|
69
|
+
auto: boolean;
|
|
70
|
+
enumValues: readonly string[] | null;
|
|
71
|
+
itemKind: FieldKind | null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Definition of a collection within the schema.
|
|
75
|
+
*/
|
|
76
|
+
interface CollectionDefinition {
|
|
77
|
+
fields: Record<string, FieldDescriptor>;
|
|
78
|
+
indexes: string[];
|
|
79
|
+
constraints: Constraint[];
|
|
80
|
+
resolvers: Record<string, CustomResolver>;
|
|
81
|
+
}
|
|
82
|
+
/** Custom resolver function for tier 3 merge resolution */
|
|
83
|
+
type CustomResolver = (local: unknown, remote: unknown, base: unknown) => unknown;
|
|
84
|
+
/**
|
|
85
|
+
* Constraint for tier 2 conflict resolution.
|
|
86
|
+
*/
|
|
87
|
+
interface Constraint {
|
|
88
|
+
type: 'unique' | 'capacity' | 'referential';
|
|
89
|
+
fields: string[];
|
|
90
|
+
where?: Record<string, unknown>;
|
|
91
|
+
onConflict: 'first-write-wins' | 'last-write-wins' | 'priority-field' | 'server-decides' | 'custom';
|
|
92
|
+
priorityField?: string;
|
|
93
|
+
resolve?: (local: unknown, remote: unknown, base: unknown) => unknown;
|
|
94
|
+
}
|
|
95
|
+
/** Relation type between collections */
|
|
96
|
+
type RelationType = 'one-to-one' | 'one-to-many' | 'many-to-one' | 'many-to-many';
|
|
97
|
+
/** On-delete behavior for relations */
|
|
98
|
+
type OnDeleteAction = 'cascade' | 'set-null' | 'restrict' | 'no-action';
|
|
99
|
+
/**
|
|
100
|
+
* Definition of a relation between two collections.
|
|
101
|
+
*/
|
|
102
|
+
interface RelationDefinition {
|
|
103
|
+
from: string;
|
|
104
|
+
to: string;
|
|
105
|
+
type: RelationType;
|
|
106
|
+
field: string;
|
|
107
|
+
onDelete: OnDeleteAction;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The complete schema definition produced by defineSchema().
|
|
111
|
+
*/
|
|
112
|
+
interface SchemaDefinition {
|
|
113
|
+
version: number;
|
|
114
|
+
collections: Record<string, CollectionDefinition>;
|
|
115
|
+
relations: Record<string, RelationDefinition>;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Merge strategies available for auto-merge and constraints.
|
|
119
|
+
*/
|
|
120
|
+
declare const MERGE_STRATEGIES: readonly ["auto-merge", "lww", "first-write-wins", "server-decides", "custom"];
|
|
121
|
+
type MergeStrategy = (typeof MERGE_STRATEGIES)[number];
|
|
122
|
+
/**
|
|
123
|
+
* Connection quality levels for adaptive sync.
|
|
124
|
+
*/
|
|
125
|
+
declare const CONNECTION_QUALITIES: readonly ["excellent", "good", "fair", "poor", "offline"];
|
|
126
|
+
type ConnectionQuality = (typeof CONNECTION_QUALITIES)[number];
|
|
127
|
+
/**
|
|
128
|
+
* Injectable time source for deterministic testing of clocks.
|
|
129
|
+
*/
|
|
130
|
+
interface TimeSource {
|
|
131
|
+
now(): number;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Injectable random source for deterministic testing of UUID generation.
|
|
135
|
+
*/
|
|
136
|
+
interface RandomSource {
|
|
137
|
+
getRandomValues<T extends ArrayBufferView>(array: T): T;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Hybrid Logical Clock implementation based on Kulkarni et al.
|
|
142
|
+
*
|
|
143
|
+
* Provides a total order that respects causality without requiring synchronized clocks.
|
|
144
|
+
* Each call to now() returns a timestamp strictly greater than the previous one.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* const clock = new HybridLogicalClock('node-1')
|
|
149
|
+
* const ts1 = clock.now()
|
|
150
|
+
* const ts2 = clock.now()
|
|
151
|
+
* // HybridLogicalClock.compare(ts1, ts2) < 0 (ts1 is earlier)
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
declare class HybridLogicalClock {
|
|
155
|
+
private readonly nodeId;
|
|
156
|
+
private readonly timeSource;
|
|
157
|
+
private readonly onDriftWarning?;
|
|
158
|
+
private wallTime;
|
|
159
|
+
private logical;
|
|
160
|
+
constructor(nodeId: string, timeSource?: TimeSource, onDriftWarning?: ((driftMs: number) => void) | undefined);
|
|
161
|
+
/**
|
|
162
|
+
* Generate a new timestamp for a local event.
|
|
163
|
+
* Guarantees monotonicity: each call returns a timestamp strictly greater than the previous.
|
|
164
|
+
*
|
|
165
|
+
* @throws {ClockDriftError} If physical time is more than 5 minutes behind the HLC wallTime
|
|
166
|
+
*/
|
|
167
|
+
now(): HLCTimestamp;
|
|
168
|
+
/**
|
|
169
|
+
* Update clock on receiving a remote timestamp.
|
|
170
|
+
* Merges the remote clock state with the local state to maintain causal ordering.
|
|
171
|
+
*
|
|
172
|
+
* @throws {ClockDriftError} If physical time is more than 5 minutes behind the resulting wallTime
|
|
173
|
+
*/
|
|
174
|
+
receive(remote: HLCTimestamp): HLCTimestamp;
|
|
175
|
+
/**
|
|
176
|
+
* Compare two timestamps. Returns negative if a < b, positive if a > b, zero if equal.
|
|
177
|
+
* Total order: wallTime first, then logical, then nodeId (lexicographic).
|
|
178
|
+
*/
|
|
179
|
+
static compare(a: HLCTimestamp, b: HLCTimestamp): number;
|
|
180
|
+
/**
|
|
181
|
+
* Serialize an HLC timestamp to a string that sorts lexicographically.
|
|
182
|
+
* Format: zero-padded wallTime:logical:nodeId
|
|
183
|
+
*/
|
|
184
|
+
static serialize(ts: HLCTimestamp): string;
|
|
185
|
+
/**
|
|
186
|
+
* Deserialize an HLC timestamp from its serialized string form.
|
|
187
|
+
*/
|
|
188
|
+
static deserialize(s: string): HLCTimestamp;
|
|
189
|
+
private checkDrift;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Creates an immutable, content-addressed Operation from the given parameters.
|
|
194
|
+
* The operation is deep-frozen after creation — it cannot be modified.
|
|
195
|
+
*
|
|
196
|
+
* @param input - The operation parameters (without id, which is computed)
|
|
197
|
+
* @param clock - The HLC clock to generate the timestamp
|
|
198
|
+
* @returns A frozen Operation with a content-addressed id
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* const op = await createOperation({
|
|
203
|
+
* nodeId: 'device-1',
|
|
204
|
+
* type: 'insert',
|
|
205
|
+
* collection: 'todos',
|
|
206
|
+
* recordId: 'rec-1',
|
|
207
|
+
* data: { title: 'Ship it' },
|
|
208
|
+
* previousData: null,
|
|
209
|
+
* sequenceNumber: 1,
|
|
210
|
+
* causalDeps: [],
|
|
211
|
+
* schemaVersion: 1,
|
|
212
|
+
* }, clock)
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
declare function createOperation(input: OperationInput, clock: HybridLogicalClock): Promise<Operation>;
|
|
216
|
+
/**
|
|
217
|
+
* Validates operation input parameters. Throws OperationError with
|
|
218
|
+
* contextual information on validation failure.
|
|
219
|
+
*/
|
|
220
|
+
declare function validateOperationParams(input: OperationInput): void;
|
|
221
|
+
/**
|
|
222
|
+
* Verify the integrity of an operation by recomputing its content hash.
|
|
223
|
+
* Returns true if the id matches the recomputed hash.
|
|
224
|
+
*/
|
|
225
|
+
declare function verifyOperationIntegrity(op: Operation): Promise<boolean>;
|
|
226
|
+
/**
|
|
227
|
+
* Type guard for Operation interface.
|
|
228
|
+
*/
|
|
229
|
+
declare function isValidOperation(value: unknown): value is Operation;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Trace of a merge decision. Records all inputs and outputs for debugging and DevTools.
|
|
233
|
+
*/
|
|
234
|
+
interface MergeTrace {
|
|
235
|
+
operationA: Operation;
|
|
236
|
+
operationB: Operation;
|
|
237
|
+
field: string;
|
|
238
|
+
strategy: string;
|
|
239
|
+
inputA: unknown;
|
|
240
|
+
inputB: unknown;
|
|
241
|
+
base: unknown | null;
|
|
242
|
+
output: unknown;
|
|
243
|
+
tier: 1 | 2 | 3;
|
|
244
|
+
constraintViolated: string | null;
|
|
245
|
+
duration: number;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* All events emitted by the Kora framework.
|
|
249
|
+
* These are consumed by DevTools and can be observed by the developer.
|
|
250
|
+
*/
|
|
251
|
+
type KoraEvent = {
|
|
252
|
+
type: 'operation:created';
|
|
253
|
+
operation: Operation;
|
|
254
|
+
} | {
|
|
255
|
+
type: 'operation:applied';
|
|
256
|
+
operation: Operation;
|
|
257
|
+
duration: number;
|
|
258
|
+
} | {
|
|
259
|
+
type: 'merge:started';
|
|
260
|
+
operationA: Operation;
|
|
261
|
+
operationB: Operation;
|
|
262
|
+
} | {
|
|
263
|
+
type: 'merge:completed';
|
|
264
|
+
trace: MergeTrace;
|
|
265
|
+
} | {
|
|
266
|
+
type: 'merge:conflict';
|
|
267
|
+
trace: MergeTrace;
|
|
268
|
+
} | {
|
|
269
|
+
type: 'constraint:violated';
|
|
270
|
+
constraint: string;
|
|
271
|
+
trace: MergeTrace;
|
|
272
|
+
} | {
|
|
273
|
+
type: 'sync:connected';
|
|
274
|
+
nodeId: string;
|
|
275
|
+
} | {
|
|
276
|
+
type: 'sync:disconnected';
|
|
277
|
+
reason: string;
|
|
278
|
+
} | {
|
|
279
|
+
type: 'sync:sent';
|
|
280
|
+
operations: Operation[];
|
|
281
|
+
batchSize: number;
|
|
282
|
+
} | {
|
|
283
|
+
type: 'sync:received';
|
|
284
|
+
operations: Operation[];
|
|
285
|
+
batchSize: number;
|
|
286
|
+
} | {
|
|
287
|
+
type: 'sync:acknowledged';
|
|
288
|
+
sequenceNumber: number;
|
|
289
|
+
} | {
|
|
290
|
+
type: 'query:subscribed';
|
|
291
|
+
queryId: string;
|
|
292
|
+
collection: string;
|
|
293
|
+
} | {
|
|
294
|
+
type: 'query:invalidated';
|
|
295
|
+
queryId: string;
|
|
296
|
+
trigger: Operation;
|
|
297
|
+
} | {
|
|
298
|
+
type: 'query:executed';
|
|
299
|
+
queryId: string;
|
|
300
|
+
duration: number;
|
|
301
|
+
resultCount: number;
|
|
302
|
+
} | {
|
|
303
|
+
type: 'connection:quality';
|
|
304
|
+
quality: ConnectionQuality;
|
|
305
|
+
};
|
|
306
|
+
/** Extract the event type string union from KoraEvent */
|
|
307
|
+
type KoraEventType = KoraEvent['type'];
|
|
308
|
+
/** Extract a specific event by its type */
|
|
309
|
+
type KoraEventByType<T extends KoraEventType> = Extract<KoraEvent, {
|
|
310
|
+
type: T;
|
|
311
|
+
}>;
|
|
312
|
+
/** Listener function for a specific event type */
|
|
313
|
+
type KoraEventListener<T extends KoraEventType> = (event: KoraEventByType<T>) => void;
|
|
314
|
+
/**
|
|
315
|
+
* Event emitter interface for the Kora framework.
|
|
316
|
+
* All packages that emit events must implement this interface.
|
|
317
|
+
*/
|
|
318
|
+
interface KoraEventEmitter {
|
|
319
|
+
on<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): () => void;
|
|
320
|
+
off<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): void;
|
|
321
|
+
emit<T extends KoraEventType>(event: KoraEventByType<T>): void;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export { type CustomResolver as C, type FieldKind as F, type HLCTimestamp as H, type KoraEvent as K, MERGE_STRATEGIES as M, type OperationType as O, type RandomSource as R, type SchemaDefinition as S, type TimeSource as T, type VersionVector as V, type FieldDescriptor as a, type CollectionDefinition as b, type RelationDefinition as c, type Operation as d, CONNECTION_QUALITIES as e, type ConnectionQuality as f, type Constraint as g, HybridLogicalClock as h, type KoraEventByType as i, type KoraEventEmitter as j, type KoraEventListener as k, type KoraEventType as l, type MergeStrategy as m, type MergeTrace as n, type OnDeleteAction as o, type OperationInput as p, type RelationType as q, createOperation as r, isValidOperation as s, validateOperationParams as t, verifyOperationIntegrity as v };
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid Logical Clock timestamp.
|
|
3
|
+
* Provides a total order that respects causality without requiring synchronized clocks.
|
|
4
|
+
*/
|
|
5
|
+
interface HLCTimestamp {
|
|
6
|
+
/** Physical wall-clock time in milliseconds */
|
|
7
|
+
wallTime: number;
|
|
8
|
+
/** Logical counter. Increments when wallTime hasn't changed since last event. */
|
|
9
|
+
logical: number;
|
|
10
|
+
/** Node ID for tie-breaking. Ensures total order even with identical wall+logical. */
|
|
11
|
+
nodeId: string;
|
|
12
|
+
}
|
|
13
|
+
/** The three mutation types an operation can represent */
|
|
14
|
+
type OperationType = 'insert' | 'update' | 'delete';
|
|
15
|
+
/**
|
|
16
|
+
* The atomic unit of the entire system. Every mutation produces an Operation.
|
|
17
|
+
* Operations are IMMUTABLE and CONTENT-ADDRESSED.
|
|
18
|
+
*/
|
|
19
|
+
interface Operation {
|
|
20
|
+
/** SHA-256 hash of (type + collection + recordId + data + timestamp + nodeId). Content-addressed. */
|
|
21
|
+
id: string;
|
|
22
|
+
/** UUID v7 of the originating device. Time-sortable. */
|
|
23
|
+
nodeId: string;
|
|
24
|
+
/** What happened */
|
|
25
|
+
type: OperationType;
|
|
26
|
+
/** Which collection (from schema) */
|
|
27
|
+
collection: string;
|
|
28
|
+
/** ID of the affected record. UUID v7 for inserts, existing ID for update/delete. */
|
|
29
|
+
recordId: string;
|
|
30
|
+
/** Field values. null for delete. For updates, contains ONLY changed fields. */
|
|
31
|
+
data: Record<string, unknown> | null;
|
|
32
|
+
/** For updates: previous values of changed fields (enables 3-way merge). null for insert/delete. */
|
|
33
|
+
previousData: Record<string, unknown> | null;
|
|
34
|
+
/** Hybrid Logical Clock timestamp. Used for causal ordering. */
|
|
35
|
+
timestamp: HLCTimestamp;
|
|
36
|
+
/** Monotonically increasing per node. Used in version vectors. */
|
|
37
|
+
sequenceNumber: number;
|
|
38
|
+
/** Operation IDs this operation causally depends on (direct parents in the DAG). */
|
|
39
|
+
causalDeps: string[];
|
|
40
|
+
/** Schema version at time of creation. Used for migration transforms. */
|
|
41
|
+
schemaVersion: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Input for creating an operation (before id and timestamp are assigned).
|
|
45
|
+
*/
|
|
46
|
+
interface OperationInput {
|
|
47
|
+
nodeId: string;
|
|
48
|
+
type: OperationType;
|
|
49
|
+
collection: string;
|
|
50
|
+
recordId: string;
|
|
51
|
+
data: Record<string, unknown> | null;
|
|
52
|
+
previousData: Record<string, unknown> | null;
|
|
53
|
+
sequenceNumber: number;
|
|
54
|
+
causalDeps: string[];
|
|
55
|
+
schemaVersion: number;
|
|
56
|
+
}
|
|
57
|
+
/** Version vector: maps nodeId to the max sequence number seen from that node */
|
|
58
|
+
type VersionVector = Map<string, number>;
|
|
59
|
+
/** Field kinds supported by the schema system */
|
|
60
|
+
type FieldKind = 'string' | 'number' | 'boolean' | 'timestamp' | 'richtext' | 'enum' | 'array';
|
|
61
|
+
/**
|
|
62
|
+
* Descriptor produced by the type builder (t.string(), t.number(), etc.).
|
|
63
|
+
* Represents a fully configured field definition.
|
|
64
|
+
*/
|
|
65
|
+
interface FieldDescriptor {
|
|
66
|
+
kind: FieldKind;
|
|
67
|
+
required: boolean;
|
|
68
|
+
defaultValue: unknown;
|
|
69
|
+
auto: boolean;
|
|
70
|
+
enumValues: readonly string[] | null;
|
|
71
|
+
itemKind: FieldKind | null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Definition of a collection within the schema.
|
|
75
|
+
*/
|
|
76
|
+
interface CollectionDefinition {
|
|
77
|
+
fields: Record<string, FieldDescriptor>;
|
|
78
|
+
indexes: string[];
|
|
79
|
+
constraints: Constraint[];
|
|
80
|
+
resolvers: Record<string, CustomResolver>;
|
|
81
|
+
}
|
|
82
|
+
/** Custom resolver function for tier 3 merge resolution */
|
|
83
|
+
type CustomResolver = (local: unknown, remote: unknown, base: unknown) => unknown;
|
|
84
|
+
/**
|
|
85
|
+
* Constraint for tier 2 conflict resolution.
|
|
86
|
+
*/
|
|
87
|
+
interface Constraint {
|
|
88
|
+
type: 'unique' | 'capacity' | 'referential';
|
|
89
|
+
fields: string[];
|
|
90
|
+
where?: Record<string, unknown>;
|
|
91
|
+
onConflict: 'first-write-wins' | 'last-write-wins' | 'priority-field' | 'server-decides' | 'custom';
|
|
92
|
+
priorityField?: string;
|
|
93
|
+
resolve?: (local: unknown, remote: unknown, base: unknown) => unknown;
|
|
94
|
+
}
|
|
95
|
+
/** Relation type between collections */
|
|
96
|
+
type RelationType = 'one-to-one' | 'one-to-many' | 'many-to-one' | 'many-to-many';
|
|
97
|
+
/** On-delete behavior for relations */
|
|
98
|
+
type OnDeleteAction = 'cascade' | 'set-null' | 'restrict' | 'no-action';
|
|
99
|
+
/**
|
|
100
|
+
* Definition of a relation between two collections.
|
|
101
|
+
*/
|
|
102
|
+
interface RelationDefinition {
|
|
103
|
+
from: string;
|
|
104
|
+
to: string;
|
|
105
|
+
type: RelationType;
|
|
106
|
+
field: string;
|
|
107
|
+
onDelete: OnDeleteAction;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The complete schema definition produced by defineSchema().
|
|
111
|
+
*/
|
|
112
|
+
interface SchemaDefinition {
|
|
113
|
+
version: number;
|
|
114
|
+
collections: Record<string, CollectionDefinition>;
|
|
115
|
+
relations: Record<string, RelationDefinition>;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Merge strategies available for auto-merge and constraints.
|
|
119
|
+
*/
|
|
120
|
+
declare const MERGE_STRATEGIES: readonly ["auto-merge", "lww", "first-write-wins", "server-decides", "custom"];
|
|
121
|
+
type MergeStrategy = (typeof MERGE_STRATEGIES)[number];
|
|
122
|
+
/**
|
|
123
|
+
* Connection quality levels for adaptive sync.
|
|
124
|
+
*/
|
|
125
|
+
declare const CONNECTION_QUALITIES: readonly ["excellent", "good", "fair", "poor", "offline"];
|
|
126
|
+
type ConnectionQuality = (typeof CONNECTION_QUALITIES)[number];
|
|
127
|
+
/**
|
|
128
|
+
* Injectable time source for deterministic testing of clocks.
|
|
129
|
+
*/
|
|
130
|
+
interface TimeSource {
|
|
131
|
+
now(): number;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Injectable random source for deterministic testing of UUID generation.
|
|
135
|
+
*/
|
|
136
|
+
interface RandomSource {
|
|
137
|
+
getRandomValues<T extends ArrayBufferView>(array: T): T;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Hybrid Logical Clock implementation based on Kulkarni et al.
|
|
142
|
+
*
|
|
143
|
+
* Provides a total order that respects causality without requiring synchronized clocks.
|
|
144
|
+
* Each call to now() returns a timestamp strictly greater than the previous one.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* const clock = new HybridLogicalClock('node-1')
|
|
149
|
+
* const ts1 = clock.now()
|
|
150
|
+
* const ts2 = clock.now()
|
|
151
|
+
* // HybridLogicalClock.compare(ts1, ts2) < 0 (ts1 is earlier)
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
declare class HybridLogicalClock {
|
|
155
|
+
private readonly nodeId;
|
|
156
|
+
private readonly timeSource;
|
|
157
|
+
private readonly onDriftWarning?;
|
|
158
|
+
private wallTime;
|
|
159
|
+
private logical;
|
|
160
|
+
constructor(nodeId: string, timeSource?: TimeSource, onDriftWarning?: ((driftMs: number) => void) | undefined);
|
|
161
|
+
/**
|
|
162
|
+
* Generate a new timestamp for a local event.
|
|
163
|
+
* Guarantees monotonicity: each call returns a timestamp strictly greater than the previous.
|
|
164
|
+
*
|
|
165
|
+
* @throws {ClockDriftError} If physical time is more than 5 minutes behind the HLC wallTime
|
|
166
|
+
*/
|
|
167
|
+
now(): HLCTimestamp;
|
|
168
|
+
/**
|
|
169
|
+
* Update clock on receiving a remote timestamp.
|
|
170
|
+
* Merges the remote clock state with the local state to maintain causal ordering.
|
|
171
|
+
*
|
|
172
|
+
* @throws {ClockDriftError} If physical time is more than 5 minutes behind the resulting wallTime
|
|
173
|
+
*/
|
|
174
|
+
receive(remote: HLCTimestamp): HLCTimestamp;
|
|
175
|
+
/**
|
|
176
|
+
* Compare two timestamps. Returns negative if a < b, positive if a > b, zero if equal.
|
|
177
|
+
* Total order: wallTime first, then logical, then nodeId (lexicographic).
|
|
178
|
+
*/
|
|
179
|
+
static compare(a: HLCTimestamp, b: HLCTimestamp): number;
|
|
180
|
+
/**
|
|
181
|
+
* Serialize an HLC timestamp to a string that sorts lexicographically.
|
|
182
|
+
* Format: zero-padded wallTime:logical:nodeId
|
|
183
|
+
*/
|
|
184
|
+
static serialize(ts: HLCTimestamp): string;
|
|
185
|
+
/**
|
|
186
|
+
* Deserialize an HLC timestamp from its serialized string form.
|
|
187
|
+
*/
|
|
188
|
+
static deserialize(s: string): HLCTimestamp;
|
|
189
|
+
private checkDrift;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Creates an immutable, content-addressed Operation from the given parameters.
|
|
194
|
+
* The operation is deep-frozen after creation — it cannot be modified.
|
|
195
|
+
*
|
|
196
|
+
* @param input - The operation parameters (without id, which is computed)
|
|
197
|
+
* @param clock - The HLC clock to generate the timestamp
|
|
198
|
+
* @returns A frozen Operation with a content-addressed id
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* const op = await createOperation({
|
|
203
|
+
* nodeId: 'device-1',
|
|
204
|
+
* type: 'insert',
|
|
205
|
+
* collection: 'todos',
|
|
206
|
+
* recordId: 'rec-1',
|
|
207
|
+
* data: { title: 'Ship it' },
|
|
208
|
+
* previousData: null,
|
|
209
|
+
* sequenceNumber: 1,
|
|
210
|
+
* causalDeps: [],
|
|
211
|
+
* schemaVersion: 1,
|
|
212
|
+
* }, clock)
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
declare function createOperation(input: OperationInput, clock: HybridLogicalClock): Promise<Operation>;
|
|
216
|
+
/**
|
|
217
|
+
* Validates operation input parameters. Throws OperationError with
|
|
218
|
+
* contextual information on validation failure.
|
|
219
|
+
*/
|
|
220
|
+
declare function validateOperationParams(input: OperationInput): void;
|
|
221
|
+
/**
|
|
222
|
+
* Verify the integrity of an operation by recomputing its content hash.
|
|
223
|
+
* Returns true if the id matches the recomputed hash.
|
|
224
|
+
*/
|
|
225
|
+
declare function verifyOperationIntegrity(op: Operation): Promise<boolean>;
|
|
226
|
+
/**
|
|
227
|
+
* Type guard for Operation interface.
|
|
228
|
+
*/
|
|
229
|
+
declare function isValidOperation(value: unknown): value is Operation;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Trace of a merge decision. Records all inputs and outputs for debugging and DevTools.
|
|
233
|
+
*/
|
|
234
|
+
interface MergeTrace {
|
|
235
|
+
operationA: Operation;
|
|
236
|
+
operationB: Operation;
|
|
237
|
+
field: string;
|
|
238
|
+
strategy: string;
|
|
239
|
+
inputA: unknown;
|
|
240
|
+
inputB: unknown;
|
|
241
|
+
base: unknown | null;
|
|
242
|
+
output: unknown;
|
|
243
|
+
tier: 1 | 2 | 3;
|
|
244
|
+
constraintViolated: string | null;
|
|
245
|
+
duration: number;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* All events emitted by the Kora framework.
|
|
249
|
+
* These are consumed by DevTools and can be observed by the developer.
|
|
250
|
+
*/
|
|
251
|
+
type KoraEvent = {
|
|
252
|
+
type: 'operation:created';
|
|
253
|
+
operation: Operation;
|
|
254
|
+
} | {
|
|
255
|
+
type: 'operation:applied';
|
|
256
|
+
operation: Operation;
|
|
257
|
+
duration: number;
|
|
258
|
+
} | {
|
|
259
|
+
type: 'merge:started';
|
|
260
|
+
operationA: Operation;
|
|
261
|
+
operationB: Operation;
|
|
262
|
+
} | {
|
|
263
|
+
type: 'merge:completed';
|
|
264
|
+
trace: MergeTrace;
|
|
265
|
+
} | {
|
|
266
|
+
type: 'merge:conflict';
|
|
267
|
+
trace: MergeTrace;
|
|
268
|
+
} | {
|
|
269
|
+
type: 'constraint:violated';
|
|
270
|
+
constraint: string;
|
|
271
|
+
trace: MergeTrace;
|
|
272
|
+
} | {
|
|
273
|
+
type: 'sync:connected';
|
|
274
|
+
nodeId: string;
|
|
275
|
+
} | {
|
|
276
|
+
type: 'sync:disconnected';
|
|
277
|
+
reason: string;
|
|
278
|
+
} | {
|
|
279
|
+
type: 'sync:sent';
|
|
280
|
+
operations: Operation[];
|
|
281
|
+
batchSize: number;
|
|
282
|
+
} | {
|
|
283
|
+
type: 'sync:received';
|
|
284
|
+
operations: Operation[];
|
|
285
|
+
batchSize: number;
|
|
286
|
+
} | {
|
|
287
|
+
type: 'sync:acknowledged';
|
|
288
|
+
sequenceNumber: number;
|
|
289
|
+
} | {
|
|
290
|
+
type: 'query:subscribed';
|
|
291
|
+
queryId: string;
|
|
292
|
+
collection: string;
|
|
293
|
+
} | {
|
|
294
|
+
type: 'query:invalidated';
|
|
295
|
+
queryId: string;
|
|
296
|
+
trigger: Operation;
|
|
297
|
+
} | {
|
|
298
|
+
type: 'query:executed';
|
|
299
|
+
queryId: string;
|
|
300
|
+
duration: number;
|
|
301
|
+
resultCount: number;
|
|
302
|
+
} | {
|
|
303
|
+
type: 'connection:quality';
|
|
304
|
+
quality: ConnectionQuality;
|
|
305
|
+
};
|
|
306
|
+
/** Extract the event type string union from KoraEvent */
|
|
307
|
+
type KoraEventType = KoraEvent['type'];
|
|
308
|
+
/** Extract a specific event by its type */
|
|
309
|
+
type KoraEventByType<T extends KoraEventType> = Extract<KoraEvent, {
|
|
310
|
+
type: T;
|
|
311
|
+
}>;
|
|
312
|
+
/** Listener function for a specific event type */
|
|
313
|
+
type KoraEventListener<T extends KoraEventType> = (event: KoraEventByType<T>) => void;
|
|
314
|
+
/**
|
|
315
|
+
* Event emitter interface for the Kora framework.
|
|
316
|
+
* All packages that emit events must implement this interface.
|
|
317
|
+
*/
|
|
318
|
+
interface KoraEventEmitter {
|
|
319
|
+
on<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): () => void;
|
|
320
|
+
off<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): void;
|
|
321
|
+
emit<T extends KoraEventType>(event: KoraEventByType<T>): void;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export { type CustomResolver as C, type FieldKind as F, type HLCTimestamp as H, type KoraEvent as K, MERGE_STRATEGIES as M, type OperationType as O, type RandomSource as R, type SchemaDefinition as S, type TimeSource as T, type VersionVector as V, type FieldDescriptor as a, type CollectionDefinition as b, type RelationDefinition as c, type Operation as d, CONNECTION_QUALITIES as e, type ConnectionQuality as f, type Constraint as g, HybridLogicalClock as h, type KoraEventByType as i, type KoraEventEmitter as j, type KoraEventListener as k, type KoraEventType as l, type MergeStrategy as m, type MergeTrace as n, type OnDeleteAction as o, type OperationInput as p, type RelationType as q, createOperation as r, isValidOperation as s, validateOperationParams as t, verifyOperationIntegrity as v };
|