@korajs/core 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.
@@ -0,0 +1,844 @@
1
+ /**
2
+ * Base field builder implementing the builder pattern for schema field definitions.
3
+ * Each builder is immutable — modifier methods return new builder instances.
4
+ *
5
+ * Type parameters track field metadata at the type level for inference:
6
+ * - Kind: the field kind ('string', 'number', etc.)
7
+ * - Req: whether the field is required (true = required on insert)
8
+ * - Auto: whether the field is auto-populated (true = excluded from insert input)
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * t.string() // required string field
13
+ * t.string().optional() // optional string field
14
+ * t.string().default('hello') // string with default value
15
+ * t.timestamp().auto() // auto-populated timestamp
16
+ * ```
17
+ */
18
+ declare class FieldBuilder<Kind extends FieldKind = FieldKind, Req extends boolean = true, Auto extends boolean = false> {
19
+ protected readonly _kind: Kind;
20
+ protected readonly _required: boolean;
21
+ protected readonly _defaultValue: unknown;
22
+ protected readonly _auto: boolean;
23
+ protected readonly _mergeStrategy: FieldMergeStrategy | null;
24
+ constructor(kind: Kind, required?: Req, defaultValue?: unknown, auto?: Auto, mergeStrategy?: FieldMergeStrategy | null);
25
+ /** Mark this field as optional (not required on insert) */
26
+ optional(): FieldBuilder<Kind, false, Auto>;
27
+ /** Set a default value for this field. Implicitly makes the field optional. */
28
+ default(value: unknown): FieldBuilder<Kind, false, Auto>;
29
+ /** Mark this field as auto-populated (e.g., createdAt timestamps). Developers cannot set auto fields. */
30
+ auto(): FieldBuilder<Kind, false, true>;
31
+ /**
32
+ * Declare a merge strategy for this field.
33
+ * Controls how concurrent modifications are resolved during sync.
34
+ *
35
+ * @param strategy - The merge strategy to use:
36
+ * - `'lww'`: Last-write-wins (default for scalar fields)
37
+ * - `'counter'`: Sum of deltas from base (for numbers)
38
+ * - `'max'`: Keep the maximum value (for numbers/timestamps)
39
+ * - `'min'`: Keep the minimum value (for numbers/timestamps)
40
+ * - `'union'`: Set-union merge (default for arrays)
41
+ * - `'append-only'`: Concatenate additions (for arrays)
42
+ * - `'server-authoritative'`: Always prefer the remote/server value
43
+ */
44
+ merge(strategy: FieldMergeStrategy): FieldBuilder<Kind, Req, Auto>;
45
+ /** @internal Build the final FieldDescriptor. Used by defineSchema(). */
46
+ _build(): FieldDescriptor;
47
+ }
48
+ /**
49
+ * Field builder for enum fields with constrained string values.
50
+ * Preserves the literal enum tuple type for inference.
51
+ */
52
+ declare class EnumFieldBuilder<Values extends readonly string[] = readonly string[], Req extends boolean = true, Auto extends boolean = false> extends FieldBuilder<'enum', Req, Auto> {
53
+ private readonly _enumValues;
54
+ private readonly _transitions;
55
+ constructor(values: Values, required?: Req, defaultValue?: unknown, auto?: Auto, mergeStrategy?: FieldMergeStrategy | null, transitions?: TransitionMap | null);
56
+ optional(): EnumFieldBuilder<Values, false, Auto>;
57
+ default(value: Values[number]): EnumFieldBuilder<Values, false, Auto>;
58
+ auto(): EnumFieldBuilder<Values, false, true>;
59
+ merge(strategy: FieldMergeStrategy): EnumFieldBuilder<Values, Req, Auto>;
60
+ /**
61
+ * Declare allowed state transitions for this enum field.
62
+ * Enables state machine validation during mutations and merges.
63
+ *
64
+ * @param map - Map of state to allowed next states
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * t.enum(['draft', 'pending', 'confirmed', 'cancelled']).transitions({
69
+ * draft: ['pending', 'cancelled'],
70
+ * pending: ['confirmed', 'cancelled'],
71
+ * confirmed: [],
72
+ * cancelled: [],
73
+ * })
74
+ * ```
75
+ */
76
+ transitions(map: Record<Values[number], Values[number][]>): EnumFieldBuilder<Values, Req, Auto>;
77
+ _build(): FieldDescriptor;
78
+ }
79
+ /**
80
+ * Field builder for array fields with a typed item kind.
81
+ * Preserves the item kind type parameter for inference.
82
+ */
83
+ declare class ArrayFieldBuilder<ItemKind extends FieldKind = FieldKind, Req extends boolean = true, Auto extends boolean = false> extends FieldBuilder<'array', Req, Auto> {
84
+ private readonly _itemKind;
85
+ constructor(itemBuilder: FieldBuilder<ItemKind>, required?: Req, defaultValue?: unknown, auto?: Auto, mergeStrategy?: FieldMergeStrategy | null);
86
+ optional(): ArrayFieldBuilder<ItemKind, false, Auto>;
87
+ default(value: unknown[]): ArrayFieldBuilder<ItemKind, false, Auto>;
88
+ auto(): ArrayFieldBuilder<ItemKind, false, true>;
89
+ merge(strategy: FieldMergeStrategy): ArrayFieldBuilder<ItemKind, Req, Auto>;
90
+ _build(): FieldDescriptor;
91
+ }
92
+ /**
93
+ * Type builder namespace. The developer's primary interface for defining field types.
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * import { t } from '@korajs/core'
98
+ *
99
+ * const fields = {
100
+ * title: t.string(),
101
+ * count: t.number(),
102
+ * active: t.boolean().default(true),
103
+ * notes: t.richtext(),
104
+ * tags: t.array(t.string()).default([]),
105
+ * priority: t.enum(['low', 'medium', 'high']).default('medium'),
106
+ * createdAt: t.timestamp().auto(),
107
+ * }
108
+ * ```
109
+ */
110
+ declare const t: {
111
+ string(): FieldBuilder<"string", true, false>;
112
+ number(): FieldBuilder<"number", true, false>;
113
+ boolean(): FieldBuilder<"boolean", true, false>;
114
+ timestamp(): FieldBuilder<"timestamp", true, false>;
115
+ richtext(): FieldBuilder<"richtext", true, false>;
116
+ enum<const V extends readonly string[]>(values: V): EnumFieldBuilder<V, true, false>;
117
+ array<K extends FieldKind>(itemBuilder: FieldBuilder<K>): ArrayFieldBuilder<K, true, false>;
118
+ };
119
+
120
+ /**
121
+ * A single migration step describing a schema change.
122
+ */
123
+ type MigrationStep = {
124
+ type: 'addField';
125
+ collection: string;
126
+ field: string;
127
+ descriptor: FieldDescriptor;
128
+ } | {
129
+ type: 'removeField';
130
+ collection: string;
131
+ field: string;
132
+ descriptor?: FieldDescriptor;
133
+ } | {
134
+ type: 'renameField';
135
+ collection: string;
136
+ from: string;
137
+ to: string;
138
+ } | {
139
+ type: 'addIndex';
140
+ collection: string;
141
+ field: string;
142
+ } | {
143
+ type: 'removeIndex';
144
+ collection: string;
145
+ field: string;
146
+ } | {
147
+ type: 'backfill';
148
+ collection: string;
149
+ transform: (record: Record<string, unknown>) => Record<string, unknown>;
150
+ /** Reverse transform for rollback. If not provided, backfill is not safely reversible. */
151
+ reverseTransform?: (record: Record<string, unknown>) => Record<string, unknown>;
152
+ };
153
+ /**
154
+ * A completed migration definition containing ordered steps and optional rollback steps.
155
+ */
156
+ interface MigrationDefinition {
157
+ readonly steps: readonly MigrationStep[];
158
+ /** Explicitly defined rollback steps. If undefined, inverse steps are auto-generated. */
159
+ readonly rollbackSteps: readonly MigrationStep[] | undefined;
160
+ /** Whether this migration can be safely rolled back. */
161
+ readonly safelyReversible: boolean;
162
+ }
163
+ /**
164
+ * Builder for defining rollback steps explicitly.
165
+ * Used with the `.down()` method on MigrationBuilder.
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * migrate()
170
+ * .addField('todos', 'priority', t.enum(['low', 'medium', 'high']).default('medium'))
171
+ * .addIndex('todos', 'priority')
172
+ * .down((rollback) => {
173
+ * rollback
174
+ * .removeIndex('todos', 'priority')
175
+ * .removeField('todos', 'priority')
176
+ * })
177
+ * ```
178
+ */
179
+ declare class RollbackBuilder {
180
+ private _steps;
181
+ /**
182
+ * Add a field during rollback (to reverse a removeField).
183
+ */
184
+ addField(collection: string, field: string, builder: FieldBuilder): RollbackBuilder;
185
+ /**
186
+ * Remove a field during rollback (to reverse an addField).
187
+ */
188
+ removeField(collection: string, field: string): RollbackBuilder;
189
+ /**
190
+ * Rename a field during rollback (to reverse a renameField).
191
+ */
192
+ renameField(collection: string, from: string, to: string): RollbackBuilder;
193
+ /**
194
+ * Add an index during rollback (to reverse a removeIndex).
195
+ */
196
+ addIndex(collection: string, field: string): RollbackBuilder;
197
+ /**
198
+ * Remove an index during rollback (to reverse an addIndex).
199
+ */
200
+ removeIndex(collection: string, field: string): RollbackBuilder;
201
+ /**
202
+ * Run a backfill during rollback (to reverse a previous backfill).
203
+ */
204
+ backfill(collection: string, transform: (record: Record<string, unknown>) => Record<string, unknown>): RollbackBuilder;
205
+ /**
206
+ * Get the accumulated rollback steps.
207
+ */
208
+ _getSteps(): readonly MigrationStep[];
209
+ }
210
+ /**
211
+ * Fluent builder for defining schema migrations.
212
+ *
213
+ * Each method returns a new builder instance (immutable).
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * migrate()
218
+ * .addField('products', 'taxInclusive', t.boolean().default(false))
219
+ * .renameField('products', 'cost', 'costPrice')
220
+ * .backfill('products', (record) => ({
221
+ * taxInclusive: record.taxRate > 0,
222
+ * }))
223
+ * ```
224
+ */
225
+ declare class MigrationBuilder implements MigrationDefinition {
226
+ readonly steps: readonly MigrationStep[];
227
+ readonly rollbackSteps: readonly MigrationStep[] | undefined;
228
+ readonly safelyReversible: boolean;
229
+ constructor(steps?: readonly MigrationStep[], rollbackSteps?: readonly MigrationStep[], safelyReversible?: boolean);
230
+ /**
231
+ * Add a new field to a collection.
232
+ * The field builder provides the type and default value.
233
+ */
234
+ addField(collection: string, field: string, builder: FieldBuilder): MigrationBuilder;
235
+ /**
236
+ * Remove a field from a collection.
237
+ * Optionally accepts a field builder to preserve the descriptor for rollback.
238
+ * Without the descriptor, rollback of this step requires a custom `.down()`.
239
+ *
240
+ * @param collection - The collection name
241
+ * @param field - The field name to remove
242
+ * @param builder - Optional field builder preserving the type info for rollback
243
+ */
244
+ removeField(collection: string, field: string, builder?: FieldBuilder): MigrationBuilder;
245
+ /**
246
+ * Rename a field in a collection.
247
+ * Implemented as ALTER TABLE RENAME COLUMN (SQLite 3.25+).
248
+ */
249
+ renameField(collection: string, from: string, to: string): MigrationBuilder;
250
+ /**
251
+ * Add an index on a field.
252
+ */
253
+ addIndex(collection: string, field: string): MigrationBuilder;
254
+ /**
255
+ * Remove an index on a field.
256
+ */
257
+ removeIndex(collection: string, field: string): MigrationBuilder;
258
+ /**
259
+ * Backfill records in a collection using a transform function.
260
+ * The transform receives each record and returns the fields to update.
261
+ * Runs after structural changes (addField, renameField, etc.).
262
+ *
263
+ * @param collection - The collection name
264
+ * @param transform - Forward transform function
265
+ * @param reverseTransform - Optional reverse transform for rollback support
266
+ */
267
+ backfill(collection: string, transform: (record: Record<string, unknown>) => Record<string, unknown>, reverseTransform?: (record: Record<string, unknown>) => Record<string, unknown>): MigrationBuilder;
268
+ /**
269
+ * Define explicit rollback steps for this migration.
270
+ * If not called, inverse steps are auto-generated from forward steps.
271
+ *
272
+ * @param fn - Function that receives a RollbackBuilder to define rollback steps
273
+ * @returns A new MigrationBuilder with the rollback steps attached
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * migrate()
278
+ * .addField('todos', 'priority', t.enum(['low', 'medium', 'high']).default('medium'))
279
+ * .addIndex('todos', 'priority')
280
+ * .down((rollback) => {
281
+ * rollback
282
+ * .removeIndex('todos', 'priority')
283
+ * .removeField('todos', 'priority')
284
+ * })
285
+ * ```
286
+ */
287
+ down(fn: (rollback: RollbackBuilder) => void): MigrationBuilder;
288
+ /**
289
+ * Determine if a migration is safely reversible based on its steps.
290
+ * A migration is not safely reversible if:
291
+ * - It has a backfill step without a reverseTransform and no explicit rollback
292
+ * - It has a removeField without a stored descriptor and no explicit rollback
293
+ */
294
+ private _computeSafelyReversible;
295
+ }
296
+ /**
297
+ * Start building a migration.
298
+ *
299
+ * @returns A new MigrationBuilder
300
+ *
301
+ * @example
302
+ * ```typescript
303
+ * import { migrate, t } from '@korajs/core'
304
+ *
305
+ * const migration = migrate()
306
+ * .addField('products', 'taxInclusive', t.boolean().default(false))
307
+ * .renameField('products', 'cost', 'costPrice')
308
+ * ```
309
+ */
310
+ declare function migrate(): MigrationBuilder;
311
+
312
+ /**
313
+ * Hybrid Logical Clock timestamp.
314
+ * Provides a total order that respects causality without requiring synchronized clocks.
315
+ */
316
+ interface HLCTimestamp {
317
+ /** Physical wall-clock time in milliseconds */
318
+ wallTime: number;
319
+ /** Logical counter. Increments when wallTime hasn't changed since last event. */
320
+ logical: number;
321
+ /** Node ID for tie-breaking. Ensures total order even with identical wall+logical. */
322
+ nodeId: string;
323
+ }
324
+ /** The three mutation types an operation can represent */
325
+ type OperationType = 'insert' | 'update' | 'delete';
326
+ /** Supported atomic operation types for intent-preserving field updates */
327
+ type AtomicOpType = 'increment' | 'max' | 'min' | 'append' | 'remove';
328
+ /**
329
+ * Atomic operation stored in an Operation's atomicOps field.
330
+ * This is the serializable form that persists in the operation log.
331
+ */
332
+ interface AtomicOp {
333
+ readonly type: AtomicOpType;
334
+ readonly value: unknown;
335
+ }
336
+ /**
337
+ * The atomic unit of the entire system. Every mutation produces an Operation.
338
+ * Operations are IMMUTABLE and CONTENT-ADDRESSED.
339
+ */
340
+ interface Operation {
341
+ /** SHA-256 hash of (type + collection + recordId + data + timestamp + nodeId). Content-addressed. */
342
+ id: string;
343
+ /** UUID v7 of the originating device. Time-sortable. */
344
+ nodeId: string;
345
+ /** What happened */
346
+ type: OperationType;
347
+ /** Which collection (from schema) */
348
+ collection: string;
349
+ /** ID of the affected record. UUID v7 for inserts, existing ID for update/delete. */
350
+ recordId: string;
351
+ /** Field values. null for delete. For updates, contains ONLY changed fields. */
352
+ data: Record<string, unknown> | null;
353
+ /** For updates: previous values of changed fields (enables 3-way merge). null for insert/delete. */
354
+ previousData: Record<string, unknown> | null;
355
+ /** Hybrid Logical Clock timestamp. Used for causal ordering. */
356
+ timestamp: HLCTimestamp;
357
+ /** Monotonically increasing per node. Used in version vectors. */
358
+ sequenceNumber: number;
359
+ /** Operation IDs this operation causally depends on (direct parents in the DAG). */
360
+ causalDeps: string[];
361
+ /** Schema version at time of creation. Used for migration transforms. */
362
+ schemaVersion: number;
363
+ /** Atomic operation intents for fields in data (e.g., increment, max). Present only when atomic ops were used. */
364
+ atomicOps?: Record<string, AtomicOp>;
365
+ /** Groups this operation with others in an atomic transaction. Not part of the content hash. */
366
+ transactionId?: string;
367
+ /** Human-readable name for the mutation group (e.g., 'complete-sale'). For DevTools display. */
368
+ mutationName?: string;
369
+ }
370
+ /**
371
+ * Input for creating an operation (before id and timestamp are assigned).
372
+ */
373
+ interface OperationInput {
374
+ nodeId: string;
375
+ type: OperationType;
376
+ collection: string;
377
+ recordId: string;
378
+ data: Record<string, unknown> | null;
379
+ previousData: Record<string, unknown> | null;
380
+ sequenceNumber: number;
381
+ causalDeps: string[];
382
+ schemaVersion: number;
383
+ /** Atomic operation intents for fields in data (e.g., increment, max). */
384
+ atomicOps?: Record<string, AtomicOp>;
385
+ /** Groups this operation with others in an atomic transaction. Not part of the content hash. */
386
+ transactionId?: string;
387
+ /** Human-readable name for the mutation group (e.g., 'complete-sale'). For DevTools display. */
388
+ mutationName?: string;
389
+ }
390
+ /** Version vector: maps nodeId to the max sequence number seen from that node */
391
+ type VersionVector = Map<string, number>;
392
+ /** Field kinds supported by the schema system */
393
+ type FieldKind = 'string' | 'number' | 'boolean' | 'timestamp' | 'richtext' | 'enum' | 'array';
394
+ /**
395
+ * Comprehensive sync diagnostics snapshot.
396
+ * Provides connection, latency, throughput, queue, and error metrics
397
+ * for monitoring and DevTools integration.
398
+ */
399
+ interface SyncDiagnosticsSnapshot {
400
+ /** Current developer-facing sync status */
401
+ status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error';
402
+ /** Timestamp when the current connection was established, or null if disconnected */
403
+ connectedAt: number | null;
404
+ /** Timestamp when the last disconnection occurred, or null if never disconnected */
405
+ disconnectedAt: number | null;
406
+ /** Number of reconnection attempts since last successful connection */
407
+ reconnectAttempts: number;
408
+ /** Current round-trip time in milliseconds */
409
+ rttMs: number;
410
+ /** Median (50th percentile) RTT over the sliding window */
411
+ rttP50Ms: number;
412
+ /** 95th percentile RTT over the sliding window */
413
+ rttP95Ms: number;
414
+ /** 99th percentile RTT over the sliding window */
415
+ rttP99Ms: number;
416
+ /** Total operations sent during this session */
417
+ operationsSent: number;
418
+ /** Total operations received during this session */
419
+ operationsReceived: number;
420
+ /** Total bytes sent during this session */
421
+ bytesSent: number;
422
+ /** Total bytes received during this session */
423
+ bytesReceived: number;
424
+ /** Number of operations waiting to be sent */
425
+ pendingOperations: number;
426
+ /** Estimated bytes in the outbound queue */
427
+ outboundQueueSize: number;
428
+ /** Timestamp of the last successful sync, or null if never synced */
429
+ lastSyncedAt: number | null;
430
+ /** Duration of the last complete sync cycle in ms, or null if no cycle completed */
431
+ syncDuration: number | null;
432
+ /** Whether the initial sync (full delta exchange) has completed */
433
+ initialSyncComplete: boolean;
434
+ /** Progress of the initial sync as a 0-1 ratio */
435
+ initialSyncProgress: number;
436
+ /** Description of the last error, or null if no error occurred */
437
+ lastError: string | null;
438
+ /** Total number of errors during this session */
439
+ errorCount: number;
440
+ /** Assessed connection quality level */
441
+ quality: 'excellent' | 'good' | 'fair' | 'poor' | 'offline';
442
+ /** Estimated effective bandwidth in bytes per second, or null if not yet measured */
443
+ effectiveBandwidth: number | null;
444
+ }
445
+ /** Built-in field merge strategies that can be declared in the schema. */
446
+ type FieldMergeStrategy = 'lww' | 'counter' | 'max' | 'min' | 'union' | 'append-only' | 'server-authoritative';
447
+ /** Map of state to allowed next states for state machine constraints */
448
+ type TransitionMap = Record<string, string[]>;
449
+ /**
450
+ * A state machine constraint extracted from the schema.
451
+ * Used by merge and validation to enforce valid state transitions.
452
+ */
453
+ interface StateMachineConstraint {
454
+ /** The enum field this constraint controls */
455
+ field: string;
456
+ /** The collection this constraint applies to */
457
+ collection: string;
458
+ /** Map of state to allowed next states */
459
+ transitions: TransitionMap;
460
+ }
461
+ /**
462
+ * Result of validating a state transition.
463
+ */
464
+ interface TransitionValidationResult {
465
+ /** Whether the transition is allowed */
466
+ valid: boolean;
467
+ /** The source state */
468
+ from: string;
469
+ /** The target state */
470
+ to: string;
471
+ /** The field being transitioned */
472
+ field: string;
473
+ /** The collection containing the field */
474
+ collection: string;
475
+ /** All allowed target states from the source state */
476
+ allowedTargets: string[];
477
+ }
478
+ /**
479
+ * Descriptor produced by the type builder (t.string(), t.number(), etc.).
480
+ * Represents a fully configured field definition.
481
+ */
482
+ interface FieldDescriptor {
483
+ kind: FieldKind;
484
+ required: boolean;
485
+ defaultValue: unknown;
486
+ auto: boolean;
487
+ enumValues: readonly string[] | null;
488
+ itemKind: FieldKind | null;
489
+ /** Declared merge strategy. Defaults to kind-appropriate strategy when undefined. */
490
+ mergeStrategy: FieldMergeStrategy | null;
491
+ /** State machine transition map for enum fields. Null if no transitions declared. */
492
+ transitions: TransitionMap | null;
493
+ }
494
+ /**
495
+ * Defines a state machine on an enum field, constraining valid state transitions.
496
+ *
497
+ * When a state machine is defined on a collection, mutations and merges
498
+ * enforce that the controlled field only moves along declared transitions.
499
+ *
500
+ * @example
501
+ * ```typescript
502
+ * stateMachine: {
503
+ * field: 'status',
504
+ * transitions: {
505
+ * draft: ['pending', 'cancelled'],
506
+ * pending: ['confirmed', 'cancelled'],
507
+ * confirmed: ['shipped'],
508
+ * shipped: ['delivered'],
509
+ * delivered: [],
510
+ * cancelled: [],
511
+ * },
512
+ * onInvalidTransition: 'reject',
513
+ * }
514
+ * ```
515
+ */
516
+ interface StateMachineDefinition {
517
+ /** The enum field this state machine controls */
518
+ field: string;
519
+ /** Map of state to allowed next states */
520
+ transitions: Record<string, string[]>;
521
+ /** What to do when an invalid transition is attempted */
522
+ onInvalidTransition: 'reject' | 'last-valid-state';
523
+ }
524
+ /**
525
+ * Definition of a collection within the schema.
526
+ */
527
+ interface CollectionDefinition {
528
+ fields: Record<string, FieldDescriptor>;
529
+ indexes: string[];
530
+ constraints: Constraint[];
531
+ resolvers: Record<string, CustomResolver>;
532
+ /** Scope fields for sync filtering. Only records matching the client's scope values are synced. */
533
+ scope: string[];
534
+ /** Optional state machine constraining transitions on an enum field */
535
+ stateMachine?: StateMachineDefinition;
536
+ }
537
+ /** Custom resolver function for tier 3 merge resolution */
538
+ type CustomResolver = (local: unknown, remote: unknown, base: unknown) => unknown;
539
+ /**
540
+ * Constraint for tier 2 conflict resolution.
541
+ */
542
+ interface Constraint {
543
+ type: 'unique' | 'capacity' | 'referential';
544
+ fields: string[];
545
+ where?: Record<string, unknown>;
546
+ onConflict: 'first-write-wins' | 'last-write-wins' | 'priority-field' | 'server-decides' | 'custom';
547
+ priorityField?: string;
548
+ resolve?: (local: unknown, remote: unknown, base: unknown) => unknown;
549
+ }
550
+ /** Relation type between collections */
551
+ type RelationType = 'one-to-one' | 'one-to-many' | 'many-to-one' | 'many-to-many';
552
+ /** On-delete behavior for relations */
553
+ type OnDeleteAction = 'cascade' | 'set-null' | 'restrict' | 'no-action';
554
+ /**
555
+ * Definition of a relation between two collections.
556
+ */
557
+ interface RelationDefinition {
558
+ from: string;
559
+ to: string;
560
+ type: RelationType;
561
+ field: string;
562
+ onDelete: OnDeleteAction;
563
+ }
564
+ /**
565
+ * The complete schema definition produced by defineSchema().
566
+ */
567
+ interface SchemaDefinition {
568
+ version: number;
569
+ collections: Record<string, CollectionDefinition>;
570
+ relations: Record<string, RelationDefinition>;
571
+ /** Schema migrations keyed by target version. */
572
+ migrations: Record<number, MigrationDefinition>;
573
+ }
574
+ /**
575
+ * Merge strategies available for auto-merge and constraints.
576
+ */
577
+ declare const MERGE_STRATEGIES: readonly ["auto-merge", "lww", "first-write-wins", "server-decides", "custom"];
578
+ type MergeStrategy = (typeof MERGE_STRATEGIES)[number];
579
+ /**
580
+ * Connection quality levels for adaptive sync.
581
+ */
582
+ declare const CONNECTION_QUALITIES: readonly ["excellent", "good", "fair", "poor", "offline"];
583
+ type ConnectionQuality = (typeof CONNECTION_QUALITIES)[number];
584
+ /**
585
+ * Injectable time source for deterministic testing of clocks.
586
+ */
587
+ interface TimeSource {
588
+ now(): number;
589
+ }
590
+ /**
591
+ * Injectable random source for deterministic testing of UUID generation.
592
+ */
593
+ interface RandomSource {
594
+ getRandomValues<T extends ArrayBufferView>(array: T): T;
595
+ }
596
+ /**
597
+ * Configuration for an offline-safe sequence.
598
+ *
599
+ * Sequences produce monotonically increasing, collision-free identifiers
600
+ * that work across offline devices. Each device maintains its own counter
601
+ * scoped by (name, scope, nodeId).
602
+ *
603
+ * Format tokens available:
604
+ * - `{date}` → YYYYMMDD
605
+ * - `{node4}` → first 4 chars of nodeId
606
+ * - `{node8}` → first 8 chars of nodeId
607
+ * - `{seq}` → zero-padded counter (4 digits)
608
+ * - `{seq:N}` → zero-padded counter with N digits
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * await app.sequences.next('receipt', {
613
+ * scope: storeId,
614
+ * format: 'S-{date}-{node4}-{seq}',
615
+ * })
616
+ * // → "S-20260508-a1b2-0042"
617
+ * ```
618
+ */
619
+ interface SequenceConfig {
620
+ /** Namespace within the sequence (e.g., a storeId or orgId). Defaults to ''. */
621
+ scope?: string;
622
+ /** Format template. Defaults to `{name}-{seq:4}`. */
623
+ format?: string;
624
+ /** Starting counter value. Defaults to 1. */
625
+ startAt?: number;
626
+ }
627
+
628
+ /**
629
+ * Hybrid Logical Clock implementation based on Kulkarni et al.
630
+ *
631
+ * Provides a total order that respects causality without requiring synchronized clocks.
632
+ * Each call to now() returns a timestamp strictly greater than the previous one.
633
+ *
634
+ * @example
635
+ * ```typescript
636
+ * const clock = new HybridLogicalClock('node-1')
637
+ * const ts1 = clock.now()
638
+ * const ts2 = clock.now()
639
+ * // HybridLogicalClock.compare(ts1, ts2) < 0 (ts1 is earlier)
640
+ * ```
641
+ */
642
+ declare class HybridLogicalClock {
643
+ private readonly nodeId;
644
+ private readonly timeSource;
645
+ private readonly onDriftWarning?;
646
+ private wallTime;
647
+ private logical;
648
+ constructor(nodeId: string, timeSource?: TimeSource, onDriftWarning?: ((driftMs: number) => void) | undefined);
649
+ /**
650
+ * Generate a new timestamp for a local event.
651
+ * Guarantees monotonicity: each call returns a timestamp strictly greater than the previous.
652
+ *
653
+ * @throws {ClockDriftError} If physical time is more than 5 minutes behind the HLC wallTime
654
+ */
655
+ now(): HLCTimestamp;
656
+ /**
657
+ * Update clock on receiving a remote timestamp.
658
+ * Merges the remote clock state with the local state to maintain causal ordering.
659
+ *
660
+ * @throws {ClockDriftError} If physical time is more than 5 minutes behind the resulting wallTime
661
+ */
662
+ receive(remote: HLCTimestamp): HLCTimestamp;
663
+ /**
664
+ * Compare two timestamps. Returns negative if a < b, positive if a > b, zero if equal.
665
+ * Total order: wallTime first, then logical, then nodeId (lexicographic).
666
+ */
667
+ static compare(a: HLCTimestamp, b: HLCTimestamp): number;
668
+ /**
669
+ * Serialize an HLC timestamp to a string that sorts lexicographically.
670
+ * Format: zero-padded wallTime:logical:nodeId
671
+ */
672
+ static serialize(ts: HLCTimestamp): string;
673
+ /**
674
+ * Deserialize an HLC timestamp from its serialized string form.
675
+ */
676
+ static deserialize(s: string): HLCTimestamp;
677
+ private checkDrift;
678
+ }
679
+
680
+ /**
681
+ * Creates an immutable, content-addressed Operation from the given parameters.
682
+ * The operation is deep-frozen after creation — it cannot be modified.
683
+ *
684
+ * @param input - The operation parameters (without id, which is computed)
685
+ * @param clock - The HLC clock to generate the timestamp
686
+ * @returns A frozen Operation with a content-addressed id
687
+ *
688
+ * @example
689
+ * ```typescript
690
+ * const op = await createOperation({
691
+ * nodeId: 'device-1',
692
+ * type: 'insert',
693
+ * collection: 'todos',
694
+ * recordId: 'rec-1',
695
+ * data: { title: 'Ship it' },
696
+ * previousData: null,
697
+ * sequenceNumber: 1,
698
+ * causalDeps: [],
699
+ * schemaVersion: 1,
700
+ * }, clock)
701
+ * ```
702
+ */
703
+ declare function createOperation(input: OperationInput, clock: HybridLogicalClock): Promise<Operation>;
704
+ /**
705
+ * Validates operation input parameters. Throws OperationError with
706
+ * contextual information on validation failure.
707
+ */
708
+ declare function validateOperationParams(input: OperationInput): void;
709
+ /**
710
+ * Verify the integrity of an operation by recomputing its content hash.
711
+ * Returns true if the id matches the recomputed hash.
712
+ */
713
+ declare function verifyOperationIntegrity(op: Operation): Promise<boolean>;
714
+ /**
715
+ * Type guard for Operation interface.
716
+ */
717
+ declare function isValidOperation(value: unknown): value is Operation;
718
+
719
+ /**
720
+ * Trace of a merge decision. Records all inputs and outputs for debugging and DevTools.
721
+ */
722
+ interface MergeTrace {
723
+ operationA: Operation;
724
+ operationB: Operation;
725
+ field: string;
726
+ strategy: string;
727
+ inputA: unknown;
728
+ inputB: unknown;
729
+ base: unknown | null;
730
+ output: unknown;
731
+ tier: 1 | 2 | 3;
732
+ constraintViolated: string | null;
733
+ duration: number;
734
+ }
735
+ /**
736
+ * All events emitted by the Kora framework.
737
+ * These are consumed by DevTools and can be observed by the developer.
738
+ */
739
+ type KoraEvent = {
740
+ type: 'operation:created';
741
+ operation: Operation;
742
+ } | {
743
+ type: 'operation:applied';
744
+ operation: Operation;
745
+ duration: number;
746
+ } | {
747
+ type: 'merge:started';
748
+ operationA: Operation;
749
+ operationB: Operation;
750
+ } | {
751
+ type: 'merge:completed';
752
+ trace: MergeTrace;
753
+ } | {
754
+ type: 'merge:conflict';
755
+ trace: MergeTrace;
756
+ } | {
757
+ type: 'constraint:violated';
758
+ constraint: string;
759
+ trace: MergeTrace;
760
+ } | {
761
+ type: 'sync:connected';
762
+ nodeId: string;
763
+ } | {
764
+ type: 'sync:disconnected';
765
+ reason: string;
766
+ } | {
767
+ type: 'sync:auth-failed';
768
+ reason: string;
769
+ } | {
770
+ type: 'sync:sent';
771
+ operations: Operation[];
772
+ batchSize: number;
773
+ } | {
774
+ type: 'sync:received';
775
+ operations: Operation[];
776
+ batchSize: number;
777
+ } | {
778
+ type: 'sync:acknowledged';
779
+ sequenceNumber: number;
780
+ } | {
781
+ type: 'query:subscribed';
782
+ queryId: string;
783
+ collection: string;
784
+ } | {
785
+ type: 'query:invalidated';
786
+ queryId: string;
787
+ trigger: Operation;
788
+ } | {
789
+ type: 'query:executed';
790
+ queryId: string;
791
+ duration: number;
792
+ resultCount: number;
793
+ } | {
794
+ type: 'connection:quality';
795
+ quality: ConnectionQuality;
796
+ } | {
797
+ type: 'sync:diagnostics';
798
+ diagnostics: SyncDiagnosticsSnapshot;
799
+ } | {
800
+ type: 'sync:bandwidth';
801
+ bytesPerSecond: number;
802
+ direction: 'in' | 'out';
803
+ } | {
804
+ type: 'sync:initial-sync-progress';
805
+ progress: number;
806
+ totalBatches: number;
807
+ receivedBatches: number;
808
+ } | {
809
+ type: 'awareness:updated';
810
+ states: Map<number, unknown>;
811
+ } | {
812
+ type: 'state-machine:transition';
813
+ collection: string;
814
+ recordId: string;
815
+ from: string;
816
+ to: string;
817
+ valid: boolean;
818
+ } | {
819
+ type: 'state-machine:rejected';
820
+ collection: string;
821
+ recordId: string;
822
+ from: string;
823
+ to: string;
824
+ allowed: string[];
825
+ };
826
+ /** Extract the event type string union from KoraEvent */
827
+ type KoraEventType = KoraEvent['type'];
828
+ /** Extract a specific event by its type */
829
+ type KoraEventByType<T extends KoraEventType> = Extract<KoraEvent, {
830
+ type: T;
831
+ }>;
832
+ /** Listener function for a specific event type */
833
+ type KoraEventListener<T extends KoraEventType> = (event: KoraEventByType<T>) => void;
834
+ /**
835
+ * Event emitter interface for the Kora framework.
836
+ * All packages that emit events must implement this interface.
837
+ */
838
+ interface KoraEventEmitter {
839
+ on<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): () => void;
840
+ off<T extends KoraEventType>(type: T, listener: KoraEventListener<T>): void;
841
+ emit<T extends KoraEventType>(event: KoraEventByType<T>): void;
842
+ }
843
+
844
+ export { type AtomicOpType as A, RollbackBuilder as B, type CustomResolver as C, type SequenceConfig as D, EnumFieldBuilder as E, FieldBuilder as F, type StateMachineDefinition as G, type HLCTimestamp as H, type SyncDiagnosticsSnapshot as I, type TimeSource as J, type KoraEvent as K, createOperation as L, type MigrationDefinition as M, isValidOperation as N, type OperationType as O, migrate as P, t as Q, type RandomSource as R, type SchemaDefinition as S, type TransitionMap as T, verifyOperationIntegrity as U, type VersionVector as V, validateOperationParams as W, type AtomicOp as a, type CollectionDefinition as b, type RelationDefinition as c, ArrayFieldBuilder as d, type Operation as e, type MigrationStep as f, type StateMachineConstraint as g, type TransitionValidationResult as h, CONNECTION_QUALITIES as i, type ConnectionQuality as j, type Constraint as k, type FieldDescriptor as l, type FieldKind as m, type FieldMergeStrategy as n, HybridLogicalClock as o, type KoraEventByType as p, type KoraEventEmitter as q, type KoraEventListener as r, type KoraEventType as s, MERGE_STRATEGIES as t, type MergeStrategy as u, type MergeTrace as v, MigrationBuilder as w, type OnDeleteAction as x, type OperationInput as y, type RelationType as z };