@datafn/client 0.0.1 → 0.0.2
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/README.md +749 -104
- package/dist/index.cjs +6162 -571
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +577 -43
- package/dist/index.d.ts +577 -43
- package/dist/index.js +6161 -572
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DatafnEvent,
|
|
1
|
+
import { DatafnEvent, DatafnSchema, DfqlQueryFragment, DfqlMutationFragment, DfqlTransact, DatafnSignal, DatafnPlugin, SearchProvider, DatafnErrorCode } from '@datafn/core';
|
|
2
|
+
export { KV_RESOURCE_NAME, kvId, ns } from '@datafn/core';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Event filtering logic
|
|
@@ -12,6 +13,7 @@ interface EventFilter {
|
|
|
12
13
|
action?: string | string[];
|
|
13
14
|
fields?: string | string[];
|
|
14
15
|
contextKeys?: string[];
|
|
16
|
+
context?: Record<string, unknown>;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Check if an event matches the filter
|
|
@@ -23,42 +25,52 @@ declare function matchesFilter(event: DatafnEvent, filter?: EventFilter): boolea
|
|
|
23
25
|
*/
|
|
24
26
|
|
|
25
27
|
type EventHandler = (event: DatafnEvent) => void;
|
|
28
|
+
interface EventBusOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Error handler called when a subscriber throws an error.
|
|
31
|
+
* Default: logs to console.error
|
|
32
|
+
*/
|
|
33
|
+
onError?: (error: unknown, event: DatafnEvent) => void;
|
|
34
|
+
}
|
|
26
35
|
/**
|
|
27
|
-
* Simple in-process event bus
|
|
36
|
+
* Simple in-process event bus with fault-tolerant delivery
|
|
28
37
|
*/
|
|
29
38
|
declare class EventBus {
|
|
30
39
|
private subscriptions;
|
|
31
40
|
private nextId;
|
|
41
|
+
private onError;
|
|
42
|
+
constructor(options?: EventBusOptions);
|
|
32
43
|
/**
|
|
33
44
|
* Subscribe to events with optional filtering
|
|
34
45
|
*/
|
|
35
46
|
subscribe(handler: EventHandler, filter?: EventFilter): () => void;
|
|
36
47
|
/**
|
|
37
|
-
* Emit an event to all matching subscribers
|
|
48
|
+
* Emit an event to all matching subscribers.
|
|
49
|
+
* Errors in handlers are isolated - a throwing handler will not prevent delivery to other handlers.
|
|
38
50
|
*/
|
|
39
51
|
emit(event: DatafnEvent): void;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*
|
|
45
|
-
* Represents a table/resource from the schema with methods for query, mutation, signals, and subscriptions.
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
interface DatafnTable<TRecord = unknown> {
|
|
49
|
-
name: string;
|
|
50
|
-
version: number;
|
|
51
|
-
query(q: unknown): Promise<unknown>;
|
|
52
|
-
mutate(m: unknown): Promise<unknown>;
|
|
53
|
-
transact(payload: unknown): Promise<unknown>;
|
|
54
|
-
signal(q: unknown): DatafnSignal<unknown>;
|
|
55
|
-
subscribe(handler: EventHandler, filter?: EventFilter): () => void;
|
|
52
|
+
/**
|
|
53
|
+
* Remove all subscriptions
|
|
54
|
+
*/
|
|
55
|
+
clear(): void;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Storage adapter types for local persistence
|
|
60
60
|
*/
|
|
61
61
|
type DatafnHydrationState = "notStarted" | "hydrating" | "ready";
|
|
62
|
+
/**
|
|
63
|
+
* Factory function for creating storage adapters with namespace-based isolation.
|
|
64
|
+
* Used for multi-user/multi-tenant data isolation.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const storageFactory: DatafnStorageFactory = (namespace) => {
|
|
69
|
+
* return IndexedDbStorageAdapter.createForNamespace("my-app", namespace);
|
|
70
|
+
* };
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
type DatafnStorageFactory = (namespace: string) => DatafnStorageAdapter;
|
|
62
74
|
type DatafnChangelogEntry = {
|
|
63
75
|
/** Monotonic local sequence (assigned by storage adapter). */
|
|
64
76
|
seq: number;
|
|
@@ -66,17 +78,31 @@ type DatafnChangelogEntry = {
|
|
|
66
78
|
mutationId: string;
|
|
67
79
|
mutation: Record<string, unknown>;
|
|
68
80
|
timestampMs: number;
|
|
81
|
+
/** Actor ID for audit attribution (when available) - AUD-001 */
|
|
82
|
+
actorId?: string;
|
|
83
|
+
/** ISO 8601 timestamp - AUD-001 */
|
|
84
|
+
timestamp?: string;
|
|
69
85
|
};
|
|
70
86
|
interface DatafnStorageAdapter {
|
|
71
87
|
getRecord(resource: string, id: string): Promise<Record<string, unknown> | null>;
|
|
72
88
|
listRecords(resource: string): Promise<Record<string, unknown>[]>;
|
|
73
89
|
upsertRecord(resource: string, record: Record<string, unknown>): Promise<void>;
|
|
74
90
|
deleteRecord(resource: string, id: string): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Atomic read-modify-write merge. MUST execute in a single transaction.
|
|
93
|
+
* If record doesn't exist, upserts with partial as the full record.
|
|
94
|
+
* Uses one-level-deep merge for object-type fields.
|
|
95
|
+
*/
|
|
96
|
+
mergeRecord(resource: string, id: string, partial: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
75
97
|
listJoinRows(relationKey: string): Promise<Array<Record<string, unknown>>>;
|
|
98
|
+
getJoinRows(relationKey: string, fromId: string): Promise<Array<Record<string, unknown>>>;
|
|
99
|
+
getJoinRowsInverse(relationKey: string, toId: string): Promise<Array<Record<string, unknown>>>;
|
|
76
100
|
upsertJoinRow(relationKey: string, row: Record<string, unknown>): Promise<void>;
|
|
101
|
+
setJoinRows(relationKey: string, rows: Array<Record<string, unknown>>): Promise<void>;
|
|
77
102
|
deleteJoinRow(relationKey: string, from: string, to: string): Promise<void>;
|
|
103
|
+
findRecords(resource: string, field: string, value: unknown): Promise<Record<string, unknown>[]>;
|
|
78
104
|
getCursor(resource: string): Promise<string | null>;
|
|
79
|
-
setCursor(resource: string, cursor: string): Promise<void>;
|
|
105
|
+
setCursor(resource: string, cursor: string | null): Promise<void>;
|
|
80
106
|
getHydrationState(resource: string): Promise<DatafnHydrationState>;
|
|
81
107
|
setHydrationState(resource: string, state: DatafnHydrationState): Promise<void>;
|
|
82
108
|
changelogAppend(entry: Omit<DatafnChangelogEntry, "seq">): Promise<DatafnChangelogEntry>;
|
|
@@ -86,13 +112,135 @@ interface DatafnStorageAdapter {
|
|
|
86
112
|
changelogAck(options: {
|
|
87
113
|
throughSeq: number;
|
|
88
114
|
}): Promise<void>;
|
|
115
|
+
countRecords(resource: string): Promise<number>;
|
|
116
|
+
countJoinRows(relationKey: string): Promise<number>;
|
|
117
|
+
/** Close all connections and release resources. */
|
|
118
|
+
close(): Promise<void>;
|
|
119
|
+
/** Delete all data across all stores. */
|
|
120
|
+
clearAll(): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Health check: verify core stores exist and are accessible.
|
|
123
|
+
* Returns { ok: true, issues: [] } when healthy.
|
|
124
|
+
* Returns { ok: false, issues: [...] } when corrupted or inaccessible.
|
|
125
|
+
*/
|
|
126
|
+
healthCheck(): Promise<{
|
|
127
|
+
ok: boolean;
|
|
128
|
+
issues: string[];
|
|
129
|
+
}>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Mutation Execution Utilities
|
|
134
|
+
*
|
|
135
|
+
* Handles mutation execution via remote adapter with event emission.
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
type ShareScope = "record" | "resource";
|
|
139
|
+
type PrincipalShareMutationInput = {
|
|
140
|
+
principalId: string;
|
|
141
|
+
level: string;
|
|
142
|
+
scope?: ShareScope;
|
|
143
|
+
id?: string;
|
|
144
|
+
};
|
|
145
|
+
type PrincipalUnshareMutationInput = {
|
|
146
|
+
principalId: string;
|
|
147
|
+
scope?: ShareScope;
|
|
148
|
+
id?: string;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Query Execution Utilities
|
|
153
|
+
*
|
|
154
|
+
* Handles query execution via remote adapter or local storage based on hydration state.
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
type PermissionEntry = {
|
|
158
|
+
userId: string;
|
|
159
|
+
level: string;
|
|
160
|
+
grantedBy: string;
|
|
161
|
+
grantedAt: number;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* DataFn Table Handle
|
|
166
|
+
*
|
|
167
|
+
* Represents a table/resource from the schema with methods for query, mutation, signals, and subscriptions.
|
|
168
|
+
*/
|
|
169
|
+
|
|
170
|
+
type QueryMetadata = {
|
|
171
|
+
includeTrashed?: boolean;
|
|
172
|
+
includeArchived?: boolean;
|
|
173
|
+
};
|
|
174
|
+
interface DatafnTable<S extends DatafnSchema = DatafnSchema, Name extends string = string, TRecord = unknown> {
|
|
175
|
+
name: Name;
|
|
176
|
+
version: number;
|
|
177
|
+
query(q: DfqlQueryFragment & {
|
|
178
|
+
metadata?: QueryMetadata;
|
|
179
|
+
}): Promise<unknown>;
|
|
180
|
+
mutate(m: DfqlMutationFragment | DfqlMutationFragment[]): Promise<unknown>;
|
|
181
|
+
delete(id: string): Promise<unknown>;
|
|
182
|
+
trash?: (id: string) => Promise<unknown>;
|
|
183
|
+
restore?: (id: string) => Promise<unknown>;
|
|
184
|
+
archive?: (id: string) => Promise<unknown>;
|
|
185
|
+
unarchive?: (id: string) => Promise<unknown>;
|
|
186
|
+
share?: {
|
|
187
|
+
(id: string, userId: string, level: string): Promise<unknown>;
|
|
188
|
+
(input: PrincipalShareMutationInput): Promise<unknown>;
|
|
189
|
+
};
|
|
190
|
+
unshare?: {
|
|
191
|
+
(id: string, userId: string): Promise<unknown>;
|
|
192
|
+
(input: PrincipalUnshareMutationInput): Promise<unknown>;
|
|
193
|
+
};
|
|
194
|
+
getPermissions?: (id: string) => Promise<PermissionEntry[]>;
|
|
195
|
+
transact(payload: DfqlTransact): Promise<unknown>;
|
|
196
|
+
signal(q: DfqlQueryFragment, options?: {
|
|
197
|
+
disableOptimistic?: boolean;
|
|
198
|
+
}): DatafnSignal<unknown>;
|
|
199
|
+
subscribe(handler: EventHandler, filter?: EventFilter): () => void;
|
|
89
200
|
}
|
|
90
201
|
|
|
202
|
+
type CloneUpOptions = {
|
|
203
|
+
resources?: string[];
|
|
204
|
+
includeManyMany?: boolean;
|
|
205
|
+
recordOperation?: "merge" | "replace" | "insert";
|
|
206
|
+
batchSize?: number;
|
|
207
|
+
maxRetries?: number;
|
|
208
|
+
failFast?: boolean;
|
|
209
|
+
clearChangelogOnSuccess?: boolean;
|
|
210
|
+
setGlobalCursorOnSuccess?: boolean;
|
|
211
|
+
pullAfter?: boolean;
|
|
212
|
+
mutationIdPrefix?: string;
|
|
213
|
+
};
|
|
214
|
+
type CloneUpResult = {
|
|
215
|
+
ok: boolean;
|
|
216
|
+
cursor: string;
|
|
217
|
+
stats: {
|
|
218
|
+
resources: Record<string, {
|
|
219
|
+
records: number;
|
|
220
|
+
mutations: number;
|
|
221
|
+
}>;
|
|
222
|
+
joinStores: Record<string, {
|
|
223
|
+
rows: number;
|
|
224
|
+
mutations: number;
|
|
225
|
+
}>;
|
|
226
|
+
batches: number;
|
|
227
|
+
};
|
|
228
|
+
errors: Array<{
|
|
229
|
+
mutationId: string;
|
|
230
|
+
code: string;
|
|
231
|
+
message: string;
|
|
232
|
+
path: string;
|
|
233
|
+
}>;
|
|
234
|
+
/** Optional total count of uploaded records (for event context) */
|
|
235
|
+
uploadedCount?: number;
|
|
236
|
+
};
|
|
237
|
+
|
|
91
238
|
/**
|
|
92
239
|
* Sync Facade
|
|
93
240
|
*
|
|
94
241
|
* Client-side sync methods that delegate to remote adapter.
|
|
95
242
|
* When storage is configured, clone/pull results are applied to local storage.
|
|
243
|
+
* Implements HOOK-001 and EVT-003: beforeSync/afterSync hooks and sync lifecycle events
|
|
96
244
|
*/
|
|
97
245
|
|
|
98
246
|
interface SyncFacade {
|
|
@@ -100,12 +248,57 @@ interface SyncFacade {
|
|
|
100
248
|
clone(payload: unknown): Promise<unknown>;
|
|
101
249
|
pull(payload: unknown): Promise<unknown>;
|
|
102
250
|
push(payload: unknown): Promise<unknown>;
|
|
251
|
+
cloneUp(options?: CloneUpOptions): Promise<CloneUpResult>;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* KV API implementation for datafn
|
|
256
|
+
* Provides a first-class key-value store that works with signals and events.
|
|
257
|
+
*/
|
|
258
|
+
|
|
259
|
+
interface DatafnKvApi {
|
|
260
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
261
|
+
set<T = unknown>(key: string, value: T, params?: {
|
|
262
|
+
context?: Record<string, unknown>;
|
|
263
|
+
debounceMs?: number;
|
|
264
|
+
}): Promise<{
|
|
265
|
+
ok: true;
|
|
266
|
+
key: string;
|
|
267
|
+
} | {
|
|
268
|
+
ok: false;
|
|
269
|
+
error: unknown;
|
|
270
|
+
}>;
|
|
271
|
+
merge(key: string, patch: Record<string, unknown>, params?: {
|
|
272
|
+
context?: Record<string, unknown>;
|
|
273
|
+
debounceMs?: number;
|
|
274
|
+
}): Promise<{
|
|
275
|
+
ok: true;
|
|
276
|
+
key: string;
|
|
277
|
+
} | {
|
|
278
|
+
ok: false;
|
|
279
|
+
error: unknown;
|
|
280
|
+
}>;
|
|
281
|
+
delete(key: string, params?: {
|
|
282
|
+
context?: Record<string, unknown>;
|
|
283
|
+
}): Promise<{
|
|
284
|
+
ok: true;
|
|
285
|
+
key: string;
|
|
286
|
+
} | {
|
|
287
|
+
ok: false;
|
|
288
|
+
error: unknown;
|
|
289
|
+
}>;
|
|
290
|
+
getOrSeed<T = unknown>(key: string, defaults: T): Promise<T>;
|
|
291
|
+
flush(key?: string): Promise<void>;
|
|
292
|
+
signal<T = unknown>(key: string, options?: {
|
|
293
|
+
defaultValue?: T;
|
|
294
|
+
}): DatafnSignal<T>;
|
|
103
295
|
}
|
|
104
296
|
|
|
105
297
|
/**
|
|
106
298
|
* DataFn client factory
|
|
107
299
|
*/
|
|
108
300
|
|
|
301
|
+
type ResourceNames<S extends DatafnSchema> = S["resources"][number]["name"];
|
|
109
302
|
interface DatafnRemoteAdapter {
|
|
110
303
|
query(q: unknown): Promise<unknown>;
|
|
111
304
|
mutation(m: unknown): Promise<unknown>;
|
|
@@ -114,10 +307,132 @@ interface DatafnRemoteAdapter {
|
|
|
114
307
|
clone(payload: unknown): Promise<unknown>;
|
|
115
308
|
pull(payload: unknown): Promise<unknown>;
|
|
116
309
|
push(payload: unknown): Promise<unknown>;
|
|
310
|
+
reconcile(payload: unknown): Promise<unknown>;
|
|
311
|
+
search?(payload: unknown): Promise<unknown>;
|
|
312
|
+
}
|
|
313
|
+
interface DatafnSyncConfig {
|
|
314
|
+
/**
|
|
315
|
+
* Enable offline support. Requires `storage` adapter.
|
|
316
|
+
*/
|
|
317
|
+
offlinability?: boolean;
|
|
318
|
+
/**
|
|
319
|
+
* Remote server URL used by the default HTTP transport and for deriving wsUrl.
|
|
320
|
+
* Optional when `remoteAdapter` is provided.
|
|
321
|
+
*/
|
|
322
|
+
remote?: string;
|
|
323
|
+
/**
|
|
324
|
+
* Optional injected adapter used instead of DefaultHttpTransport.
|
|
325
|
+
* Required for extension environments.
|
|
326
|
+
*/
|
|
327
|
+
remoteAdapter?: DatafnRemoteAdapter;
|
|
328
|
+
/**
|
|
329
|
+
* Enable WebSocket updates.
|
|
330
|
+
*/
|
|
331
|
+
ws?: boolean;
|
|
332
|
+
/**
|
|
333
|
+
* WebSocket URL. If not provided, derived from `remote` when `ws` is enabled.
|
|
334
|
+
*/
|
|
335
|
+
wsUrl?: string;
|
|
336
|
+
/**
|
|
337
|
+
* Batch push interval in milliseconds.
|
|
338
|
+
* Must be a positive integer.
|
|
339
|
+
*/
|
|
340
|
+
pushInterval?: number;
|
|
341
|
+
/**
|
|
342
|
+
* Batch push page size.
|
|
343
|
+
* Must be a positive integer. Default 100.
|
|
344
|
+
*/
|
|
345
|
+
pushBatchSize?: number;
|
|
346
|
+
/**
|
|
347
|
+
* Max retries for push.
|
|
348
|
+
* Must be a non-negative integer. Default 3.
|
|
349
|
+
*/
|
|
350
|
+
pushMaxRetries?: number;
|
|
351
|
+
/**
|
|
352
|
+
* Hydration plan for large datasets.
|
|
353
|
+
*/
|
|
354
|
+
hydration?: {
|
|
355
|
+
/**
|
|
356
|
+
* Resources that MUST be cloned to `ready` before the app can consider itself hydrated.
|
|
357
|
+
*/
|
|
358
|
+
bootResources?: string[];
|
|
359
|
+
/**
|
|
360
|
+
* Resources that MAY hydrate in the background after boot.
|
|
361
|
+
*/
|
|
362
|
+
backgroundResources?: string[];
|
|
363
|
+
/**
|
|
364
|
+
* Per-resource clone page size limits used by paginated clone.
|
|
365
|
+
*/
|
|
366
|
+
clonePageSize?: number | Record<string, number>;
|
|
367
|
+
};
|
|
368
|
+
/**
|
|
369
|
+
* Explicit mode selection.
|
|
370
|
+
* - "sync": requires remote or remoteAdapter.
|
|
371
|
+
* - "local-only": remote is not required; all local tables start as `ready`.
|
|
372
|
+
*/
|
|
373
|
+
mode?: "sync" | "local-only";
|
|
374
|
+
/**
|
|
375
|
+
* WebSocket reconnection configuration with exponential backoff and jitter.
|
|
376
|
+
* Default: enabled with baseDelayMs=1000, multiplier=2, maxDelayMs=60000, jitterMs=500.
|
|
377
|
+
*/
|
|
378
|
+
wsReconnect?: {
|
|
379
|
+
enabled?: boolean;
|
|
380
|
+
baseDelayMs?: number;
|
|
381
|
+
maxDelayMs?: number;
|
|
382
|
+
multiplier?: number;
|
|
383
|
+
jitterMs?: number;
|
|
384
|
+
};
|
|
385
|
+
/**
|
|
386
|
+
* Push retry exponential backoff configuration.
|
|
387
|
+
* Default: baseDelayMs=1000, multiplier=2, maxDelayMs=60000, jitterMs=500.
|
|
388
|
+
*/
|
|
389
|
+
pushRetryBackoff?: {
|
|
390
|
+
baseDelayMs?: number;
|
|
391
|
+
maxDelayMs?: number;
|
|
392
|
+
multiplier?: number;
|
|
393
|
+
jitterMs?: number;
|
|
394
|
+
};
|
|
395
|
+
/**
|
|
396
|
+
* Push interval exponential backoff configuration when push keeps failing.
|
|
397
|
+
* When a push round exhausts all retries and fails, the next round is delayed
|
|
398
|
+
* using exponential backoff: pushInterval * multiplier^consecutiveFailures.
|
|
399
|
+
* On first success after failures, backoff is reset to configured pushInterval.
|
|
400
|
+
* Default: baseMultiplier=2, maxDelayMs=300000 (5 minutes), jitterMs=1000.
|
|
401
|
+
*/
|
|
402
|
+
pushIntervalBackoff?: {
|
|
403
|
+
baseMultiplier?: number;
|
|
404
|
+
maxDelayMs?: number;
|
|
405
|
+
jitterMs?: number;
|
|
406
|
+
};
|
|
407
|
+
/**
|
|
408
|
+
* Pull batch size limit per request.
|
|
409
|
+
* Must be a positive integer between 10 and 10000.
|
|
410
|
+
* Default: 200.
|
|
411
|
+
*/
|
|
412
|
+
pullBatchSize?: number;
|
|
413
|
+
/**
|
|
414
|
+
* Maximum number of catch-up pull iterations per sync cycle (CLIENT-PULL-003).
|
|
415
|
+
* Prevents infinite loops if the server continuously returns hasMore=true.
|
|
416
|
+
* Default: 50.
|
|
417
|
+
*/
|
|
418
|
+
maxPullIterations?: number;
|
|
419
|
+
/**
|
|
420
|
+
* Enable cross-tab coordination via BroadcastChannel.
|
|
421
|
+
* When enabled, mutation events are relayed to other same-origin tabs for near-instant reactivity.
|
|
422
|
+
* Default: false (opt-in).
|
|
423
|
+
*/
|
|
424
|
+
crossTab?: boolean;
|
|
425
|
+
/**
|
|
426
|
+
* Defer search indexing for initial clone and rebuild asynchronously after clone completion.
|
|
427
|
+
*/
|
|
428
|
+
skipCloneIndexing?: boolean;
|
|
117
429
|
}
|
|
118
|
-
interface DatafnClientConfig {
|
|
119
|
-
schema:
|
|
120
|
-
|
|
430
|
+
interface DatafnClientConfig<S extends DatafnSchema> {
|
|
431
|
+
schema: S;
|
|
432
|
+
/**
|
|
433
|
+
* Sync configuration.
|
|
434
|
+
*/
|
|
435
|
+
sync?: DatafnSyncConfig;
|
|
121
436
|
/**
|
|
122
437
|
* Optional plugins for client-side hook execution
|
|
123
438
|
*/
|
|
@@ -126,25 +441,114 @@ interface DatafnClientConfig {
|
|
|
126
441
|
* Stable client/device identifier used for idempotency and offline change logs.
|
|
127
442
|
* Required when `storage` is provided.
|
|
128
443
|
*/
|
|
129
|
-
clientId
|
|
444
|
+
clientId: string;
|
|
445
|
+
/**
|
|
446
|
+
* Local persistence adapter. Can be:
|
|
447
|
+
* - A direct `DatafnStorageAdapter` instance
|
|
448
|
+
* - A factory function `(namespace: string) => DatafnStorageAdapter` for multi-user isolation
|
|
449
|
+
*
|
|
450
|
+
* When using a factory function, `namespace` must also be provided.
|
|
451
|
+
*/
|
|
452
|
+
storage?: DatafnStorageAdapter | DatafnStorageFactory;
|
|
130
453
|
/**
|
|
131
|
-
*
|
|
454
|
+
* Namespace for client-side data isolation.
|
|
455
|
+
* Used to construct isolated storage (IndexedDB database names, BroadcastChannel names).
|
|
456
|
+
* Required when `storage` is a factory function.
|
|
132
457
|
*/
|
|
133
|
-
|
|
458
|
+
namespace?: string;
|
|
134
459
|
getTimestamp?: () => number;
|
|
460
|
+
/**
|
|
461
|
+
* Resource-aware ID generator function for insert operations.
|
|
462
|
+
* If not provided, defaults to crypto.randomUUID() with resource prefix.
|
|
463
|
+
* Allows users to use custom ID strategies (UUID v7, ULID, etc.)
|
|
464
|
+
* without adding dependencies to @datafn/client.
|
|
465
|
+
*/
|
|
466
|
+
generateId?: (params: {
|
|
467
|
+
resource: string;
|
|
468
|
+
idPrefix?: string;
|
|
469
|
+
}) => string;
|
|
470
|
+
/**
|
|
471
|
+
* Optional search provider for client-side search query routing.
|
|
472
|
+
*/
|
|
473
|
+
searchProvider?: SearchProvider;
|
|
135
474
|
}
|
|
136
|
-
|
|
137
|
-
|
|
475
|
+
/**
|
|
476
|
+
* Override options for switchContext.
|
|
477
|
+
* Only the provided fields are changed; everything else is inherited from the current config.
|
|
478
|
+
*/
|
|
479
|
+
type SwitchContextOverride = {
|
|
480
|
+
/** New namespace (replaces current) */
|
|
481
|
+
namespace?: string;
|
|
482
|
+
/** New sync configuration (replaces current, not merged) */
|
|
483
|
+
sync?: DatafnSyncConfig;
|
|
484
|
+
/** New storage adapter or factory (replaces current) */
|
|
485
|
+
storage?: DatafnStorageAdapter | DatafnStorageFactory;
|
|
486
|
+
};
|
|
487
|
+
type DatafnClient<S extends DatafnSchema> = {
|
|
488
|
+
table<Name extends ResourceNames<S>>(name: Name): DatafnTable<S, Name>;
|
|
138
489
|
query(q: unknown | unknown[]): Promise<unknown>;
|
|
139
490
|
mutate(mutation: unknown | unknown[]): Promise<unknown>;
|
|
140
491
|
transact(payload: unknown): Promise<unknown>;
|
|
141
492
|
subscribe(handler: EventHandler, filter?: EventFilter): () => void;
|
|
142
|
-
sync: SyncFacade
|
|
143
|
-
|
|
493
|
+
sync: SyncFacade & {
|
|
494
|
+
start(): Promise<void>;
|
|
495
|
+
stop(): void;
|
|
496
|
+
pullNow(): Promise<void>;
|
|
497
|
+
cloneNow(): Promise<void>;
|
|
498
|
+
reconcileNow(): Promise<void>;
|
|
499
|
+
};
|
|
500
|
+
kv: DatafnKvApi;
|
|
501
|
+
/** Tear down client: stop sync, close connections, unsubscribe all, release resources. */
|
|
502
|
+
destroy(): Promise<void>;
|
|
503
|
+
/** Wipe all local data (IndexedDB stores, cursors, changelog, hydration state). */
|
|
504
|
+
clear(): Promise<void>;
|
|
505
|
+
/** Flush a specific debounced mutation immediately. */
|
|
506
|
+
flush(key?: string): Promise<void>;
|
|
507
|
+
/** Flush all pending debounced mutations immediately. */
|
|
508
|
+
flushAll(): Promise<void>;
|
|
509
|
+
/** Export all local records as structured JSON. */
|
|
510
|
+
exportData(options?: {
|
|
511
|
+
resources?: string[];
|
|
512
|
+
}): Promise<unknown>;
|
|
513
|
+
/** Import records from a structured JSON payload. */
|
|
514
|
+
importData(data: unknown, options?: {
|
|
515
|
+
triggerCloneUp?: boolean;
|
|
516
|
+
}): Promise<unknown>;
|
|
517
|
+
/** Check storage health and verify hydration state consistency. */
|
|
518
|
+
checkHealth(): Promise<{
|
|
519
|
+
ok: boolean;
|
|
520
|
+
issues: string[];
|
|
521
|
+
action?: "none" | "reclone";
|
|
522
|
+
}>;
|
|
523
|
+
/** Perform a cross-resource search using the configured search provider. */
|
|
524
|
+
search(params: unknown): Promise<unknown>;
|
|
525
|
+
/**
|
|
526
|
+
* Switch to a different configuration context (auth, sync mode, or storage).
|
|
527
|
+
* Destroys the current underlying client and recreates it with merged config.
|
|
528
|
+
* Concurrent calls are serialized. Auto-starts sync when sync.mode is "sync".
|
|
529
|
+
*/
|
|
530
|
+
switchContext(override: SwitchContextOverride): Promise<void>;
|
|
531
|
+
/** Get the current resolved namespace, or undefined if not configured. */
|
|
532
|
+
currentNamespace(): string | undefined;
|
|
533
|
+
/**
|
|
534
|
+
* Subscribe to client lifecycle changes.
|
|
535
|
+
* The callback fires immediately with the stable proxy reference,
|
|
536
|
+
* and again after every switchContext() completes.
|
|
537
|
+
* Returns an unsubscribe function.
|
|
538
|
+
*/
|
|
539
|
+
subscribeClient(fn: (client: DatafnClient<S>) => void): () => void;
|
|
540
|
+
} & {
|
|
541
|
+
[Name in ResourceNames<S>]: DatafnTable<S, Name>;
|
|
542
|
+
};
|
|
144
543
|
/**
|
|
145
|
-
* Create a DataFn client
|
|
544
|
+
* Create a DataFn client with built-in context switching.
|
|
545
|
+
*
|
|
546
|
+
* The returned client is a stable Proxy reference that always delegates to the
|
|
547
|
+
* current underlying client. Use `switchContext()` to switch auth, sync mode,
|
|
548
|
+
* or storage without replacing the reference. Use `subscribeClient()` to react
|
|
549
|
+
* to switches (e.g. to recreate signals in framework adapters).
|
|
146
550
|
*/
|
|
147
|
-
declare function createDatafnClient(config: DatafnClientConfig): DatafnClient
|
|
551
|
+
declare function createDatafnClient<S extends DatafnSchema>(config: DatafnClientConfig<S>): DatafnClient<S>;
|
|
148
552
|
|
|
149
553
|
/**
|
|
150
554
|
* DataFn Client Error Types
|
|
@@ -159,12 +563,16 @@ type DatafnClientError = {
|
|
|
159
563
|
};
|
|
160
564
|
};
|
|
161
565
|
/**
|
|
162
|
-
*
|
|
566
|
+
* Build and throw a DatafnClientError.
|
|
163
567
|
*/
|
|
164
|
-
declare function
|
|
568
|
+
declare function throwClientError(code: DatafnClientError["code"], message: string, details: {
|
|
165
569
|
path: string;
|
|
166
570
|
[key: string]: unknown;
|
|
167
571
|
}): never;
|
|
572
|
+
/**
|
|
573
|
+
* Alias for `throwClientError`.
|
|
574
|
+
*/
|
|
575
|
+
declare const createClientError: typeof throwClientError;
|
|
168
576
|
|
|
169
577
|
/**
|
|
170
578
|
* Remote Response Unwrapping Utilities
|
|
@@ -194,16 +602,28 @@ declare class MemoryStorageAdapter implements DatafnStorageAdapter {
|
|
|
194
602
|
private hydration;
|
|
195
603
|
private changelog;
|
|
196
604
|
private changelogSeq;
|
|
197
|
-
|
|
605
|
+
private changelogIndex;
|
|
606
|
+
private validResources?;
|
|
607
|
+
constructor(resources?: string[]);
|
|
608
|
+
private validateTableName;
|
|
198
609
|
getRecord(resource: string, id: string): Promise<Record<string, unknown> | null>;
|
|
199
610
|
listRecords(resource: string): Promise<Record<string, unknown>[]>;
|
|
200
611
|
upsertRecord(resource: string, record: Record<string, unknown>): Promise<void>;
|
|
201
612
|
deleteRecord(resource: string, id: string): Promise<void>;
|
|
613
|
+
/**
|
|
614
|
+
* Atomic read-modify-write merge using one-level-deep merge.
|
|
615
|
+
*/
|
|
616
|
+
mergeRecord(resource: string, id: string, partial: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
617
|
+
private deepMergeOneLevel;
|
|
202
618
|
listJoinRows(relationKey: string): Promise<Array<Record<string, unknown>>>;
|
|
619
|
+
getJoinRows(relationKey: string, fromId: string): Promise<Array<Record<string, unknown>>>;
|
|
620
|
+
getJoinRowsInverse(relationKey: string, toId: string): Promise<Array<Record<string, unknown>>>;
|
|
203
621
|
upsertJoinRow(relationKey: string, row: Record<string, unknown>): Promise<void>;
|
|
622
|
+
setJoinRows(relationKey: string, rows: Array<Record<string, unknown>>): Promise<void>;
|
|
204
623
|
deleteJoinRow(relationKey: string, from: string, to: string): Promise<void>;
|
|
624
|
+
findRecords(resource: string, field: string, value: unknown): Promise<Record<string, unknown>[]>;
|
|
205
625
|
getCursor(resource: string): Promise<string | null>;
|
|
206
|
-
setCursor(resource: string, cursor: string): Promise<void>;
|
|
626
|
+
setCursor(resource: string, cursor: string | null): Promise<void>;
|
|
207
627
|
getHydrationState(resource: string): Promise<DatafnHydrationState>;
|
|
208
628
|
setHydrationState(resource: string, state: DatafnHydrationState): Promise<void>;
|
|
209
629
|
changelogAppend(entry: Omit<DatafnChangelogEntry, "seq">): Promise<DatafnChangelogEntry>;
|
|
@@ -213,7 +633,14 @@ declare class MemoryStorageAdapter implements DatafnStorageAdapter {
|
|
|
213
633
|
changelogAck(options: {
|
|
214
634
|
throughSeq: number;
|
|
215
635
|
}): Promise<void>;
|
|
216
|
-
|
|
636
|
+
countRecords(resource: string): Promise<number>;
|
|
637
|
+
countJoinRows(relationKey: string): Promise<number>;
|
|
638
|
+
close(): Promise<void>;
|
|
639
|
+
clearAll(): Promise<void>;
|
|
640
|
+
healthCheck(): Promise<{
|
|
641
|
+
ok: boolean;
|
|
642
|
+
issues: string[];
|
|
643
|
+
}>;
|
|
217
644
|
}
|
|
218
645
|
|
|
219
646
|
/**
|
|
@@ -223,17 +650,76 @@ declare class MemoryStorageAdapter implements DatafnStorageAdapter {
|
|
|
223
650
|
|
|
224
651
|
declare class IndexedDbStorageAdapter implements DatafnStorageAdapter {
|
|
225
652
|
private dbPromise;
|
|
226
|
-
|
|
653
|
+
private validResources?;
|
|
654
|
+
private schema?;
|
|
655
|
+
readonly dbName: string;
|
|
656
|
+
/**
|
|
657
|
+
* Create an IndexedDB storage adapter with schema-aware store creation.
|
|
658
|
+
* This is the **recommended** way to create the adapter — schema is required,
|
|
659
|
+
* ensuring all resource object stores and join tables are created in IndexedDB.
|
|
660
|
+
*
|
|
661
|
+
* If the database already exists but is missing stores (stale DB), the adapter
|
|
662
|
+
* automatically bumps the version and creates the missing stores.
|
|
663
|
+
*
|
|
664
|
+
* @param options.dbName - Database name (e.g., "my-app-db")
|
|
665
|
+
* @param options.schema - DataFn schema (required — used to create object stores)
|
|
666
|
+
*
|
|
667
|
+
* @example
|
|
668
|
+
* ```typescript
|
|
669
|
+
* const storage = IndexedDbStorageAdapter.create({
|
|
670
|
+
* dbName: "my-app-db",
|
|
671
|
+
* schema,
|
|
672
|
+
* });
|
|
673
|
+
* ```
|
|
674
|
+
*/
|
|
675
|
+
static create(options: {
|
|
676
|
+
dbName: string;
|
|
677
|
+
schema: DatafnSchema;
|
|
678
|
+
}): IndexedDbStorageAdapter;
|
|
679
|
+
/**
|
|
680
|
+
* Create a storage adapter with a namespace-derived database name.
|
|
681
|
+
* The namespace string has `:` replaced with `_` for the IDB database name.
|
|
682
|
+
*
|
|
683
|
+
* @example
|
|
684
|
+
* ```typescript
|
|
685
|
+
* const storage = IndexedDbStorageAdapter.createForNamespace("my-app", "org:user-1");
|
|
686
|
+
* // IDB database name: "my-app_org_user-1"
|
|
687
|
+
* ```
|
|
688
|
+
*/
|
|
689
|
+
static createForNamespace(baseDbName: string, namespace: string, resources?: string[], schema?: DatafnSchema): IndexedDbStorageAdapter;
|
|
690
|
+
constructor(dbName?: string, resources?: string[], schema?: DatafnSchema);
|
|
691
|
+
/**
|
|
692
|
+
* Open (or upgrade) the IndexedDB database at the given version.
|
|
693
|
+
* All object store creation happens inside `onupgradeneeded`.
|
|
694
|
+
*/
|
|
695
|
+
private openDatabase;
|
|
696
|
+
/**
|
|
697
|
+
* Verify all expected stores exist after initial open.
|
|
698
|
+
* If stores are missing (stale DB opened without schema previously),
|
|
699
|
+
* close the database and reopen with a bumped version to trigger
|
|
700
|
+
* `onupgradeneeded`, which will create the missing stores.
|
|
701
|
+
*/
|
|
702
|
+
private ensureStores;
|
|
703
|
+
private validateTableName;
|
|
227
704
|
private getStore;
|
|
228
705
|
getRecord(resource: string, id: string): Promise<Record<string, unknown> | null>;
|
|
229
706
|
listRecords(resource: string): Promise<Record<string, unknown>[]>;
|
|
230
707
|
upsertRecord(resource: string, record: Record<string, unknown>): Promise<void>;
|
|
231
708
|
deleteRecord(resource: string, id: string): Promise<void>;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
709
|
+
/**
|
|
710
|
+
* Atomic read-modify-write merge using a single transaction.
|
|
711
|
+
* Uses one-level-deep merge for object-type fields.
|
|
712
|
+
*/
|
|
713
|
+
mergeRecord(resource: string, id: string, partial: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
714
|
+
listJoinRows(storeName: string): Promise<Array<Record<string, unknown>>>;
|
|
715
|
+
getJoinRows(storeName: string, fromId: string): Promise<Array<Record<string, unknown>>>;
|
|
716
|
+
getJoinRowsInverse(storeName: string, toId: string): Promise<Array<Record<string, unknown>>>;
|
|
717
|
+
upsertJoinRow(storeName: string, row: Record<string, unknown>): Promise<void>;
|
|
718
|
+
setJoinRows(storeName: string, rows: Array<Record<string, unknown>>): Promise<void>;
|
|
719
|
+
deleteJoinRow(storeName: string, from: string, to: string): Promise<void>;
|
|
720
|
+
findRecords(resource: string, field: string, value: unknown): Promise<Record<string, unknown>[]>;
|
|
235
721
|
getCursor(resource: string): Promise<string | null>;
|
|
236
|
-
setCursor(resource: string, cursor: string): Promise<void>;
|
|
722
|
+
setCursor(resource: string, cursor: string | null): Promise<void>;
|
|
237
723
|
getHydrationState(resource: string): Promise<DatafnHydrationState>;
|
|
238
724
|
setHydrationState(resource: string, state: DatafnHydrationState): Promise<void>;
|
|
239
725
|
changelogAppend(entry: Omit<DatafnChangelogEntry, "seq">): Promise<DatafnChangelogEntry>;
|
|
@@ -243,6 +729,54 @@ declare class IndexedDbStorageAdapter implements DatafnStorageAdapter {
|
|
|
243
729
|
changelogAck(options: {
|
|
244
730
|
throughSeq: number;
|
|
245
731
|
}): Promise<void>;
|
|
732
|
+
countRecords(resource: string): Promise<number>;
|
|
733
|
+
countJoinRows(relationKey: string): Promise<number>;
|
|
734
|
+
close(): Promise<void>;
|
|
735
|
+
clearAll(): Promise<void>;
|
|
736
|
+
healthCheck(): Promise<{
|
|
737
|
+
ok: boolean;
|
|
738
|
+
issues: string[];
|
|
739
|
+
}>;
|
|
246
740
|
}
|
|
247
741
|
|
|
248
|
-
|
|
742
|
+
/**
|
|
743
|
+
* Date Codec (CODEC-001)
|
|
744
|
+
*
|
|
745
|
+
* Provides schema-driven date serialization and parsing:
|
|
746
|
+
* - Outbound (mutation): Date objects → epoch milliseconds (number)
|
|
747
|
+
* - Inbound (query result): epoch milliseconds or ISO strings → Date objects for schema date fields
|
|
748
|
+
* - Deterministic error behavior for invalid dates
|
|
749
|
+
*/
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Serialize Date fields in mutation records to epoch milliseconds (CODEC-001 outbound)
|
|
753
|
+
*
|
|
754
|
+
* @param schema Schema definition
|
|
755
|
+
* @param resource Resource name
|
|
756
|
+
* @param record Record to serialize
|
|
757
|
+
* @returns Serialized record with Date → epoch milliseconds (number)
|
|
758
|
+
* @throws DFQL_INVALID if a date field contains a non-Date value
|
|
759
|
+
*/
|
|
760
|
+
declare function serializeDateFields(schema: DatafnSchema, resource: string, record: Record<string, unknown>): Record<string, unknown>;
|
|
761
|
+
/**
|
|
762
|
+
* Parse date fields in query results from epoch milliseconds or ISO strings to Date objects (CODEC-001 inbound)
|
|
763
|
+
*
|
|
764
|
+
* @param schema Schema definition
|
|
765
|
+
* @param resource Resource name
|
|
766
|
+
* @param record Record to parse
|
|
767
|
+
* @returns Parsed record with epoch milliseconds or ISO string → Date
|
|
768
|
+
* @throws DFQL_INVALID if a date field contains an invalid value
|
|
769
|
+
*/
|
|
770
|
+
declare function parseDateFields(schema: DatafnSchema, resource: string, record: Record<string, unknown>): Record<string, unknown>;
|
|
771
|
+
/**
|
|
772
|
+
* Parse date fields in a query result payload
|
|
773
|
+
* Handles both non-aggregate (data array) and aggregate (groups array) results
|
|
774
|
+
*
|
|
775
|
+
* @param schema Schema definition
|
|
776
|
+
* @param resource Resource name
|
|
777
|
+
* @param result Query result payload
|
|
778
|
+
* @returns Result with parsed Date fields
|
|
779
|
+
*/
|
|
780
|
+
declare function parseQueryResultDates(schema: DatafnSchema, resource: string, result: any): any;
|
|
781
|
+
|
|
782
|
+
export { type CloneUpOptions, type CloneUpResult, type DatafnChangelogEntry, type DatafnClient, type DatafnClientConfig, type DatafnClientError, type DatafnHydrationState, type DatafnKvApi, type DatafnRemoteAdapter, type DatafnStorageAdapter, type DatafnStorageFactory, type DatafnTable, EventBus, type EventFilter, type EventHandler, IndexedDbStorageAdapter, MemoryStorageAdapter, type PermissionEntry, type SwitchContextOverride, createClientError, createDatafnClient, matchesFilter, parseDateFields, parseQueryResultDates, serializeDateFields, throwClientError, unwrapRemoteSuccess };
|