@korajs/core 1.0.0-beta.0 → 1.0.0-beta.10

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.
@@ -1,9 +1,10 @@
1
- import { p as KoraEventEmitter } from '../events-BynBOsO3.cjs';
2
- import { S as ScopeMap } from '../build-scope-map-BIeawJzC.cjs';
1
+ import { p as KoraEventEmitter } from '../events-3z0Tema3.cjs';
2
+ import { S as ScopeMap } from '../build-scope-map-B8dpCt6P.cjs';
3
3
 
4
4
  /** Sync status shape consumed by framework binding hooks (matches {@link SyncStatusInfo} in `@korajs/sync`). */
5
5
  interface KoraBindingSyncStatus {
6
- status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error';
6
+ status: 'connected' | 'reconnecting' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error';
7
+ reconnecting: boolean;
7
8
  pendingOperations: number;
8
9
  lastSyncedAt: number | null;
9
10
  lastSuccessfulPush: number | null;
@@ -72,6 +73,8 @@ interface AuthSyncBinding {
72
73
  * Separate from the user id (`sub`).
73
74
  */
74
75
  resolveNodeId?: () => Promise<string | undefined>;
76
+ /** Returns the authenticated user id used for local store namespacing. */
77
+ resolveUserId?: () => Promise<string | undefined>;
75
78
  /** Notifies when auth state changes so sync can refresh scope or reconnect. */
76
79
  subscribe?: (listener: () => void) => () => void;
77
80
  }
@@ -1,9 +1,10 @@
1
- import { p as KoraEventEmitter } from '../events-BynBOsO3.js';
2
- import { S as ScopeMap } from '../build-scope-map-DOf4JLTh.js';
1
+ import { p as KoraEventEmitter } from '../events-3z0Tema3.js';
2
+ import { S as ScopeMap } from '../build-scope-map-CDpIGOsf.js';
3
3
 
4
4
  /** Sync status shape consumed by framework binding hooks (matches {@link SyncStatusInfo} in `@korajs/sync`). */
5
5
  interface KoraBindingSyncStatus {
6
- status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error';
6
+ status: 'connected' | 'reconnecting' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error';
7
+ reconnecting: boolean;
7
8
  pendingOperations: number;
8
9
  lastSyncedAt: number | null;
9
10
  lastSuccessfulPush: number | null;
@@ -72,6 +73,8 @@ interface AuthSyncBinding {
72
73
  * Separate from the user id (`sub`).
73
74
  */
74
75
  resolveNodeId?: () => Promise<string | undefined>;
76
+ /** Returns the authenticated user id used for local store namespacing. */
77
+ resolveUserId?: () => Promise<string | undefined>;
75
78
  /** Notifies when auth state changes so sync can refresh scope or reconnect. */
76
79
  subscribe?: (listener: () => void) => () => void;
77
80
  }
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './events-BynBOsO3.cjs';
1
+ import { S as SchemaDefinition } from './events-3z0Tema3.cjs';
2
2
 
3
3
  /**
4
4
  * Per-collection scope map: `{ collectionName: { field: value, ... } }`
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './events-BynBOsO3.js';
1
+ import { S as SchemaDefinition } from './events-3z0Tema3.js';
2
2
 
3
3
  /**
4
4
  * Per-collection scope map: `{ collectionName: { field: value, ... } }`
@@ -89,6 +89,56 @@ declare class ArrayFieldBuilder<ItemKind extends FieldKind = FieldKind, Req exte
89
89
  merge(strategy: FieldMergeStrategy): ArrayFieldBuilder<ItemKind, Req, Auto>;
90
90
  _build(): FieldDescriptor;
91
91
  }
92
+ /**
93
+ * Field builder for structured object fields with a nested field schema.
94
+ *
95
+ * Each nested key merges by its own declared kind (scalars via LWW, nested
96
+ * arrays via add-wins, nested objects recursively), so two devices editing
97
+ * different keys of the same object offline both converge on reconnect instead
98
+ * of one clobbering the other.
99
+ */
100
+ declare class ObjectFieldBuilder<Req extends boolean = true, Auto extends boolean = false> extends FieldBuilder<'object', Req, Auto> {
101
+ private readonly _fields;
102
+ constructor(fields: Record<string, FieldBuilder>, required?: Req, defaultValue?: unknown, auto?: Auto, mergeStrategy?: FieldMergeStrategy | null);
103
+ optional(): ObjectFieldBuilder<false, Auto>;
104
+ default(value: Record<string, unknown>): ObjectFieldBuilder<false, Auto>;
105
+ auto(): ObjectFieldBuilder<false, true>;
106
+ merge(strategy: FieldMergeStrategy): ObjectFieldBuilder<Req, Auto>;
107
+ _build(): FieldDescriptor;
108
+ }
109
+ /**
110
+ * Field builder for dynamic-key JSON values. Carries a compile-time shape `T`
111
+ * for inference while merging structurally as a convergent CRDT: a plain-object
112
+ * value recurses as a map, an array merges add-wins, any other value is a scalar
113
+ * leaf under last-write-wins.
114
+ */
115
+ declare class JsonFieldBuilder<T = unknown, Req extends boolean = true, Auto extends boolean = false> extends FieldBuilder<'json', Req, Auto> {
116
+ optional(): JsonFieldBuilder<T, false, Auto>;
117
+ default(value: T): JsonFieldBuilder<T, false, Auto>;
118
+ auto(): JsonFieldBuilder<T, false, true>;
119
+ merge(strategy: FieldMergeStrategy): JsonFieldBuilder<T, Req, Auto>;
120
+ _build(): FieldDescriptor;
121
+ }
122
+ /**
123
+ * Field builder for secret fields (passwords, tokens, API keys).
124
+ *
125
+ * A secret field's value is never exposed in merge traces, DevTools, or logs
126
+ * (it is redacted at the point traces are built). Its at-rest protection is
127
+ * chosen with `.hashed()` (one-way, for passwords) or `.encrypted()` (reversible,
128
+ * for tokens); the default is `encrypted`.
129
+ */
130
+ declare class SecretFieldBuilder<Req extends boolean = true, Auto extends boolean = false> extends FieldBuilder<'secret', Req, Auto> {
131
+ private readonly _secretMode;
132
+ constructor(secretMode?: SecretMode, required?: Req, defaultValue?: unknown, auto?: Auto, mergeStrategy?: FieldMergeStrategy | null);
133
+ /** Store this secret as a one-way salted hash (passwords: verify, never read back). */
134
+ hashed(): SecretFieldBuilder<Req, Auto>;
135
+ /** Store this secret as reversible ciphertext (tokens/keys: decrypt to use). */
136
+ encrypted(): SecretFieldBuilder<Req, Auto>;
137
+ optional(): SecretFieldBuilder<false, Auto>;
138
+ auto(): SecretFieldBuilder<false, true>;
139
+ merge(strategy: FieldMergeStrategy): SecretFieldBuilder<Req, Auto>;
140
+ _build(): FieldDescriptor;
141
+ }
92
142
  /**
93
143
  * Type builder namespace. The developer's primary interface for defining field types.
94
144
  *
@@ -103,6 +153,8 @@ declare class ArrayFieldBuilder<ItemKind extends FieldKind = FieldKind, Req exte
103
153
  * notes: t.richtext(),
104
154
  * tags: t.array(t.string()).default([]),
105
155
  * priority: t.enum(['low', 'medium', 'high']).default('medium'),
156
+ * settings: t.object({ theme: t.string(), fontSize: t.number() }),
157
+ * metadata: t.json<{ source: string }>(),
106
158
  * createdAt: t.timestamp().auto(),
107
159
  * }
108
160
  * ```
@@ -115,6 +167,10 @@ declare const t: {
115
167
  richtext(): FieldBuilder<"richtext", true, false>;
116
168
  enum<const V extends readonly string[]>(values: V): EnumFieldBuilder<V, true, false>;
117
169
  array<K extends FieldKind>(itemBuilder: FieldBuilder<K>): ArrayFieldBuilder<K, true, false>;
170
+ object(fields: Record<string, FieldBuilder>): ObjectFieldBuilder<true, false>;
171
+ json<T = unknown>(): JsonFieldBuilder<T, true, false>;
172
+ blob(): FieldBuilder<"blob", true, false>;
173
+ secret(): SecretFieldBuilder<true, false>;
118
174
  };
119
175
 
120
176
  /**
@@ -390,7 +446,39 @@ interface OperationInput {
390
446
  /** Version vector: maps nodeId to the max sequence number seen from that node */
391
447
  type VersionVector = Map<string, number>;
392
448
  /** Field kinds supported by the schema system */
393
- type FieldKind = 'string' | 'number' | 'boolean' | 'timestamp' | 'richtext' | 'enum' | 'array';
449
+ type FieldKind = 'string' | 'number' | 'boolean' | 'timestamp' | 'richtext' | 'enum' | 'array' | 'object' | 'json' | 'blob' | 'secret';
450
+ /**
451
+ * How a `secret` field's value is protected at rest.
452
+ * - `hashed`: one-way (passwords). Stored as a salted hash; never read back.
453
+ * - `encrypted`: reversible (tokens, keys). Stored as ciphertext; decrypted to use.
454
+ */
455
+ type SecretMode = 'hashed' | 'encrypted';
456
+ /**
457
+ * A content-addressed reference to binary data stored out of band.
458
+ *
459
+ * A `blob` field never carries the bytes in the operation log; it carries this
460
+ * small reference. The bytes live in a content-addressed blob store, keyed by
461
+ * `hash`, so identical content is stored once and never re-synced to a peer that
462
+ * already has that hash.
463
+ */
464
+ interface BlobRef {
465
+ /** Hex-encoded SHA-256 hash of the bytes. The content address. */
466
+ hash: string;
467
+ /** Size of the bytes in bytes. */
468
+ size: number;
469
+ /** Optional MIME type (for example "image/png"). */
470
+ mimeType?: string;
471
+ /** Optional original filename. */
472
+ filename?: string;
473
+ /**
474
+ * Hex-encoded SHA-256 hash of this blob's manifest — the small chunk index a
475
+ * peer fetches first to learn how to pull the bytes out of band. Present when
476
+ * the blob was stored for transfer; absent for a bare reference created without
477
+ * chunking. Because it is a content address like `hash`, the manifest is fetched
478
+ * and integrity-verified over the same channel as the chunks.
479
+ */
480
+ manifestHash?: string;
481
+ }
394
482
  /**
395
483
  * Comprehensive sync diagnostics snapshot.
396
484
  * Provides connection, latency, throughput, queue, and error metrics
@@ -398,7 +486,7 @@ type FieldKind = 'string' | 'number' | 'boolean' | 'timestamp' | 'richtext' | 'e
398
486
  */
399
487
  interface SyncDiagnosticsSnapshot {
400
488
  /** Current developer-facing sync status */
401
- status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error';
489
+ status: 'connected' | 'reconnecting' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error';
402
490
  /** Timestamp when the current connection was established, or null if disconnected */
403
491
  connectedAt: number | null;
404
492
  /** Timestamp when the last disconnection occurred, or null if never disconnected */
@@ -490,6 +578,17 @@ interface FieldDescriptor {
490
578
  mergeStrategy: FieldMergeStrategy | null;
491
579
  /** State machine transition map for enum fields. Null if no transitions declared. */
492
580
  transitions: TransitionMap | null;
581
+ /**
582
+ * Nested field descriptors for `object` fields (each key merges by its own
583
+ * kind). Null for `json` fields (dynamic keys, resolved structurally) and for
584
+ * all non-structured kinds.
585
+ */
586
+ nestedFields?: Record<string, FieldDescriptor> | null;
587
+ /**
588
+ * At-rest protection mode for `secret` fields (`hashed` or `encrypted`).
589
+ * Null/absent for every other kind.
590
+ */
591
+ secretMode?: SecretMode | null;
493
592
  }
494
593
  /**
495
594
  * Defines a state machine on an enum field, constraining valid state transitions.
@@ -727,6 +826,33 @@ type KoraEvent = {
727
826
  code: string;
728
827
  message: string;
729
828
  retriable: boolean;
829
+ } | {
830
+ /**
831
+ * The server rejected one of THIS client's outbound operations before it
832
+ * became authoritative. The op has been diverted out of the pending sync
833
+ * queue into the durable rejected store (kept, not retried); the app can
834
+ * surface the reason and decide whether to roll back the optimistic local
835
+ * write or let the user edit and resubmit.
836
+ */
837
+ type: 'sync:operation-rejected';
838
+ operationId: string;
839
+ collection: string;
840
+ recordId: string;
841
+ code: string;
842
+ message: string;
843
+ retriable: boolean;
844
+ } | {
845
+ type: 'sync:delivery-gap';
846
+ expectedBase: number;
847
+ receivedBase: number;
848
+ currentWatermark: number;
849
+ messageId: string;
850
+ repeatCount: number;
851
+ } | {
852
+ type: 'sync:delivery-stalled';
853
+ sessionId: string;
854
+ watermark: number;
855
+ repeatCount: number;
730
856
  } | {
731
857
  type: 'query:subscribed';
732
858
  queryId: string;
@@ -781,6 +907,46 @@ type KoraEvent = {
781
907
  type: 'store:quota-exceeded';
782
908
  dbName: string;
783
909
  message: string;
910
+ } | {
911
+ /**
912
+ * OPFS persistence was unavailable, so the store fell back to a
913
+ * NON-PERSISTENT in-memory database. Anything written this session is lost
914
+ * on reload. This is emitted instead of failing silently so the condition
915
+ * is observable rather than a data-loss trap.
916
+ */
917
+ type: 'store:opfs-unavailable';
918
+ dbName: string;
919
+ /**
920
+ * Why OPFS could not be used. `lock-conflict` means another runtime on this
921
+ * origin already holds the OPFS pool for this database; `timeout` means the
922
+ * VFS install did not complete in time (common in headless CI); `unsupported`
923
+ * means the runtime has no usable OPFS.
924
+ */
925
+ reason: 'lock-conflict' | 'timeout' | 'unsupported';
926
+ message: string;
927
+ } | {
928
+ /**
929
+ * A preferred storage backend could not provide durable storage, so Kora
930
+ * promoted the app to another durable backend before user code observed the
931
+ * store. This is informational: data still survives reloads.
932
+ */
933
+ type: 'store:storage-fallback';
934
+ dbName: string;
935
+ from: 'opfs' | 'sqlite-wasm';
936
+ to: 'indexeddb';
937
+ reason: 'lock-conflict' | 'timeout' | 'unsupported';
938
+ message: string;
939
+ } | {
940
+ /**
941
+ * Another runtime on this origin was already using this database name, so
942
+ * this runtime attached to it as a follower and now SHARES that one
943
+ * database. That is intended for multiple tabs of the SAME app; it is a bug
944
+ * if these are logically separate apps, which should each use a distinct
945
+ * store name (`store: { name: '...' }`) to stay isolated.
946
+ */
947
+ type: 'store:db-name-collision';
948
+ dbName: string;
949
+ message: string;
784
950
  } | {
785
951
  type: 'replay:completed';
786
952
  targetOperationId: string;
@@ -805,4 +971,4 @@ interface KoraEventEmitter {
805
971
  emit<T extends KoraEventType>(event: KoraEventByType<T>): void;
806
972
  }
807
973
 
808
- export { type AtomicOpType as A, type SequenceConfig as B, type CustomResolver as C, type StateMachineDefinition as D, EnumFieldBuilder as E, FieldBuilder as F, type SyncDiagnosticsSnapshot as G, type HLCTimestamp as H, type SyncRuleDefinition as I, type TimeSource as J, type KoraEvent as K, migrate as L, type MigrationDefinition as M, t as N, type OperationType as O, type RandomSource as R, type SchemaDefinition as S, type TransitionMap as T, type VersionVector as V, type AtomicOp as a, type CollectionDefinition as b, type RelationDefinition as c, ArrayFieldBuilder as d, type FieldKind as e, type Operation as f, type MigrationStep as g, type StateMachineConstraint as h, type TransitionValidationResult as i, CONNECTION_QUALITIES as j, type ConnectionQuality as k, type Constraint as l, type FieldDescriptor as m, type FieldMergeStrategy as n, type KoraEventByType as o, type KoraEventEmitter as p, type KoraEventListener as q, type KoraEventType as r, MERGE_STRATEGIES as s, type MergeStrategy as t, type MergeTrace as u, MigrationBuilder as v, type OnDeleteAction as w, type OperationInput as x, type RelationType as y, RollbackBuilder as z };
974
+ export { type AtomicOpType as A, type BlobRef as B, type CollectionDefinition as C, type SecretMode as D, EnumFieldBuilder as E, FieldBuilder as F, type SequenceConfig as G, type HLCTimestamp as H, type StateMachineDefinition as I, JsonFieldBuilder as J, type KoraEvent as K, type SyncDiagnosticsSnapshot as L, type MigrationDefinition as M, type SyncRuleDefinition as N, type OperationType as O, type TimeSource as P, migrate as Q, type RandomSource as R, type SchemaDefinition as S, type TransitionMap as T, t as U, type VersionVector as V, type AtomicOp as a, type CustomResolver as b, type RelationDefinition as c, ArrayFieldBuilder as d, type FieldKind as e, type Operation as f, type MigrationStep as g, type StateMachineConstraint as h, type TransitionValidationResult as i, CONNECTION_QUALITIES as j, type ConnectionQuality as k, type Constraint as l, type FieldDescriptor as m, type FieldMergeStrategy as n, type KoraEventByType as o, type KoraEventEmitter as p, type KoraEventListener as q, type KoraEventType as r, MERGE_STRATEGIES as s, type MergeStrategy as t, type MergeTrace as u, MigrationBuilder as v, type OnDeleteAction as w, type OperationInput as x, type RelationType as y, RollbackBuilder as z };
@@ -89,6 +89,56 @@ declare class ArrayFieldBuilder<ItemKind extends FieldKind = FieldKind, Req exte
89
89
  merge(strategy: FieldMergeStrategy): ArrayFieldBuilder<ItemKind, Req, Auto>;
90
90
  _build(): FieldDescriptor;
91
91
  }
92
+ /**
93
+ * Field builder for structured object fields with a nested field schema.
94
+ *
95
+ * Each nested key merges by its own declared kind (scalars via LWW, nested
96
+ * arrays via add-wins, nested objects recursively), so two devices editing
97
+ * different keys of the same object offline both converge on reconnect instead
98
+ * of one clobbering the other.
99
+ */
100
+ declare class ObjectFieldBuilder<Req extends boolean = true, Auto extends boolean = false> extends FieldBuilder<'object', Req, Auto> {
101
+ private readonly _fields;
102
+ constructor(fields: Record<string, FieldBuilder>, required?: Req, defaultValue?: unknown, auto?: Auto, mergeStrategy?: FieldMergeStrategy | null);
103
+ optional(): ObjectFieldBuilder<false, Auto>;
104
+ default(value: Record<string, unknown>): ObjectFieldBuilder<false, Auto>;
105
+ auto(): ObjectFieldBuilder<false, true>;
106
+ merge(strategy: FieldMergeStrategy): ObjectFieldBuilder<Req, Auto>;
107
+ _build(): FieldDescriptor;
108
+ }
109
+ /**
110
+ * Field builder for dynamic-key JSON values. Carries a compile-time shape `T`
111
+ * for inference while merging structurally as a convergent CRDT: a plain-object
112
+ * value recurses as a map, an array merges add-wins, any other value is a scalar
113
+ * leaf under last-write-wins.
114
+ */
115
+ declare class JsonFieldBuilder<T = unknown, Req extends boolean = true, Auto extends boolean = false> extends FieldBuilder<'json', Req, Auto> {
116
+ optional(): JsonFieldBuilder<T, false, Auto>;
117
+ default(value: T): JsonFieldBuilder<T, false, Auto>;
118
+ auto(): JsonFieldBuilder<T, false, true>;
119
+ merge(strategy: FieldMergeStrategy): JsonFieldBuilder<T, Req, Auto>;
120
+ _build(): FieldDescriptor;
121
+ }
122
+ /**
123
+ * Field builder for secret fields (passwords, tokens, API keys).
124
+ *
125
+ * A secret field's value is never exposed in merge traces, DevTools, or logs
126
+ * (it is redacted at the point traces are built). Its at-rest protection is
127
+ * chosen with `.hashed()` (one-way, for passwords) or `.encrypted()` (reversible,
128
+ * for tokens); the default is `encrypted`.
129
+ */
130
+ declare class SecretFieldBuilder<Req extends boolean = true, Auto extends boolean = false> extends FieldBuilder<'secret', Req, Auto> {
131
+ private readonly _secretMode;
132
+ constructor(secretMode?: SecretMode, required?: Req, defaultValue?: unknown, auto?: Auto, mergeStrategy?: FieldMergeStrategy | null);
133
+ /** Store this secret as a one-way salted hash (passwords: verify, never read back). */
134
+ hashed(): SecretFieldBuilder<Req, Auto>;
135
+ /** Store this secret as reversible ciphertext (tokens/keys: decrypt to use). */
136
+ encrypted(): SecretFieldBuilder<Req, Auto>;
137
+ optional(): SecretFieldBuilder<false, Auto>;
138
+ auto(): SecretFieldBuilder<false, true>;
139
+ merge(strategy: FieldMergeStrategy): SecretFieldBuilder<Req, Auto>;
140
+ _build(): FieldDescriptor;
141
+ }
92
142
  /**
93
143
  * Type builder namespace. The developer's primary interface for defining field types.
94
144
  *
@@ -103,6 +153,8 @@ declare class ArrayFieldBuilder<ItemKind extends FieldKind = FieldKind, Req exte
103
153
  * notes: t.richtext(),
104
154
  * tags: t.array(t.string()).default([]),
105
155
  * priority: t.enum(['low', 'medium', 'high']).default('medium'),
156
+ * settings: t.object({ theme: t.string(), fontSize: t.number() }),
157
+ * metadata: t.json<{ source: string }>(),
106
158
  * createdAt: t.timestamp().auto(),
107
159
  * }
108
160
  * ```
@@ -115,6 +167,10 @@ declare const t: {
115
167
  richtext(): FieldBuilder<"richtext", true, false>;
116
168
  enum<const V extends readonly string[]>(values: V): EnumFieldBuilder<V, true, false>;
117
169
  array<K extends FieldKind>(itemBuilder: FieldBuilder<K>): ArrayFieldBuilder<K, true, false>;
170
+ object(fields: Record<string, FieldBuilder>): ObjectFieldBuilder<true, false>;
171
+ json<T = unknown>(): JsonFieldBuilder<T, true, false>;
172
+ blob(): FieldBuilder<"blob", true, false>;
173
+ secret(): SecretFieldBuilder<true, false>;
118
174
  };
119
175
 
120
176
  /**
@@ -390,7 +446,39 @@ interface OperationInput {
390
446
  /** Version vector: maps nodeId to the max sequence number seen from that node */
391
447
  type VersionVector = Map<string, number>;
392
448
  /** Field kinds supported by the schema system */
393
- type FieldKind = 'string' | 'number' | 'boolean' | 'timestamp' | 'richtext' | 'enum' | 'array';
449
+ type FieldKind = 'string' | 'number' | 'boolean' | 'timestamp' | 'richtext' | 'enum' | 'array' | 'object' | 'json' | 'blob' | 'secret';
450
+ /**
451
+ * How a `secret` field's value is protected at rest.
452
+ * - `hashed`: one-way (passwords). Stored as a salted hash; never read back.
453
+ * - `encrypted`: reversible (tokens, keys). Stored as ciphertext; decrypted to use.
454
+ */
455
+ type SecretMode = 'hashed' | 'encrypted';
456
+ /**
457
+ * A content-addressed reference to binary data stored out of band.
458
+ *
459
+ * A `blob` field never carries the bytes in the operation log; it carries this
460
+ * small reference. The bytes live in a content-addressed blob store, keyed by
461
+ * `hash`, so identical content is stored once and never re-synced to a peer that
462
+ * already has that hash.
463
+ */
464
+ interface BlobRef {
465
+ /** Hex-encoded SHA-256 hash of the bytes. The content address. */
466
+ hash: string;
467
+ /** Size of the bytes in bytes. */
468
+ size: number;
469
+ /** Optional MIME type (for example "image/png"). */
470
+ mimeType?: string;
471
+ /** Optional original filename. */
472
+ filename?: string;
473
+ /**
474
+ * Hex-encoded SHA-256 hash of this blob's manifest — the small chunk index a
475
+ * peer fetches first to learn how to pull the bytes out of band. Present when
476
+ * the blob was stored for transfer; absent for a bare reference created without
477
+ * chunking. Because it is a content address like `hash`, the manifest is fetched
478
+ * and integrity-verified over the same channel as the chunks.
479
+ */
480
+ manifestHash?: string;
481
+ }
394
482
  /**
395
483
  * Comprehensive sync diagnostics snapshot.
396
484
  * Provides connection, latency, throughput, queue, and error metrics
@@ -398,7 +486,7 @@ type FieldKind = 'string' | 'number' | 'boolean' | 'timestamp' | 'richtext' | 'e
398
486
  */
399
487
  interface SyncDiagnosticsSnapshot {
400
488
  /** Current developer-facing sync status */
401
- status: 'connected' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error';
489
+ status: 'connected' | 'reconnecting' | 'syncing' | 'synced' | 'offline' | 'error' | 'schema-mismatch' | 'clock-error';
402
490
  /** Timestamp when the current connection was established, or null if disconnected */
403
491
  connectedAt: number | null;
404
492
  /** Timestamp when the last disconnection occurred, or null if never disconnected */
@@ -490,6 +578,17 @@ interface FieldDescriptor {
490
578
  mergeStrategy: FieldMergeStrategy | null;
491
579
  /** State machine transition map for enum fields. Null if no transitions declared. */
492
580
  transitions: TransitionMap | null;
581
+ /**
582
+ * Nested field descriptors for `object` fields (each key merges by its own
583
+ * kind). Null for `json` fields (dynamic keys, resolved structurally) and for
584
+ * all non-structured kinds.
585
+ */
586
+ nestedFields?: Record<string, FieldDescriptor> | null;
587
+ /**
588
+ * At-rest protection mode for `secret` fields (`hashed` or `encrypted`).
589
+ * Null/absent for every other kind.
590
+ */
591
+ secretMode?: SecretMode | null;
493
592
  }
494
593
  /**
495
594
  * Defines a state machine on an enum field, constraining valid state transitions.
@@ -727,6 +826,33 @@ type KoraEvent = {
727
826
  code: string;
728
827
  message: string;
729
828
  retriable: boolean;
829
+ } | {
830
+ /**
831
+ * The server rejected one of THIS client's outbound operations before it
832
+ * became authoritative. The op has been diverted out of the pending sync
833
+ * queue into the durable rejected store (kept, not retried); the app can
834
+ * surface the reason and decide whether to roll back the optimistic local
835
+ * write or let the user edit and resubmit.
836
+ */
837
+ type: 'sync:operation-rejected';
838
+ operationId: string;
839
+ collection: string;
840
+ recordId: string;
841
+ code: string;
842
+ message: string;
843
+ retriable: boolean;
844
+ } | {
845
+ type: 'sync:delivery-gap';
846
+ expectedBase: number;
847
+ receivedBase: number;
848
+ currentWatermark: number;
849
+ messageId: string;
850
+ repeatCount: number;
851
+ } | {
852
+ type: 'sync:delivery-stalled';
853
+ sessionId: string;
854
+ watermark: number;
855
+ repeatCount: number;
730
856
  } | {
731
857
  type: 'query:subscribed';
732
858
  queryId: string;
@@ -781,6 +907,46 @@ type KoraEvent = {
781
907
  type: 'store:quota-exceeded';
782
908
  dbName: string;
783
909
  message: string;
910
+ } | {
911
+ /**
912
+ * OPFS persistence was unavailable, so the store fell back to a
913
+ * NON-PERSISTENT in-memory database. Anything written this session is lost
914
+ * on reload. This is emitted instead of failing silently so the condition
915
+ * is observable rather than a data-loss trap.
916
+ */
917
+ type: 'store:opfs-unavailable';
918
+ dbName: string;
919
+ /**
920
+ * Why OPFS could not be used. `lock-conflict` means another runtime on this
921
+ * origin already holds the OPFS pool for this database; `timeout` means the
922
+ * VFS install did not complete in time (common in headless CI); `unsupported`
923
+ * means the runtime has no usable OPFS.
924
+ */
925
+ reason: 'lock-conflict' | 'timeout' | 'unsupported';
926
+ message: string;
927
+ } | {
928
+ /**
929
+ * A preferred storage backend could not provide durable storage, so Kora
930
+ * promoted the app to another durable backend before user code observed the
931
+ * store. This is informational: data still survives reloads.
932
+ */
933
+ type: 'store:storage-fallback';
934
+ dbName: string;
935
+ from: 'opfs' | 'sqlite-wasm';
936
+ to: 'indexeddb';
937
+ reason: 'lock-conflict' | 'timeout' | 'unsupported';
938
+ message: string;
939
+ } | {
940
+ /**
941
+ * Another runtime on this origin was already using this database name, so
942
+ * this runtime attached to it as a follower and now SHARES that one
943
+ * database. That is intended for multiple tabs of the SAME app; it is a bug
944
+ * if these are logically separate apps, which should each use a distinct
945
+ * store name (`store: { name: '...' }`) to stay isolated.
946
+ */
947
+ type: 'store:db-name-collision';
948
+ dbName: string;
949
+ message: string;
784
950
  } | {
785
951
  type: 'replay:completed';
786
952
  targetOperationId: string;
@@ -805,4 +971,4 @@ interface KoraEventEmitter {
805
971
  emit<T extends KoraEventType>(event: KoraEventByType<T>): void;
806
972
  }
807
973
 
808
- export { type AtomicOpType as A, type SequenceConfig as B, type CustomResolver as C, type StateMachineDefinition as D, EnumFieldBuilder as E, FieldBuilder as F, type SyncDiagnosticsSnapshot as G, type HLCTimestamp as H, type SyncRuleDefinition as I, type TimeSource as J, type KoraEvent as K, migrate as L, type MigrationDefinition as M, t as N, type OperationType as O, type RandomSource as R, type SchemaDefinition as S, type TransitionMap as T, type VersionVector as V, type AtomicOp as a, type CollectionDefinition as b, type RelationDefinition as c, ArrayFieldBuilder as d, type FieldKind as e, type Operation as f, type MigrationStep as g, type StateMachineConstraint as h, type TransitionValidationResult as i, CONNECTION_QUALITIES as j, type ConnectionQuality as k, type Constraint as l, type FieldDescriptor as m, type FieldMergeStrategy as n, type KoraEventByType as o, type KoraEventEmitter as p, type KoraEventListener as q, type KoraEventType as r, MERGE_STRATEGIES as s, type MergeStrategy as t, type MergeTrace as u, MigrationBuilder as v, type OnDeleteAction as w, type OperationInput as x, type RelationType as y, RollbackBuilder as z };
974
+ export { type AtomicOpType as A, type BlobRef as B, type CollectionDefinition as C, type SecretMode as D, EnumFieldBuilder as E, FieldBuilder as F, type SequenceConfig as G, type HLCTimestamp as H, type StateMachineDefinition as I, JsonFieldBuilder as J, type KoraEvent as K, type SyncDiagnosticsSnapshot as L, type MigrationDefinition as M, type SyncRuleDefinition as N, type OperationType as O, type TimeSource as P, migrate as Q, type RandomSource as R, type SchemaDefinition as S, type TransitionMap as T, t as U, type VersionVector as V, type AtomicOp as a, type CustomResolver as b, type RelationDefinition as c, ArrayFieldBuilder as d, type FieldKind as e, type Operation as f, type MigrationStep as g, type StateMachineConstraint as h, type TransitionValidationResult as i, CONNECTION_QUALITIES as j, type ConnectionQuality as k, type Constraint as l, type FieldDescriptor as m, type FieldMergeStrategy as n, type KoraEventByType as o, type KoraEventEmitter as p, type KoraEventListener as q, type KoraEventType as r, MERGE_STRATEGIES as s, type MergeStrategy as t, type MergeTrace as u, MigrationBuilder as v, type OnDeleteAction as w, type OperationInput as x, type RelationType as y, RollbackBuilder as z };