@datafn/core 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 +465 -89
- package/dist/index.cjs +1178 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +567 -29
- package/dist/index.d.ts +567 -29
- package/dist/index.js +1141 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,26 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataFn Error Types and Envelopes
|
|
3
|
+
*/
|
|
4
|
+
type DatafnErrorCode = "SCHEMA_INVALID" | "INVALID_CAPABILITY" | "INVALID_CAPABILITY_CONFIG" | "CAPABILITY_FIELD_COLLISION" | "CAPABILITY_DEPENDENCY" | "DFQL_INVALID" | "DFQL_UNKNOWN_RESOURCE" | "DFQL_UNKNOWN_FIELD" | "DFQL_UNKNOWN_RELATION" | "DFQL_UNSUPPORTED" | "DFQL_ABORTED" | "LIMIT_EXCEEDED" | "FORBIDDEN" | "NOT_FOUND" | "CONFLICT" | "INTERNAL"
|
|
5
|
+
/** LOW-026: Network/transport-layer errors (timeouts, connection refused, etc.) */
|
|
6
|
+
| "TRANSPORT_ERROR";
|
|
7
|
+
type DatafnError = {
|
|
8
|
+
code: DatafnErrorCode;
|
|
9
|
+
message: string;
|
|
10
|
+
details?: unknown;
|
|
11
|
+
};
|
|
12
|
+
type DatafnEnvelope<T> = {
|
|
13
|
+
ok: true;
|
|
14
|
+
result: T;
|
|
15
|
+
} | {
|
|
16
|
+
ok: false;
|
|
17
|
+
error: DatafnError;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Helper functions for creating envelopes
|
|
21
|
+
*/
|
|
22
|
+
declare function ok<T>(result: T): DatafnEnvelope<T>;
|
|
23
|
+
declare function err<T = never>(code: DatafnErrorCode, message: string, details?: unknown): DatafnEnvelope<T>;
|
|
24
|
+
|
|
25
|
+
type SimpleCapability = "timestamps" | "audit" | "trash" | "archivable";
|
|
26
|
+
type AccessLevel = "viewer" | "editor" | "owner";
|
|
27
|
+
type ShareableVisibilityDefault = "ns" | "private" | "shared";
|
|
28
|
+
type ShareablePrincipalMode = "opaque-id";
|
|
29
|
+
type ShareableRelationInheritance = {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
relations?: string[];
|
|
32
|
+
requireRelateConsent?: boolean;
|
|
33
|
+
};
|
|
34
|
+
type ShareableCapability = {
|
|
35
|
+
shareable: {
|
|
36
|
+
levels: AccessLevel[];
|
|
37
|
+
default?: "private" | "shared";
|
|
38
|
+
visibilityDefault?: ShareableVisibilityDefault;
|
|
39
|
+
supportsScopeGrants?: boolean;
|
|
40
|
+
crossNsShareable?: boolean;
|
|
41
|
+
principalMode?: ShareablePrincipalMode;
|
|
42
|
+
relationInheritance?: ShareableRelationInheritance;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
type CapabilityEntry = SimpleCapability | ShareableCapability;
|
|
46
|
+
type SchemaCapabilities = CapabilityEntry[];
|
|
47
|
+
type ResourceCapabilities = CapabilityEntry[] | {
|
|
48
|
+
exclude: SimpleCapability[];
|
|
49
|
+
};
|
|
50
|
+
type CapabilityKey = SimpleCapability | "shareable";
|
|
51
|
+
type CapabilityFieldDef = Pick<DatafnFieldSchema, "name" | "type" | "required" | "nullable" | "readonly" | "default">;
|
|
52
|
+
declare const CAPABILITY_FIELD_DEFS: Record<CapabilityKey, CapabilityFieldDef[]>;
|
|
53
|
+
type RelationCapabilityKey = "timestamps" | "audit";
|
|
54
|
+
/**
|
|
55
|
+
* Field definitions injected into join tables for each relation capability.
|
|
56
|
+
* Same field names as resource capabilities; kept separate for clarity.
|
|
57
|
+
*/
|
|
58
|
+
declare const RELATION_CAPABILITY_FIELD_DEFS: Record<RelationCapabilityKey, CapabilityFieldDef[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Returns the field names injected by the given resolved relation capabilities.
|
|
61
|
+
* Output is deduplicated and ordered deterministically (canonical capability order).
|
|
62
|
+
*/
|
|
63
|
+
declare function getRelationCapabilityFieldNames(caps: string[]): string[];
|
|
64
|
+
declare function resolveCapabilities(globalCaps?: SchemaCapabilities, resourceCaps?: ResourceCapabilities): CapabilityEntry[];
|
|
65
|
+
declare function getCapabilityFields(resolvedCaps: CapabilityEntry[]): DatafnFieldSchema[];
|
|
66
|
+
|
|
1
67
|
/**
|
|
2
68
|
* DataFn Schema Types
|
|
3
69
|
*/
|
|
70
|
+
|
|
4
71
|
type DatafnSchema = {
|
|
72
|
+
capabilities?: SchemaCapabilities;
|
|
5
73
|
resources: DatafnResourceSchema[];
|
|
6
74
|
relations?: DatafnRelationSchema[];
|
|
75
|
+
/** Defaults to true. Set to false to disable row-level namespace isolation. */
|
|
76
|
+
namespaced?: boolean;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Permissions policy shape for server-side authorization enforcement.
|
|
80
|
+
* All field arrays are optional; if omitted, no fields are accessible.
|
|
81
|
+
*/
|
|
82
|
+
type DatafnPermissionsPolicy = {
|
|
83
|
+
/**
|
|
84
|
+
* Read policy: controls which fields can be selected and filtered.
|
|
85
|
+
*/
|
|
86
|
+
read?: {
|
|
87
|
+
fields: string[];
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Write policy: controls which fields can be written via mutations.
|
|
91
|
+
*/
|
|
92
|
+
write?: {
|
|
93
|
+
fields: string[];
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Optional owner field for owner-scoped resources.
|
|
97
|
+
* When set, additional owner-based authorization checks can be performed.
|
|
98
|
+
*/
|
|
99
|
+
ownerField?: string;
|
|
7
100
|
};
|
|
8
101
|
type DatafnResourceSchema = {
|
|
9
102
|
name: string;
|
|
10
103
|
version: number;
|
|
11
104
|
idPrefix?: string;
|
|
12
105
|
isRemoteOnly?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Resource capabilities.
|
|
108
|
+
* For shareable resources, SPV2 extends shareable config with
|
|
109
|
+
* visibility/membership/scope-related options while preserving legacy fields.
|
|
110
|
+
*/
|
|
111
|
+
capabilities?: ResourceCapabilities;
|
|
13
112
|
fields: DatafnFieldSchema[];
|
|
14
113
|
indices?: {
|
|
15
114
|
base?: string[];
|
|
16
115
|
search?: string[];
|
|
17
116
|
vector?: string[];
|
|
18
117
|
} | string[];
|
|
19
|
-
permissions?:
|
|
118
|
+
permissions?: DatafnPermissionsPolicy;
|
|
20
119
|
};
|
|
21
120
|
type DatafnFieldSchema = {
|
|
22
121
|
name: string;
|
|
23
|
-
type: "string" | "number" | "boolean" | "object" | "array" | "date" | "file";
|
|
122
|
+
type: "string" | "number" | "boolean" | "object" | "array" | "date" | "file" | "json";
|
|
24
123
|
required: boolean;
|
|
25
124
|
nullable?: boolean;
|
|
26
125
|
readonly?: boolean;
|
|
@@ -35,6 +134,7 @@ type DatafnFieldSchema = {
|
|
|
35
134
|
encrypt?: boolean;
|
|
36
135
|
volatile?: boolean;
|
|
37
136
|
};
|
|
137
|
+
type RelationSimpleCapability = "timestamps" | "audit";
|
|
38
138
|
type DatafnRelationSchema = {
|
|
39
139
|
from: string | string[];
|
|
40
140
|
to: string | string[];
|
|
@@ -44,16 +144,70 @@ type DatafnRelationSchema = {
|
|
|
44
144
|
cache?: boolean;
|
|
45
145
|
metadata?: Array<{
|
|
46
146
|
name: string;
|
|
47
|
-
type: "string" | "number" | "boolean" | "date" | "object";
|
|
147
|
+
type: "string" | "number" | "boolean" | "date" | "object" | "json";
|
|
48
148
|
}>;
|
|
49
149
|
fkField?: string;
|
|
50
150
|
pathField?: string;
|
|
151
|
+
joinTable?: string;
|
|
152
|
+
joinColumns?: {
|
|
153
|
+
from: string;
|
|
154
|
+
to: string;
|
|
155
|
+
};
|
|
156
|
+
capabilities?: RelationSimpleCapability[];
|
|
51
157
|
};
|
|
158
|
+
/**
|
|
159
|
+
* Type-safe schema definition helper.
|
|
160
|
+
* Uses const generic to preserve literal field name types and constrain
|
|
161
|
+
* index entries to only declared field names within each resource.
|
|
162
|
+
*/
|
|
163
|
+
type DatafnFieldInput = {
|
|
164
|
+
readonly name: string;
|
|
165
|
+
readonly type: DatafnFieldSchema["type"];
|
|
166
|
+
readonly required: boolean;
|
|
167
|
+
readonly [k: string]: unknown;
|
|
168
|
+
};
|
|
169
|
+
type DatafnIndicesInput<FieldName extends string> = {
|
|
170
|
+
readonly base?: readonly FieldName[];
|
|
171
|
+
readonly search?: readonly FieldName[];
|
|
172
|
+
readonly vector?: readonly FieldName[];
|
|
173
|
+
} | readonly FieldName[];
|
|
174
|
+
type SchemaCapabilitiesInput = readonly CapabilityEntry[];
|
|
175
|
+
type ResourceCapabilitiesInput = SchemaCapabilitiesInput | {
|
|
176
|
+
readonly exclude: readonly SimpleCapability[];
|
|
177
|
+
};
|
|
178
|
+
type DatafnResourceBase = {
|
|
179
|
+
readonly name: string;
|
|
180
|
+
readonly version: number;
|
|
181
|
+
readonly idPrefix?: string;
|
|
182
|
+
readonly isRemoteOnly?: boolean;
|
|
183
|
+
readonly capabilities?: ResourceCapabilitiesInput;
|
|
184
|
+
readonly fields: readonly DatafnFieldInput[];
|
|
185
|
+
readonly indices?: DatafnIndicesInput<string>;
|
|
186
|
+
readonly permissions?: DatafnPermissionsPolicy;
|
|
187
|
+
readonly [k: string]: unknown;
|
|
188
|
+
};
|
|
189
|
+
type ValidateResources<R extends readonly DatafnResourceBase[]> = {
|
|
190
|
+
readonly [K in keyof R]: R[K] extends {
|
|
191
|
+
readonly fields: readonly {
|
|
192
|
+
readonly name: infer N extends string;
|
|
193
|
+
}[];
|
|
194
|
+
} ? Omit<R[K], "indices"> & {
|
|
195
|
+
readonly indices?: DatafnIndicesInput<N>;
|
|
196
|
+
} : R[K];
|
|
197
|
+
};
|
|
198
|
+
declare function defineSchema<const T extends {
|
|
199
|
+
readonly capabilities?: SchemaCapabilitiesInput;
|
|
200
|
+
readonly resources: readonly DatafnResourceBase[];
|
|
201
|
+
readonly relations?: readonly DatafnRelationSchema[] | DatafnRelationSchema[];
|
|
202
|
+
readonly namespaced?: boolean;
|
|
203
|
+
}>(schema: T & {
|
|
204
|
+
readonly resources: ValidateResources<T["resources"]>;
|
|
205
|
+
}): T & DatafnSchema;
|
|
52
206
|
/**
|
|
53
207
|
* Event Types
|
|
54
208
|
*/
|
|
55
209
|
interface DatafnEvent {
|
|
56
|
-
type: "mutation_applied" | "mutation_rejected" | "sync_applied" | "sync_failed";
|
|
210
|
+
type: "mutation_applied" | "mutation_rejected" | "sync_applied" | "sync_failed" | "sync_retry" | "connectivity_changed" | "ws_connected" | "ws_disconnected";
|
|
57
211
|
resource?: string;
|
|
58
212
|
ids?: string[];
|
|
59
213
|
mutationId?: string;
|
|
@@ -62,6 +216,8 @@ interface DatafnEvent {
|
|
|
62
216
|
context?: unknown;
|
|
63
217
|
action?: string;
|
|
64
218
|
fields?: string[];
|
|
219
|
+
system?: boolean;
|
|
220
|
+
fromRemoteTab?: boolean;
|
|
65
221
|
}
|
|
66
222
|
type DatafnEventFilter = Partial<{
|
|
67
223
|
type: DatafnEvent["type"] | Array<DatafnEvent["type"]>;
|
|
@@ -71,6 +227,7 @@ type DatafnEventFilter = Partial<{
|
|
|
71
227
|
action: string | string[];
|
|
72
228
|
fields: string | string[];
|
|
73
229
|
contextKeys: string[];
|
|
230
|
+
context: Record<string, unknown>;
|
|
74
231
|
}>;
|
|
75
232
|
/**
|
|
76
233
|
* Signal Type
|
|
@@ -78,7 +235,51 @@ type DatafnEventFilter = Partial<{
|
|
|
78
235
|
interface DatafnSignal<T> {
|
|
79
236
|
get(): T;
|
|
80
237
|
subscribe(handler: (value: T) => void): () => void;
|
|
238
|
+
readonly loading: boolean;
|
|
239
|
+
readonly error: DatafnError | null;
|
|
240
|
+
readonly refreshing: boolean;
|
|
241
|
+
readonly nextCursor: string | null | undefined;
|
|
242
|
+
dispose(): void;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Logger interface for structured, pluggable logging.
|
|
246
|
+
*/
|
|
247
|
+
interface DatafnLogger {
|
|
248
|
+
info(message: string, context?: Record<string, unknown>): void;
|
|
249
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
250
|
+
error(message: string, context?: Record<string, unknown>): void;
|
|
251
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
81
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Limits configuration for server-side enforcement.
|
|
255
|
+
*/
|
|
256
|
+
type DatafnLimitsConfig = {
|
|
257
|
+
maxLimit?: number;
|
|
258
|
+
maxTransactSteps?: number;
|
|
259
|
+
maxPayloadBytes?: number;
|
|
260
|
+
maxPullLimit?: number;
|
|
261
|
+
maxBatchSize?: number;
|
|
262
|
+
maxCloneRecords?: number;
|
|
263
|
+
idempotencyTtlMs?: number;
|
|
264
|
+
idempotencyMaxEntries?: number;
|
|
265
|
+
maxSelectTokens?: number;
|
|
266
|
+
maxFilterKeysPerLevel?: number;
|
|
267
|
+
maxSortFields?: number;
|
|
268
|
+
maxAggregations?: number;
|
|
269
|
+
maxSearchQueryLength?: number;
|
|
270
|
+
maxLikePatternLength?: number;
|
|
271
|
+
maxIdLength?: number;
|
|
272
|
+
maxFilterDepth?: number;
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* WebSocket configuration for connection limits and heartbeat.
|
|
276
|
+
*/
|
|
277
|
+
type DatafnWsConfig = {
|
|
278
|
+
maxConnections?: number;
|
|
279
|
+
maxConnectionsPerNamespace?: number;
|
|
280
|
+
heartbeatIntervalMs?: number;
|
|
281
|
+
heartbeatTimeoutMs?: number;
|
|
282
|
+
};
|
|
82
283
|
/**
|
|
83
284
|
* Plugin Types
|
|
84
285
|
*/
|
|
@@ -94,38 +295,29 @@ interface DatafnPlugin {
|
|
|
94
295
|
afterQuery?: (ctx: DatafnHookContext, q: unknown, result: unknown) => Promise<unknown> | unknown;
|
|
95
296
|
beforeMutation?: (ctx: DatafnHookContext, m: unknown | unknown[]) => Promise<unknown> | unknown;
|
|
96
297
|
afterMutation?: (ctx: DatafnHookContext, m: unknown | unknown[], result: unknown) => Promise<void> | void;
|
|
97
|
-
beforeSync?: (ctx: DatafnHookContext, phase: "seed" | "clone" | "pull" | "push", payload: unknown) => Promise<unknown> | unknown;
|
|
98
|
-
afterSync?: (ctx: DatafnHookContext, phase: "seed" | "clone" | "pull" | "push", payload: unknown, result: unknown) => Promise<void> | void;
|
|
298
|
+
beforeSync?: (ctx: DatafnHookContext, phase: "seed" | "clone" | "pull" | "push" | "cloneUp" | "reconcile", payload: unknown) => Promise<unknown> | unknown;
|
|
299
|
+
afterSync?: (ctx: DatafnHookContext, phase: "seed" | "clone" | "pull" | "push" | "cloneUp" | "reconcile", payload: unknown, result: unknown) => Promise<void> | void;
|
|
99
300
|
}
|
|
100
301
|
|
|
101
|
-
/**
|
|
102
|
-
* DataFn Error Types and Envelopes
|
|
103
|
-
*/
|
|
104
|
-
type DatafnErrorCode = "SCHEMA_INVALID" | "DFQL_INVALID" | "DFQL_UNKNOWN_RESOURCE" | "DFQL_UNKNOWN_FIELD" | "DFQL_UNKNOWN_RELATION" | "DFQL_UNSUPPORTED" | "LIMIT_EXCEEDED" | "FORBIDDEN" | "NOT_FOUND" | "CONFLICT" | "INTERNAL";
|
|
105
|
-
type DatafnError = {
|
|
106
|
-
code: DatafnErrorCode;
|
|
107
|
-
message: string;
|
|
108
|
-
details?: unknown;
|
|
109
|
-
};
|
|
110
|
-
type DatafnEnvelope<T> = {
|
|
111
|
-
ok: true;
|
|
112
|
-
result: T;
|
|
113
|
-
} | {
|
|
114
|
-
ok: false;
|
|
115
|
-
error: DatafnError;
|
|
116
|
-
};
|
|
117
|
-
/**
|
|
118
|
-
* Helper functions for creating envelopes
|
|
119
|
-
*/
|
|
120
|
-
declare function ok<T>(result: T): DatafnEnvelope<T>;
|
|
121
|
-
declare function err<T = never>(code: DatafnErrorCode, message: string, details?: unknown): DatafnEnvelope<T>;
|
|
122
|
-
|
|
123
302
|
/**
|
|
124
303
|
* Schema Validation
|
|
125
304
|
*
|
|
126
305
|
* Validates and normalizes DataFn schemas according to SCHEMA-001.
|
|
127
306
|
*/
|
|
128
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Resolves and validates relation capability declarations.
|
|
310
|
+
* Returns canonical ordered, deduplicated array, or error.
|
|
311
|
+
* - Returns ok([]) when capabilities is undefined.
|
|
312
|
+
* - Rejects non-array, non-string entries, and unknown capability values.
|
|
313
|
+
* - Returns deterministic canonical order: ["timestamps", "audit"] subset.
|
|
314
|
+
*/
|
|
315
|
+
declare function resolveRelationCapabilities(capabilities: unknown): DatafnEnvelope<RelationSimpleCapability[]>;
|
|
316
|
+
/**
|
|
317
|
+
* Returns whether namespace isolation is enabled for a given schema.
|
|
318
|
+
* Default is `true`.
|
|
319
|
+
*/
|
|
320
|
+
declare function isNamespaced(schema: DatafnSchema): boolean;
|
|
129
321
|
/**
|
|
130
322
|
* Validates a schema and returns a normalized version.
|
|
131
323
|
*
|
|
@@ -140,6 +332,20 @@ declare function err<T = never>(code: DatafnErrorCode, message: string, details?
|
|
|
140
332
|
*/
|
|
141
333
|
declare function validateSchema(schema: unknown): DatafnEnvelope<DatafnSchema>;
|
|
142
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Composable namespace helper.
|
|
337
|
+
*
|
|
338
|
+
* Joins non-empty string segments with `:` to form a namespace string.
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ns("org-acme", "user-123") // => "org-acme:user-123"
|
|
342
|
+
* ns("user-1") // => "user-1"
|
|
343
|
+
* ns("org", "project", "user") // => "org:project:user"
|
|
344
|
+
* ns("org", "", "user") // => "org:user" (empty filtered)
|
|
345
|
+
* ns() // throws Error
|
|
346
|
+
*/
|
|
347
|
+
declare function ns(...segments: string[]): string;
|
|
348
|
+
|
|
143
349
|
/**
|
|
144
350
|
* DFQL Normalization
|
|
145
351
|
*
|
|
@@ -151,6 +357,11 @@ declare function validateSchema(schema: unknown): DatafnEnvelope<DatafnSchema>;
|
|
|
151
357
|
* - Sorts object keys alphabetically
|
|
152
358
|
* - Removes undefined values
|
|
153
359
|
* - Preserves arrays, primitives, and null as-is
|
|
360
|
+
*
|
|
361
|
+
* LOW-023: undefined vs null semantics:
|
|
362
|
+
* - `null` is preserved as `null` (explicit absence of a value — serialisable to JSON)
|
|
363
|
+
* - `undefined` is stripped from the output (non-serialisable; treated as "not set")
|
|
364
|
+
* This mirrors JSON.stringify behavior and ensures stable cache keys across environments.
|
|
154
365
|
*/
|
|
155
366
|
declare function normalizeDfql(value: unknown): unknown;
|
|
156
367
|
/**
|
|
@@ -175,4 +386,331 @@ declare function dfqlKey(value: unknown): string;
|
|
|
175
386
|
*/
|
|
176
387
|
declare function unwrapEnvelope<T>(env: DatafnEnvelope<T>): T;
|
|
177
388
|
|
|
178
|
-
|
|
389
|
+
type DfqlSort = string[];
|
|
390
|
+
type DfqlCursor = {
|
|
391
|
+
after?: Record<string, unknown>;
|
|
392
|
+
before?: Record<string, unknown>;
|
|
393
|
+
};
|
|
394
|
+
type DfqlQuery = {
|
|
395
|
+
resource: string;
|
|
396
|
+
version: number;
|
|
397
|
+
select?: string[];
|
|
398
|
+
omit?: string[];
|
|
399
|
+
filters?: Record<string, unknown>;
|
|
400
|
+
search?: Record<string, unknown>;
|
|
401
|
+
sort?: DfqlSort;
|
|
402
|
+
limit?: number;
|
|
403
|
+
offset?: number;
|
|
404
|
+
cursor?: DfqlCursor;
|
|
405
|
+
count?: boolean;
|
|
406
|
+
groupBy?: string[];
|
|
407
|
+
aggregations?: Record<string, unknown>;
|
|
408
|
+
having?: Record<string, unknown>;
|
|
409
|
+
signal?: AbortSignal;
|
|
410
|
+
};
|
|
411
|
+
type DfqlQueryFragment = Omit<DfqlQuery, "resource" | "version">;
|
|
412
|
+
type DfqlMutation = {
|
|
413
|
+
resource: string;
|
|
414
|
+
version: number;
|
|
415
|
+
operation: string;
|
|
416
|
+
id?: string | string[];
|
|
417
|
+
record?: Record<string, unknown>;
|
|
418
|
+
records?: Array<Record<string, unknown>>;
|
|
419
|
+
clientId?: string;
|
|
420
|
+
mutationId?: string;
|
|
421
|
+
timestamp?: number | string;
|
|
422
|
+
context?: unknown;
|
|
423
|
+
relations?: Record<string, unknown>;
|
|
424
|
+
if?: Record<string, unknown>;
|
|
425
|
+
cascade?: unknown;
|
|
426
|
+
silent?: boolean;
|
|
427
|
+
system?: boolean;
|
|
428
|
+
debounceKey?: string;
|
|
429
|
+
debounceMs?: number;
|
|
430
|
+
};
|
|
431
|
+
type DfqlMutationFragment = Omit<DfqlMutation, "resource" | "version">;
|
|
432
|
+
type DfqlTransact = {
|
|
433
|
+
transactionId?: string;
|
|
434
|
+
atomic?: boolean;
|
|
435
|
+
steps: Array<{
|
|
436
|
+
query?: DfqlQuery;
|
|
437
|
+
mutation?: DfqlMutation;
|
|
438
|
+
}>;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Built-in KV resource utilities
|
|
443
|
+
*/
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Canonical KV resource name
|
|
447
|
+
*/
|
|
448
|
+
declare const KV_RESOURCE_NAME = "kv";
|
|
449
|
+
/**
|
|
450
|
+
* Generate the canonical KV id from a key.
|
|
451
|
+
* Mapping: kvId(key) = "kv:" + key
|
|
452
|
+
*/
|
|
453
|
+
declare function kvId(key: string): string;
|
|
454
|
+
/**
|
|
455
|
+
* Ensure the schema includes the built-in KV resource.
|
|
456
|
+
* If schema already has resource "kv", validates it is compatible.
|
|
457
|
+
* Otherwise appends a resource definition for KV.
|
|
458
|
+
*/
|
|
459
|
+
declare function ensureBuiltinKv(schema: DatafnSchema): DatafnSchema;
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Shared join store key utilities.
|
|
463
|
+
*
|
|
464
|
+
* These functions provide the single canonical derivation of join store names
|
|
465
|
+
* used by both client and server across all sync operations.
|
|
466
|
+
*/
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Derive the logical join store key used by clients and change tracking.
|
|
470
|
+
* Format: join_{fromResource}_{relationName}_{toResource}
|
|
471
|
+
*/
|
|
472
|
+
declare function getJoinStoreKey(from: string, relation: string, to: string): string;
|
|
473
|
+
/**
|
|
474
|
+
* Derive the actual DB table name for a join table.
|
|
475
|
+
* Format: __datafn_join_{fromResource}_{relationName}
|
|
476
|
+
* Can be overridden by relation.joinTable in schema.
|
|
477
|
+
*/
|
|
478
|
+
declare function getJoinTableName(from: string, relation: string, joinTable?: string): string;
|
|
479
|
+
/**
|
|
480
|
+
* Enumerate all logical join store keys for a schema's many-many relations.
|
|
481
|
+
* Handles multi-resource from/to arrays.
|
|
482
|
+
* Useful for building cursor maps, reconcile payloads, and similar operations.
|
|
483
|
+
*
|
|
484
|
+
* @param relations - Array of relation schema definitions
|
|
485
|
+
* @param resourceFilter - Optional set of resource names; if provided, only
|
|
486
|
+
* joins whose `from` resource is in this set are included.
|
|
487
|
+
*/
|
|
488
|
+
declare function enumerateJoinStoreKeys(relations: DatafnRelationSchema[], resourceFilter?: Set<string>): string[];
|
|
489
|
+
|
|
490
|
+
interface SchemaIndex {
|
|
491
|
+
resourcesByName: Map<string, DatafnResourceSchema>;
|
|
492
|
+
fieldsByResource: Map<string, Map<string, DatafnFieldSchema>>;
|
|
493
|
+
/** All relations involving the resource (as from OR inverse to). */
|
|
494
|
+
relationsByResource: Map<string, DatafnRelationSchema[]>;
|
|
495
|
+
/** Relations where the resource is the from side. */
|
|
496
|
+
relationsFromResource: Map<string, DatafnRelationSchema[]>;
|
|
497
|
+
}
|
|
498
|
+
declare function buildSchemaIndex(schema: DatafnSchema): SchemaIndex;
|
|
499
|
+
declare function getResource(index: SchemaIndex, name: string): DatafnResourceSchema | undefined;
|
|
500
|
+
declare function getField(index: SchemaIndex, resource: string, field: string): DatafnFieldSchema | undefined;
|
|
501
|
+
declare function getRelationsFrom(index: SchemaIndex, resource: string): DatafnRelationSchema[];
|
|
502
|
+
declare function getRelation(index: SchemaIndex, fromResource: string, relationName: string): DatafnRelationSchema | undefined;
|
|
503
|
+
declare function getRelationTarget(relation: DatafnRelationSchema): string;
|
|
504
|
+
/**
|
|
505
|
+
* Find a relation by name in either direction (forward or inverse).
|
|
506
|
+
* Checks both `relation === relationName` (from side) and `inverse === relationName` (to side).
|
|
507
|
+
*/
|
|
508
|
+
declare function findRelationBidirectional(schema: DatafnSchema, resource: string, relationName: string): DatafnRelationSchema | undefined;
|
|
509
|
+
|
|
510
|
+
/** Map non-$-prefixed operator names to core's $-prefixed equivalents. */
|
|
511
|
+
declare const OP_REMAP: Record<string, string>;
|
|
512
|
+
/** Recursively remap non-$-prefixed operator names to core's $-prefixed form. */
|
|
513
|
+
declare function normalizeFilterOps(filters: Record<string, unknown>): Record<string, unknown>;
|
|
514
|
+
interface FilterEvalOptions {
|
|
515
|
+
resolveRelation?: (resource: string, recordId: string, relationName: string) => Record<string, unknown>[];
|
|
516
|
+
resource?: string;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Evaluate a DFQL filter against a single record.
|
|
520
|
+
* Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $contains, $startsWith, $endsWith,
|
|
521
|
+
* $like, $ilike, $not_like, $not_ilike, $is_null, $is_not_null, $is_empty, $is_not_empty,
|
|
522
|
+
* $before, $after, $between, $not_between, $and, $or.
|
|
523
|
+
* Returns true if the record matches all filter conditions.
|
|
524
|
+
*/
|
|
525
|
+
declare function evaluateFilter(record: Record<string, unknown>, filters: Record<string, unknown>, opts?: FilterEvalOptions, _depth?: number): boolean;
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Shared relation payload normalization utilities.
|
|
529
|
+
* Used by both server mutation executor and offline client.
|
|
530
|
+
*/
|
|
531
|
+
/**
|
|
532
|
+
* Normalized relation payload item.
|
|
533
|
+
*/
|
|
534
|
+
interface NormalizedRelation {
|
|
535
|
+
toId: string;
|
|
536
|
+
metadata: Record<string, unknown>;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Normalize relation payload to consistent format.
|
|
540
|
+
* - String -> [{ toId, metadata: {} }]
|
|
541
|
+
* - String[] -> [{ toId, metadata: {} }, ...]
|
|
542
|
+
* - Object with $ref -> [{ toId: $ref, metadata: {...rest} }]
|
|
543
|
+
* - Array of above -> flatten
|
|
544
|
+
*/
|
|
545
|
+
declare function normalizeRelationPayload(payload: unknown): NormalizedRelation[];
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Shared aggregation computation.
|
|
549
|
+
* Used by both server and client offline aggregate query execution.
|
|
550
|
+
*/
|
|
551
|
+
/**
|
|
552
|
+
* Calculate an aggregation over a set of records for a given op and field.
|
|
553
|
+
* Null-safe: null/undefined values are excluded from sum/min/max/avg.
|
|
554
|
+
* Returns null for empty sets (SQL semantics).
|
|
555
|
+
*/
|
|
556
|
+
declare function calculateAggregation(op: string, field: string, records: Record<string, unknown>[]): unknown;
|
|
557
|
+
|
|
558
|
+
interface SortTerm {
|
|
559
|
+
field: string;
|
|
560
|
+
direction: "asc" | "desc";
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Parse DFQL sort strings into structured sort terms.
|
|
564
|
+
* Handles "field", "field:asc", "field:desc", "-field".
|
|
565
|
+
* Returns [] for undefined or empty input.
|
|
566
|
+
*/
|
|
567
|
+
declare function parseSortTerms(sort: string[] | undefined): SortTerm[];
|
|
568
|
+
/**
|
|
569
|
+
* Sort records by the given terms with a stable id tie-breaker.
|
|
570
|
+
* Null values sort after non-null in ascending order (before in descending).
|
|
571
|
+
*/
|
|
572
|
+
declare function sortRecords(records: Record<string, unknown>[], terms: SortTerm[]): Record<string, unknown>[];
|
|
573
|
+
|
|
574
|
+
interface SelectToken {
|
|
575
|
+
path: string[];
|
|
576
|
+
baseName: string;
|
|
577
|
+
directive: string | undefined;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Parse a DFQL select token string into its structured components.
|
|
581
|
+
* Examples: "id" → {path:["id"], baseName:"id", directive:undefined}
|
|
582
|
+
* "tags.*" → {path:["tags","*"], baseName:"tags", directive:"*"}
|
|
583
|
+
* "tasks.tags.*" → {path:["tasks","tags","*"], baseName:"tasks", directive:"tags.*"}
|
|
584
|
+
*/
|
|
585
|
+
declare function parseSelectToken(token: string): SelectToken;
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Shared date conversion utilities (DTE-001, DTE-002)
|
|
589
|
+
*/
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Convert a Date, ISO string, or epoch number to epoch milliseconds.
|
|
593
|
+
* Idempotent: if already a number, returns it directly.
|
|
594
|
+
*/
|
|
595
|
+
declare function toEpochMs(value: unknown): number;
|
|
596
|
+
/**
|
|
597
|
+
* Convert an epoch number, ISO string, or Date to a Date object.
|
|
598
|
+
* Idempotent: if already a Date, returns it directly.
|
|
599
|
+
*/
|
|
600
|
+
declare function fromEpochMs(value: unknown): Date;
|
|
601
|
+
type FieldLike = Pick<DatafnFieldSchema, "name" | "type">;
|
|
602
|
+
/**
|
|
603
|
+
* Mutate record in place: convert date-typed fields to epoch milliseconds.
|
|
604
|
+
*
|
|
605
|
+
* LOW-024: This function intentionally mutates the record in-place and returns it
|
|
606
|
+
* (callers rely on the mutation side-effect, as verified by existing tests).
|
|
607
|
+
* If a non-mutating variant is needed, create a separate helper that copies first.
|
|
608
|
+
*/
|
|
609
|
+
declare function coerceDateFieldsToEpoch(record: Record<string, unknown>, fields: FieldLike[]): Record<string, unknown>;
|
|
610
|
+
/**
|
|
611
|
+
* Mutate record in place: convert date-typed fields to Date objects.
|
|
612
|
+
*
|
|
613
|
+
* LOW-024: This function intentionally mutates the record in-place and returns it
|
|
614
|
+
* (callers rely on the mutation side-effect, as verified by existing tests).
|
|
615
|
+
* If a non-mutating variant is needed, create a separate helper that copies first.
|
|
616
|
+
*/
|
|
617
|
+
declare function parseDateFieldsToDate(record: Record<string, unknown>, fields: FieldLike[]): Record<string, unknown>;
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Shared validation primitives for DataFn.
|
|
621
|
+
* - Prototype pollution key checking (recursive)
|
|
622
|
+
* - Field value type validation against schema types
|
|
623
|
+
*/
|
|
624
|
+
/**
|
|
625
|
+
* Recursively checks a value for prototype pollution keys.
|
|
626
|
+
* Returns `{ ok: true }` if clean, or `{ ok: false, key }` with the first offending key found.
|
|
627
|
+
*/
|
|
628
|
+
declare function checkPrototypePollution(obj: unknown): {
|
|
629
|
+
ok: boolean;
|
|
630
|
+
key?: string;
|
|
631
|
+
};
|
|
632
|
+
/**
|
|
633
|
+
* Validates a field value against its declared schema type.
|
|
634
|
+
* Returns `{ ok: true }` if valid, or `{ ok: false, error }` with a descriptive message.
|
|
635
|
+
*/
|
|
636
|
+
declare function validateFieldValue(schemaType: string, value: unknown, nullable: boolean): {
|
|
637
|
+
ok: boolean;
|
|
638
|
+
error?: string;
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Generic plugin hook runner (HKS-001)
|
|
643
|
+
* Fail-closed for before hooks, fail-open for after hooks.
|
|
644
|
+
*/
|
|
645
|
+
|
|
646
|
+
interface HookError {
|
|
647
|
+
code: string;
|
|
648
|
+
message: string;
|
|
649
|
+
details?: Record<string, unknown>;
|
|
650
|
+
}
|
|
651
|
+
type BeforeHookResult<T> = {
|
|
652
|
+
ok: true;
|
|
653
|
+
value: T;
|
|
654
|
+
} | {
|
|
655
|
+
ok: false;
|
|
656
|
+
error: HookError;
|
|
657
|
+
};
|
|
658
|
+
/**
|
|
659
|
+
* Run a before-style hook across plugins filtered by env.
|
|
660
|
+
* Fail-closed: first error stops the chain and returns { ok: false, error }.
|
|
661
|
+
* Each hook's return value (if defined) becomes the new payload.
|
|
662
|
+
*
|
|
663
|
+
* @param preArgs Optional args inserted before payload in hook call: hook(ctx, ...preArgs, payload)
|
|
664
|
+
*/
|
|
665
|
+
declare function runBeforeHook<T = unknown>(plugins: DatafnPlugin[], env: "client" | "server", hookName: keyof DatafnPlugin, ctx: DatafnHookContext, payload: T, preArgs?: unknown[]): Promise<BeforeHookResult<T>>;
|
|
666
|
+
/**
|
|
667
|
+
* Run an after-style hook across plugins filtered by env.
|
|
668
|
+
* Fail-open: errors are logged but do not stop the chain.
|
|
669
|
+
*
|
|
670
|
+
* @param preArgs Optional args inserted before payload in hook call: hook(ctx, ...preArgs, payload, result)
|
|
671
|
+
*/
|
|
672
|
+
declare function runAfterHook(plugins: DatafnPlugin[], env: "client" | "server", hookName: keyof DatafnPlugin, ctx: DatafnHookContext, payload: unknown, result: unknown, preArgs?: unknown[]): Promise<void>;
|
|
673
|
+
|
|
674
|
+
interface SearchProvider {
|
|
675
|
+
readonly name: string;
|
|
676
|
+
search(params: {
|
|
677
|
+
resource: string;
|
|
678
|
+
query: string;
|
|
679
|
+
type?: "fullText" | "semantic";
|
|
680
|
+
fields?: string[];
|
|
681
|
+
limit?: number;
|
|
682
|
+
prefix?: boolean;
|
|
683
|
+
fuzzy?: boolean | number;
|
|
684
|
+
fieldBoosts?: Record<string, number>;
|
|
685
|
+
signal?: AbortSignal;
|
|
686
|
+
}): Promise<string[]>;
|
|
687
|
+
searchAll?(params: {
|
|
688
|
+
query: string;
|
|
689
|
+
resources?: string[];
|
|
690
|
+
fields?: string[];
|
|
691
|
+
limit?: number;
|
|
692
|
+
limitPerResource?: number;
|
|
693
|
+
prefix?: boolean;
|
|
694
|
+
fuzzy?: boolean | number;
|
|
695
|
+
fieldBoosts?: Record<string, number>;
|
|
696
|
+
signal?: AbortSignal;
|
|
697
|
+
}): Promise<Array<{
|
|
698
|
+
resource: string;
|
|
699
|
+
id: string;
|
|
700
|
+
score: number;
|
|
701
|
+
}>>;
|
|
702
|
+
updateIndices(params: {
|
|
703
|
+
resource: string;
|
|
704
|
+
records: Record<string, unknown>[];
|
|
705
|
+
operation: "upsert" | "delete";
|
|
706
|
+
}): Promise<void>;
|
|
707
|
+
initialize?(config: {
|
|
708
|
+
resources: Array<{
|
|
709
|
+
name: string;
|
|
710
|
+
searchFields: string[];
|
|
711
|
+
}>;
|
|
712
|
+
}): Promise<void>;
|
|
713
|
+
dispose?(): Promise<void>;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export { type AccessLevel, type BeforeHookResult, CAPABILITY_FIELD_DEFS, type CapabilityEntry, type DatafnEnvelope, type DatafnError, type DatafnErrorCode, type DatafnEvent, type DatafnEventFilter, type DatafnFieldSchema, type DatafnHookContext, type DatafnLimitsConfig, type DatafnLogger, type DatafnPlugin, type DatafnRelationSchema, type DatafnResourceSchema, type DatafnSchema, type DatafnSignal, type DatafnWsConfig, type DfqlCursor, type DfqlMutation, type DfqlMutationFragment, type DfqlQuery, type DfqlQueryFragment, type DfqlSort, type DfqlTransact, type FilterEvalOptions, type HookError, KV_RESOURCE_NAME, type NormalizedRelation, OP_REMAP, RELATION_CAPABILITY_FIELD_DEFS, type RelationSimpleCapability, type ResourceCapabilities, type SchemaCapabilities, type SchemaIndex, type SearchProvider, type SelectToken, type ShareableCapability, type SimpleCapability, type SortTerm, buildSchemaIndex, calculateAggregation, checkPrototypePollution, coerceDateFieldsToEpoch, defineSchema, dfqlKey, ensureBuiltinKv, enumerateJoinStoreKeys, err, evaluateFilter, findRelationBidirectional, fromEpochMs, getCapabilityFields, getField, getJoinStoreKey, getJoinTableName, getRelation, getRelationCapabilityFieldNames, getRelationTarget, getRelationsFrom, getResource, isNamespaced, kvId, normalizeDfql, normalizeFilterOps, normalizeRelationPayload, ns, ok, parseDateFieldsToDate, parseSelectToken, parseSortTerms, resolveCapabilities, resolveRelationCapabilities, runAfterHook, runBeforeHook, sortRecords, toEpochMs, unwrapEnvelope, validateFieldValue, validateSchema };
|