@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.
- package/dist/bindings/index.d.cts +6 -3
- package/dist/bindings/index.d.ts +6 -3
- package/dist/{build-scope-map-BIeawJzC.d.cts → build-scope-map-B8dpCt6P.d.cts} +1 -1
- package/dist/{build-scope-map-DOf4JLTh.d.ts → build-scope-map-CDpIGOsf.d.ts} +1 -1
- package/dist/{events-BynBOsO3.d.cts → events-3z0Tema3.d.cts} +169 -3
- package/dist/{events-BynBOsO3.d.ts → events-3z0Tema3.d.ts} +169 -3
- package/dist/index.cjs +648 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +222 -7
- package/dist/index.d.ts +222 -7
- package/dist/index.js +633 -29
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/{operation-BpZlYSpe.d.cts → operation-BvBgl-4c.d.cts} +1 -1
- package/dist/{operation-D5WOZYvy.d.ts → operation-ClhEbhoV.d.ts} +1 -1
- package/package.json +2 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { p as KoraEventEmitter } from '../events-
|
|
2
|
-
import { S as ScopeMap } from '../build-scope-map-
|
|
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
|
}
|
package/dist/bindings/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { p as KoraEventEmitter } from '../events-
|
|
2
|
-
import { S as ScopeMap } from '../build-scope-map-
|
|
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
|
}
|
|
@@ -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
|
|
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
|
|
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 };
|