@datafn/core 0.0.1
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 +219 -0
- package/dist/index.cjs +162 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +178 -0
- package/dist/index.d.ts +178 -0
- package/dist/index.js +155 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# @datafn/core
|
|
2
|
+
|
|
3
|
+
Core types, schema validation, and DFQL normalization for DataFn.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @datafn/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Type Definitions**: Complete TypeScript types for schemas, events, signals, and plugins
|
|
14
|
+
- **Schema Validation**: Runtime validation of DataFn schemas
|
|
15
|
+
- **DFQL Normalization**: Deterministic normalization for cache keys
|
|
16
|
+
- **Error Handling**: Structured error types with envelopes
|
|
17
|
+
|
|
18
|
+
## API
|
|
19
|
+
|
|
20
|
+
### Types
|
|
21
|
+
|
|
22
|
+
#### DatafnSchema
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
type DatafnSchema = {
|
|
26
|
+
resources: DatafnResourceSchema[];
|
|
27
|
+
relations?: DatafnRelationSchema[];
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Defines the complete data model including resources (tables) and their relationships.
|
|
32
|
+
|
|
33
|
+
#### DatafnResourceSchema
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
type DatafnResourceSchema = {
|
|
37
|
+
name: string;
|
|
38
|
+
version: number;
|
|
39
|
+
fields: DatafnFieldSchema[];
|
|
40
|
+
idPrefix?: string;
|
|
41
|
+
isRemoteOnly?: boolean;
|
|
42
|
+
indices?:
|
|
43
|
+
| { base?: string[]; search?: string[]; vector?: string[] }
|
|
44
|
+
| string[];
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Defines a resource (table) with fields, version, and optional indices.
|
|
49
|
+
|
|
50
|
+
#### DatafnEvent
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
interface DatafnEvent {
|
|
54
|
+
type:
|
|
55
|
+
| "mutation_applied"
|
|
56
|
+
| "mutation_rejected"
|
|
57
|
+
| "sync_applied"
|
|
58
|
+
| "sync_failed";
|
|
59
|
+
resource?: string;
|
|
60
|
+
ids?: string[];
|
|
61
|
+
mutationId?: string;
|
|
62
|
+
clientId?: string;
|
|
63
|
+
timestampMs: number;
|
|
64
|
+
context?: unknown;
|
|
65
|
+
action?: string;
|
|
66
|
+
fields?: string[];
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Event structure for mutation and sync operations.
|
|
71
|
+
|
|
72
|
+
#### DatafnPlugin & DatafnHookContext
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
type DatafnHookContext = {
|
|
76
|
+
env: "client" | "server";
|
|
77
|
+
schema: DatafnSchema;
|
|
78
|
+
context?: unknown;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
interface DatafnPlugin {
|
|
82
|
+
name: string;
|
|
83
|
+
runsOn: Array<"client" | "server">;
|
|
84
|
+
beforeQuery?: (ctx: DatafnHookContext, q: unknown) => Promise<unknown> | unknown;
|
|
85
|
+
afterQuery?: (ctx: DatafnHookContext, q: unknown, result: unknown) => Promise<unknown> | unknown;
|
|
86
|
+
beforeMutation?: (ctx: DatafnHookContext, m: unknown) => Promise<unknown> | unknown;
|
|
87
|
+
afterMutation?: (ctx: DatafnHookContext, m: unknown, result: unknown) => Promise<void> | void;
|
|
88
|
+
// ... hooks for sync (beforeSync, afterSync)
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Plugins allow intercepting and modifying queries, mutations, and sync operations.
|
|
93
|
+
|
|
94
|
+
### Functions
|
|
95
|
+
|
|
96
|
+
#### validateSchema(schema: unknown): DatafnEnvelope<DatafnSchema>
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { validateSchema, unwrapEnvelope } from "@datafn/core";
|
|
100
|
+
|
|
101
|
+
// Validate and unwrap (throws if invalid)
|
|
102
|
+
const schema = unwrapEnvelope(
|
|
103
|
+
validateSchema({
|
|
104
|
+
resources: [
|
|
105
|
+
{
|
|
106
|
+
name: "user",
|
|
107
|
+
version: 1,
|
|
108
|
+
fields: [
|
|
109
|
+
{ name: "email", type: "string", required: true },
|
|
110
|
+
{ name: "name", type: "string", required: true },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Validates a schema and returns an envelope. Use `unwrapEnvelope` to get the result or throw the error.
|
|
119
|
+
|
|
120
|
+
#### unwrapEnvelope<T>(env: DatafnEnvelope<T>): T
|
|
121
|
+
|
|
122
|
+
Helper to unwrap success result or throw error from an envelope.
|
|
123
|
+
|
|
124
|
+
#### normalizeDfql(dfql: unknown): unknown
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { normalizeDfql } from "@datafn/core";
|
|
128
|
+
|
|
129
|
+
const normalized = normalizeDfql({ b: 2, a: 1, c: undefined });
|
|
130
|
+
// Returns: { a: 1, b: 2 }
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Recursively sorts object keys and removes undefined values for deterministic comparison.
|
|
134
|
+
|
|
135
|
+
#### dfqlKey(dfql: unknown): string
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { dfqlKey } from "@datafn/core";
|
|
139
|
+
|
|
140
|
+
const key = dfqlKey({ resource: "user", filters: { id: "user:1" } });
|
|
141
|
+
// Returns: deterministic JSON string for caching
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Generates a deterministic cache key from DFQL.
|
|
145
|
+
|
|
146
|
+
#### ok<T>(result: T): DatafnEnvelope<T>
|
|
147
|
+
#### err(error: DatafnError): DatafnEnvelope<never>
|
|
148
|
+
|
|
149
|
+
Helpers to create success and error envelopes.
|
|
150
|
+
|
|
151
|
+
### Error Handling
|
|
152
|
+
|
|
153
|
+
DatafnError is a plain object interface, not a class.
|
|
154
|
+
|
|
155
|
+
#### DatafnError
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
interface DatafnError {
|
|
159
|
+
code: DatafnErrorCode;
|
|
160
|
+
message: string;
|
|
161
|
+
details?: {
|
|
162
|
+
path: string;
|
|
163
|
+
[key: string]: unknown;
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The `details.path` property is always present to indicate the location of the error (e.g., `"filters.status"` or `"$"`).
|
|
169
|
+
|
|
170
|
+
**Example:**
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const error: DatafnError = {
|
|
174
|
+
code: "DFQL_INVALID",
|
|
175
|
+
message: "Invalid query filter",
|
|
176
|
+
details: {
|
|
177
|
+
path: "filters.status",
|
|
178
|
+
expected: ["active", "archived"]
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Schema Definition Example
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { DatafnSchema } from "@datafn/core";
|
|
187
|
+
|
|
188
|
+
const schema: DatafnSchema = {
|
|
189
|
+
resources: [
|
|
190
|
+
{
|
|
191
|
+
name: "post",
|
|
192
|
+
version: 1,
|
|
193
|
+
fields: [
|
|
194
|
+
{ name: "title", type: "string", required: true },
|
|
195
|
+
{ name: "content", type: "string", required: true },
|
|
196
|
+
{ name: "authorId", type: "string", required: true },
|
|
197
|
+
{ name: "publishedAt", type: "date", required: false },
|
|
198
|
+
],
|
|
199
|
+
indices: {
|
|
200
|
+
base: ["authorId"],
|
|
201
|
+
search: ["title", "content"],
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
relations: [
|
|
206
|
+
{
|
|
207
|
+
from: "post",
|
|
208
|
+
to: "user",
|
|
209
|
+
type: "many-one",
|
|
210
|
+
relation: "author",
|
|
211
|
+
fkField: "authorId",
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
function ok(result) {
|
|
5
|
+
return { ok: true, result };
|
|
6
|
+
}
|
|
7
|
+
function err(code, message, details) {
|
|
8
|
+
return {
|
|
9
|
+
ok: false,
|
|
10
|
+
error: {
|
|
11
|
+
code,
|
|
12
|
+
message,
|
|
13
|
+
details: details ?? { path: "$" }
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/schema.ts
|
|
19
|
+
function validateSchema(schema) {
|
|
20
|
+
if (typeof schema !== "object" || schema === null || Array.isArray(schema)) {
|
|
21
|
+
return err("SCHEMA_INVALID", "Invalid schema: expected object", {
|
|
22
|
+
path: "$"
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const s = schema;
|
|
26
|
+
if (!s.resources || !Array.isArray(s.resources)) {
|
|
27
|
+
return err("SCHEMA_INVALID", "Invalid schema: missing resources", {
|
|
28
|
+
path: "resources"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const resourceNames = /* @__PURE__ */ new Set();
|
|
32
|
+
const normalizedResources = [];
|
|
33
|
+
for (const resource of s.resources) {
|
|
34
|
+
if (typeof resource !== "object" || resource === null || Array.isArray(resource)) {
|
|
35
|
+
return err("SCHEMA_INVALID", "Invalid schema: resource must be object", {
|
|
36
|
+
path: "resources"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const r = resource;
|
|
40
|
+
if (typeof r.name !== "string") {
|
|
41
|
+
return err(
|
|
42
|
+
"SCHEMA_INVALID",
|
|
43
|
+
"Invalid schema: resource.name must be string",
|
|
44
|
+
{ path: "resources" }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (resourceNames.has(r.name)) {
|
|
48
|
+
return err(
|
|
49
|
+
"SCHEMA_INVALID",
|
|
50
|
+
`Invalid schema: duplicate resource name: ${r.name}`,
|
|
51
|
+
{ path: "resources" }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
resourceNames.add(r.name);
|
|
55
|
+
if (typeof r.version !== "number" || !Number.isInteger(r.version)) {
|
|
56
|
+
return err(
|
|
57
|
+
"SCHEMA_INVALID",
|
|
58
|
+
"Invalid schema: resource.version must be integer",
|
|
59
|
+
{ path: "resources" }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (!Array.isArray(r.fields)) {
|
|
63
|
+
return err(
|
|
64
|
+
"SCHEMA_INVALID",
|
|
65
|
+
"Invalid schema: resource.fields must be array",
|
|
66
|
+
{ path: "resources" }
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
70
|
+
for (const field of r.fields) {
|
|
71
|
+
if (typeof field !== "object" || field === null || Array.isArray(field)) {
|
|
72
|
+
return err("SCHEMA_INVALID", "Invalid schema: field must be object", {
|
|
73
|
+
path: "resources"
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const f = field;
|
|
77
|
+
if (typeof f.name !== "string") {
|
|
78
|
+
return err(
|
|
79
|
+
"SCHEMA_INVALID",
|
|
80
|
+
"Invalid schema: field.name must be string",
|
|
81
|
+
{ path: "resources" }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
if (fieldNames.has(f.name)) {
|
|
85
|
+
return err(
|
|
86
|
+
"SCHEMA_INVALID",
|
|
87
|
+
`Invalid schema: duplicate field name: ${f.name}`,
|
|
88
|
+
{ path: "resources" }
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
fieldNames.add(f.name);
|
|
92
|
+
}
|
|
93
|
+
let normalizedIndices;
|
|
94
|
+
if (Array.isArray(r.indices)) {
|
|
95
|
+
normalizedIndices = {
|
|
96
|
+
base: r.indices,
|
|
97
|
+
search: [],
|
|
98
|
+
vector: []
|
|
99
|
+
};
|
|
100
|
+
} else if (r.indices && typeof r.indices === "object") {
|
|
101
|
+
const idx = r.indices;
|
|
102
|
+
normalizedIndices = {
|
|
103
|
+
base: idx.base || [],
|
|
104
|
+
search: idx.search || [],
|
|
105
|
+
vector: idx.vector || []
|
|
106
|
+
};
|
|
107
|
+
} else {
|
|
108
|
+
normalizedIndices = { base: [], search: [], vector: [] };
|
|
109
|
+
}
|
|
110
|
+
normalizedResources.push({
|
|
111
|
+
...r,
|
|
112
|
+
indices: normalizedIndices
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const relations = Array.isArray(s.relations) ? s.relations : [];
|
|
116
|
+
return ok({
|
|
117
|
+
resources: normalizedResources,
|
|
118
|
+
relations
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/normalize.ts
|
|
123
|
+
function normalizeDfql(value) {
|
|
124
|
+
if (value === null || value === void 0) {
|
|
125
|
+
return value === null ? null : void 0;
|
|
126
|
+
}
|
|
127
|
+
if (typeof value !== "object") {
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(value)) {
|
|
131
|
+
return value.map((item) => normalizeDfql(item));
|
|
132
|
+
}
|
|
133
|
+
const normalized = {};
|
|
134
|
+
const keys = Object.keys(value).sort();
|
|
135
|
+
for (const key of keys) {
|
|
136
|
+
const val = value[key];
|
|
137
|
+
if (val !== void 0) {
|
|
138
|
+
normalized[key] = normalizeDfql(val);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return normalized;
|
|
142
|
+
}
|
|
143
|
+
function dfqlKey(value) {
|
|
144
|
+
return JSON.stringify(normalizeDfql(value));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/envelope.ts
|
|
148
|
+
function unwrapEnvelope(env) {
|
|
149
|
+
if (env.ok) {
|
|
150
|
+
return env.result;
|
|
151
|
+
}
|
|
152
|
+
throw env.error;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
exports.dfqlKey = dfqlKey;
|
|
156
|
+
exports.err = err;
|
|
157
|
+
exports.normalizeDfql = normalizeDfql;
|
|
158
|
+
exports.ok = ok;
|
|
159
|
+
exports.unwrapEnvelope = unwrapEnvelope;
|
|
160
|
+
exports.validateSchema = validateSchema;
|
|
161
|
+
//# sourceMappingURL=index.cjs.map
|
|
162
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/schema.ts","../src/normalize.ts","../src/envelope.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * DataFn Error Types and Envelopes\n */\n\nexport type DatafnErrorCode =\n | \"SCHEMA_INVALID\"\n | \"DFQL_INVALID\"\n | \"DFQL_UNKNOWN_RESOURCE\"\n | \"DFQL_UNKNOWN_FIELD\"\n | \"DFQL_UNKNOWN_RELATION\"\n | \"DFQL_UNSUPPORTED\"\n | \"LIMIT_EXCEEDED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"INTERNAL\";\n\nexport type DatafnError = {\n code: DatafnErrorCode;\n message: string;\n details?: unknown;\n};\n\nexport type DatafnEnvelope<T> =\n | { ok: true; result: T }\n | { ok: false; error: DatafnError };\n\n/**\n * Helper functions for creating envelopes\n */\n\nexport function ok<T>(result: T): DatafnEnvelope<T> {\n return { ok: true, result };\n}\n\nexport function err<T = never>(\n code: DatafnErrorCode,\n message: string,\n details?: unknown\n): DatafnEnvelope<T> {\n return {\n ok: false,\n error: {\n code,\n message,\n details: details ?? { path: \"$\" },\n },\n };\n}\n","/**\n * Schema Validation\n *\n * Validates and normalizes DataFn schemas according to SCHEMA-001.\n */\n\nimport type { DatafnSchema, DatafnResourceSchema } from \"./types.js\";\nimport type { DatafnEnvelope } from \"./errors.js\";\nimport { ok, err } from \"./errors.js\";\n\n/**\n * Validates a schema and returns a normalized version.\n *\n * Normalization:\n * - Converts `indices: string[]` to `{ base: string[], search: [], vector: [] }`\n * - Ensures `relations` is present (defaults to [])\n *\n * Validation:\n * - `resources` must be present and be an array\n * - Each resource must have unique `name` and integer `version`\n * - Fields must have unique names within a resource\n */\nexport function validateSchema(schema: unknown): DatafnEnvelope<DatafnSchema> {\n // Check that schema is an object\n if (typeof schema !== \"object\" || schema === null || Array.isArray(schema)) {\n return err(\"SCHEMA_INVALID\", \"Invalid schema: expected object\", {\n path: \"$\",\n });\n }\n\n const s = schema as Record<string, unknown>;\n\n // Check resources exists and is an array\n if (!s.resources || !Array.isArray(s.resources)) {\n return err(\"SCHEMA_INVALID\", \"Invalid schema: missing resources\", {\n path: \"resources\",\n });\n }\n\n const resourceNames = new Set<string>();\n const normalizedResources: DatafnResourceSchema[] = [];\n\n for (const resource of s.resources) {\n if (\n typeof resource !== \"object\" ||\n resource === null ||\n Array.isArray(resource)\n ) {\n return err(\"SCHEMA_INVALID\", \"Invalid schema: resource must be object\", {\n path: \"resources\",\n });\n }\n\n const r = resource as Record<string, unknown>;\n\n // Validate name\n if (typeof r.name !== \"string\") {\n return err(\n \"SCHEMA_INVALID\",\n \"Invalid schema: resource.name must be string\",\n { path: \"resources\" }\n );\n }\n\n // Check for duplicate resource names\n if (resourceNames.has(r.name)) {\n return err(\n \"SCHEMA_INVALID\",\n `Invalid schema: duplicate resource name: ${r.name}`,\n { path: \"resources\" }\n );\n }\n resourceNames.add(r.name);\n\n // Validate version\n if (typeof r.version !== \"number\" || !Number.isInteger(r.version)) {\n return err(\n \"SCHEMA_INVALID\",\n \"Invalid schema: resource.version must be integer\",\n { path: \"resources\" }\n );\n }\n\n // Validate fields\n if (!Array.isArray(r.fields)) {\n return err(\n \"SCHEMA_INVALID\",\n \"Invalid schema: resource.fields must be array\",\n { path: \"resources\" }\n );\n }\n\n const fieldNames = new Set<string>();\n for (const field of r.fields) {\n if (typeof field !== \"object\" || field === null || Array.isArray(field)) {\n return err(\"SCHEMA_INVALID\", \"Invalid schema: field must be object\", {\n path: \"resources\",\n });\n }\n const f = field as Record<string, unknown>;\n if (typeof f.name !== \"string\") {\n return err(\n \"SCHEMA_INVALID\",\n \"Invalid schema: field.name must be string\",\n { path: \"resources\" }\n );\n }\n if (fieldNames.has(f.name)) {\n return err(\n \"SCHEMA_INVALID\",\n `Invalid schema: duplicate field name: ${f.name}`,\n { path: \"resources\" }\n );\n }\n fieldNames.add(f.name);\n }\n\n // Normalize indices\n let normalizedIndices: {\n base: string[];\n search: string[];\n vector: string[];\n };\n if (Array.isArray(r.indices)) {\n normalizedIndices = {\n base: r.indices as string[],\n search: [],\n vector: [],\n };\n } else if (r.indices && typeof r.indices === \"object\") {\n const idx = r.indices as Record<string, unknown>;\n normalizedIndices = {\n base: (idx.base as string[]) || [],\n search: (idx.search as string[]) || [],\n vector: (idx.vector as string[]) || [],\n };\n } else {\n normalizedIndices = { base: [], search: [], vector: [] };\n }\n\n normalizedResources.push({\n ...r,\n indices: normalizedIndices,\n } as DatafnResourceSchema);\n }\n\n // Normalize relations (default to empty array)\n const relations = Array.isArray(s.relations) ? s.relations : [];\n\n return ok({\n resources: normalizedResources,\n relations: relations as DatafnSchema[\"relations\"],\n });\n}\n","/**\n * DFQL Normalization\n *\n * Produces canonical JSON for DFQL objects to enable stable cache keys\n * and deterministic comparisons.\n */\n\n/**\n * Recursively normalizes a value:\n * - Sorts object keys alphabetically\n * - Removes undefined values\n * - Preserves arrays, primitives, and null as-is\n */\nexport function normalizeDfql(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value === null ? null : undefined;\n }\n\n if (typeof value !== \"object\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => normalizeDfql(item));\n }\n\n // Object: sort keys, remove undefined values, recursively normalize\n const normalized: Record<string, unknown> = {};\n const keys = Object.keys(value as Record<string, unknown>).sort();\n\n for (const key of keys) {\n const val = (value as Record<string, unknown>)[key];\n if (val !== undefined) {\n normalized[key] = normalizeDfql(val);\n }\n }\n\n return normalized;\n}\n\n/**\n * Returns a stable string key for a DFQL value.\n * This is the canonical form used for caching and comparison.\n */\nexport function dfqlKey(value: unknown): string {\n return JSON.stringify(normalizeDfql(value));\n}\n","/**\n * DataFn Envelope Utilities\n */\n\nimport type { DatafnEnvelope, DatafnError } from \"./errors.js\";\n\n/**\n * Unwraps a DatafnEnvelope, returning the result for ok:true\n * or throwing the exact error object for ok:false.\n *\n * This provides deterministic error handling for envelope-returning functions.\n *\n * @param env - The envelope to unwrap\n * @returns The result value for success envelopes\n * @throws The exact DatafnError object for failure envelopes\n */\nexport function unwrapEnvelope<T>(env: DatafnEnvelope<T>): T {\n if (env.ok) {\n return env.result;\n }\n // Throw the exact error object (code/message/details.path match)\n throw env.error;\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataFn Schema Types
|
|
3
|
+
*/
|
|
4
|
+
type DatafnSchema = {
|
|
5
|
+
resources: DatafnResourceSchema[];
|
|
6
|
+
relations?: DatafnRelationSchema[];
|
|
7
|
+
};
|
|
8
|
+
type DatafnResourceSchema = {
|
|
9
|
+
name: string;
|
|
10
|
+
version: number;
|
|
11
|
+
idPrefix?: string;
|
|
12
|
+
isRemoteOnly?: boolean;
|
|
13
|
+
fields: DatafnFieldSchema[];
|
|
14
|
+
indices?: {
|
|
15
|
+
base?: string[];
|
|
16
|
+
search?: string[];
|
|
17
|
+
vector?: string[];
|
|
18
|
+
} | string[];
|
|
19
|
+
permissions?: unknown;
|
|
20
|
+
};
|
|
21
|
+
type DatafnFieldSchema = {
|
|
22
|
+
name: string;
|
|
23
|
+
type: "string" | "number" | "boolean" | "object" | "array" | "date" | "file";
|
|
24
|
+
required: boolean;
|
|
25
|
+
nullable?: boolean;
|
|
26
|
+
readonly?: boolean;
|
|
27
|
+
default?: unknown;
|
|
28
|
+
enum?: unknown[];
|
|
29
|
+
min?: number;
|
|
30
|
+
max?: number;
|
|
31
|
+
minLength?: number;
|
|
32
|
+
maxLength?: number;
|
|
33
|
+
pattern?: string;
|
|
34
|
+
unique?: boolean | string;
|
|
35
|
+
encrypt?: boolean;
|
|
36
|
+
volatile?: boolean;
|
|
37
|
+
};
|
|
38
|
+
type DatafnRelationSchema = {
|
|
39
|
+
from: string | string[];
|
|
40
|
+
to: string | string[];
|
|
41
|
+
type: "one-many" | "many-one" | "many-many" | "htree";
|
|
42
|
+
relation?: string;
|
|
43
|
+
inverse?: string;
|
|
44
|
+
cache?: boolean;
|
|
45
|
+
metadata?: Array<{
|
|
46
|
+
name: string;
|
|
47
|
+
type: "string" | "number" | "boolean" | "date" | "object";
|
|
48
|
+
}>;
|
|
49
|
+
fkField?: string;
|
|
50
|
+
pathField?: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Event Types
|
|
54
|
+
*/
|
|
55
|
+
interface DatafnEvent {
|
|
56
|
+
type: "mutation_applied" | "mutation_rejected" | "sync_applied" | "sync_failed";
|
|
57
|
+
resource?: string;
|
|
58
|
+
ids?: string[];
|
|
59
|
+
mutationId?: string;
|
|
60
|
+
clientId?: string;
|
|
61
|
+
timestampMs: number;
|
|
62
|
+
context?: unknown;
|
|
63
|
+
action?: string;
|
|
64
|
+
fields?: string[];
|
|
65
|
+
}
|
|
66
|
+
type DatafnEventFilter = Partial<{
|
|
67
|
+
type: DatafnEvent["type"] | Array<DatafnEvent["type"]>;
|
|
68
|
+
resource: string | string[];
|
|
69
|
+
ids: string | string[];
|
|
70
|
+
mutationId: string | string[];
|
|
71
|
+
action: string | string[];
|
|
72
|
+
fields: string | string[];
|
|
73
|
+
contextKeys: string[];
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Signal Type
|
|
77
|
+
*/
|
|
78
|
+
interface DatafnSignal<T> {
|
|
79
|
+
get(): T;
|
|
80
|
+
subscribe(handler: (value: T) => void): () => void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Plugin Types
|
|
84
|
+
*/
|
|
85
|
+
type DatafnHookContext = {
|
|
86
|
+
env: "client" | "server";
|
|
87
|
+
schema: DatafnSchema;
|
|
88
|
+
context?: unknown;
|
|
89
|
+
};
|
|
90
|
+
interface DatafnPlugin {
|
|
91
|
+
name: string;
|
|
92
|
+
runsOn: Array<"client" | "server">;
|
|
93
|
+
beforeQuery?: (ctx: DatafnHookContext, q: unknown) => Promise<unknown> | unknown;
|
|
94
|
+
afterQuery?: (ctx: DatafnHookContext, q: unknown, result: unknown) => Promise<unknown> | unknown;
|
|
95
|
+
beforeMutation?: (ctx: DatafnHookContext, m: unknown | unknown[]) => Promise<unknown> | unknown;
|
|
96
|
+
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;
|
|
99
|
+
}
|
|
100
|
+
|
|
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
|
+
/**
|
|
124
|
+
* Schema Validation
|
|
125
|
+
*
|
|
126
|
+
* Validates and normalizes DataFn schemas according to SCHEMA-001.
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validates a schema and returns a normalized version.
|
|
131
|
+
*
|
|
132
|
+
* Normalization:
|
|
133
|
+
* - Converts `indices: string[]` to `{ base: string[], search: [], vector: [] }`
|
|
134
|
+
* - Ensures `relations` is present (defaults to [])
|
|
135
|
+
*
|
|
136
|
+
* Validation:
|
|
137
|
+
* - `resources` must be present and be an array
|
|
138
|
+
* - Each resource must have unique `name` and integer `version`
|
|
139
|
+
* - Fields must have unique names within a resource
|
|
140
|
+
*/
|
|
141
|
+
declare function validateSchema(schema: unknown): DatafnEnvelope<DatafnSchema>;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* DFQL Normalization
|
|
145
|
+
*
|
|
146
|
+
* Produces canonical JSON for DFQL objects to enable stable cache keys
|
|
147
|
+
* and deterministic comparisons.
|
|
148
|
+
*/
|
|
149
|
+
/**
|
|
150
|
+
* Recursively normalizes a value:
|
|
151
|
+
* - Sorts object keys alphabetically
|
|
152
|
+
* - Removes undefined values
|
|
153
|
+
* - Preserves arrays, primitives, and null as-is
|
|
154
|
+
*/
|
|
155
|
+
declare function normalizeDfql(value: unknown): unknown;
|
|
156
|
+
/**
|
|
157
|
+
* Returns a stable string key for a DFQL value.
|
|
158
|
+
* This is the canonical form used for caching and comparison.
|
|
159
|
+
*/
|
|
160
|
+
declare function dfqlKey(value: unknown): string;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* DataFn Envelope Utilities
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Unwraps a DatafnEnvelope, returning the result for ok:true
|
|
168
|
+
* or throwing the exact error object for ok:false.
|
|
169
|
+
*
|
|
170
|
+
* This provides deterministic error handling for envelope-returning functions.
|
|
171
|
+
*
|
|
172
|
+
* @param env - The envelope to unwrap
|
|
173
|
+
* @returns The result value for success envelopes
|
|
174
|
+
* @throws The exact DatafnError object for failure envelopes
|
|
175
|
+
*/
|
|
176
|
+
declare function unwrapEnvelope<T>(env: DatafnEnvelope<T>): T;
|
|
177
|
+
|
|
178
|
+
export { type DatafnEnvelope, type DatafnError, type DatafnErrorCode, type DatafnEvent, type DatafnEventFilter, type DatafnFieldSchema, type DatafnHookContext, type DatafnPlugin, type DatafnRelationSchema, type DatafnResourceSchema, type DatafnSchema, type DatafnSignal, dfqlKey, err, normalizeDfql, ok, unwrapEnvelope, validateSchema };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataFn Schema Types
|
|
3
|
+
*/
|
|
4
|
+
type DatafnSchema = {
|
|
5
|
+
resources: DatafnResourceSchema[];
|
|
6
|
+
relations?: DatafnRelationSchema[];
|
|
7
|
+
};
|
|
8
|
+
type DatafnResourceSchema = {
|
|
9
|
+
name: string;
|
|
10
|
+
version: number;
|
|
11
|
+
idPrefix?: string;
|
|
12
|
+
isRemoteOnly?: boolean;
|
|
13
|
+
fields: DatafnFieldSchema[];
|
|
14
|
+
indices?: {
|
|
15
|
+
base?: string[];
|
|
16
|
+
search?: string[];
|
|
17
|
+
vector?: string[];
|
|
18
|
+
} | string[];
|
|
19
|
+
permissions?: unknown;
|
|
20
|
+
};
|
|
21
|
+
type DatafnFieldSchema = {
|
|
22
|
+
name: string;
|
|
23
|
+
type: "string" | "number" | "boolean" | "object" | "array" | "date" | "file";
|
|
24
|
+
required: boolean;
|
|
25
|
+
nullable?: boolean;
|
|
26
|
+
readonly?: boolean;
|
|
27
|
+
default?: unknown;
|
|
28
|
+
enum?: unknown[];
|
|
29
|
+
min?: number;
|
|
30
|
+
max?: number;
|
|
31
|
+
minLength?: number;
|
|
32
|
+
maxLength?: number;
|
|
33
|
+
pattern?: string;
|
|
34
|
+
unique?: boolean | string;
|
|
35
|
+
encrypt?: boolean;
|
|
36
|
+
volatile?: boolean;
|
|
37
|
+
};
|
|
38
|
+
type DatafnRelationSchema = {
|
|
39
|
+
from: string | string[];
|
|
40
|
+
to: string | string[];
|
|
41
|
+
type: "one-many" | "many-one" | "many-many" | "htree";
|
|
42
|
+
relation?: string;
|
|
43
|
+
inverse?: string;
|
|
44
|
+
cache?: boolean;
|
|
45
|
+
metadata?: Array<{
|
|
46
|
+
name: string;
|
|
47
|
+
type: "string" | "number" | "boolean" | "date" | "object";
|
|
48
|
+
}>;
|
|
49
|
+
fkField?: string;
|
|
50
|
+
pathField?: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Event Types
|
|
54
|
+
*/
|
|
55
|
+
interface DatafnEvent {
|
|
56
|
+
type: "mutation_applied" | "mutation_rejected" | "sync_applied" | "sync_failed";
|
|
57
|
+
resource?: string;
|
|
58
|
+
ids?: string[];
|
|
59
|
+
mutationId?: string;
|
|
60
|
+
clientId?: string;
|
|
61
|
+
timestampMs: number;
|
|
62
|
+
context?: unknown;
|
|
63
|
+
action?: string;
|
|
64
|
+
fields?: string[];
|
|
65
|
+
}
|
|
66
|
+
type DatafnEventFilter = Partial<{
|
|
67
|
+
type: DatafnEvent["type"] | Array<DatafnEvent["type"]>;
|
|
68
|
+
resource: string | string[];
|
|
69
|
+
ids: string | string[];
|
|
70
|
+
mutationId: string | string[];
|
|
71
|
+
action: string | string[];
|
|
72
|
+
fields: string | string[];
|
|
73
|
+
contextKeys: string[];
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Signal Type
|
|
77
|
+
*/
|
|
78
|
+
interface DatafnSignal<T> {
|
|
79
|
+
get(): T;
|
|
80
|
+
subscribe(handler: (value: T) => void): () => void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Plugin Types
|
|
84
|
+
*/
|
|
85
|
+
type DatafnHookContext = {
|
|
86
|
+
env: "client" | "server";
|
|
87
|
+
schema: DatafnSchema;
|
|
88
|
+
context?: unknown;
|
|
89
|
+
};
|
|
90
|
+
interface DatafnPlugin {
|
|
91
|
+
name: string;
|
|
92
|
+
runsOn: Array<"client" | "server">;
|
|
93
|
+
beforeQuery?: (ctx: DatafnHookContext, q: unknown) => Promise<unknown> | unknown;
|
|
94
|
+
afterQuery?: (ctx: DatafnHookContext, q: unknown, result: unknown) => Promise<unknown> | unknown;
|
|
95
|
+
beforeMutation?: (ctx: DatafnHookContext, m: unknown | unknown[]) => Promise<unknown> | unknown;
|
|
96
|
+
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;
|
|
99
|
+
}
|
|
100
|
+
|
|
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
|
+
/**
|
|
124
|
+
* Schema Validation
|
|
125
|
+
*
|
|
126
|
+
* Validates and normalizes DataFn schemas according to SCHEMA-001.
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Validates a schema and returns a normalized version.
|
|
131
|
+
*
|
|
132
|
+
* Normalization:
|
|
133
|
+
* - Converts `indices: string[]` to `{ base: string[], search: [], vector: [] }`
|
|
134
|
+
* - Ensures `relations` is present (defaults to [])
|
|
135
|
+
*
|
|
136
|
+
* Validation:
|
|
137
|
+
* - `resources` must be present and be an array
|
|
138
|
+
* - Each resource must have unique `name` and integer `version`
|
|
139
|
+
* - Fields must have unique names within a resource
|
|
140
|
+
*/
|
|
141
|
+
declare function validateSchema(schema: unknown): DatafnEnvelope<DatafnSchema>;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* DFQL Normalization
|
|
145
|
+
*
|
|
146
|
+
* Produces canonical JSON for DFQL objects to enable stable cache keys
|
|
147
|
+
* and deterministic comparisons.
|
|
148
|
+
*/
|
|
149
|
+
/**
|
|
150
|
+
* Recursively normalizes a value:
|
|
151
|
+
* - Sorts object keys alphabetically
|
|
152
|
+
* - Removes undefined values
|
|
153
|
+
* - Preserves arrays, primitives, and null as-is
|
|
154
|
+
*/
|
|
155
|
+
declare function normalizeDfql(value: unknown): unknown;
|
|
156
|
+
/**
|
|
157
|
+
* Returns a stable string key for a DFQL value.
|
|
158
|
+
* This is the canonical form used for caching and comparison.
|
|
159
|
+
*/
|
|
160
|
+
declare function dfqlKey(value: unknown): string;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* DataFn Envelope Utilities
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Unwraps a DatafnEnvelope, returning the result for ok:true
|
|
168
|
+
* or throwing the exact error object for ok:false.
|
|
169
|
+
*
|
|
170
|
+
* This provides deterministic error handling for envelope-returning functions.
|
|
171
|
+
*
|
|
172
|
+
* @param env - The envelope to unwrap
|
|
173
|
+
* @returns The result value for success envelopes
|
|
174
|
+
* @throws The exact DatafnError object for failure envelopes
|
|
175
|
+
*/
|
|
176
|
+
declare function unwrapEnvelope<T>(env: DatafnEnvelope<T>): T;
|
|
177
|
+
|
|
178
|
+
export { type DatafnEnvelope, type DatafnError, type DatafnErrorCode, type DatafnEvent, type DatafnEventFilter, type DatafnFieldSchema, type DatafnHookContext, type DatafnPlugin, type DatafnRelationSchema, type DatafnResourceSchema, type DatafnSchema, type DatafnSignal, dfqlKey, err, normalizeDfql, ok, unwrapEnvelope, validateSchema };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
function ok(result) {
|
|
3
|
+
return { ok: true, result };
|
|
4
|
+
}
|
|
5
|
+
function err(code, message, details) {
|
|
6
|
+
return {
|
|
7
|
+
ok: false,
|
|
8
|
+
error: {
|
|
9
|
+
code,
|
|
10
|
+
message,
|
|
11
|
+
details: details ?? { path: "$" }
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/schema.ts
|
|
17
|
+
function validateSchema(schema) {
|
|
18
|
+
if (typeof schema !== "object" || schema === null || Array.isArray(schema)) {
|
|
19
|
+
return err("SCHEMA_INVALID", "Invalid schema: expected object", {
|
|
20
|
+
path: "$"
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const s = schema;
|
|
24
|
+
if (!s.resources || !Array.isArray(s.resources)) {
|
|
25
|
+
return err("SCHEMA_INVALID", "Invalid schema: missing resources", {
|
|
26
|
+
path: "resources"
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const resourceNames = /* @__PURE__ */ new Set();
|
|
30
|
+
const normalizedResources = [];
|
|
31
|
+
for (const resource of s.resources) {
|
|
32
|
+
if (typeof resource !== "object" || resource === null || Array.isArray(resource)) {
|
|
33
|
+
return err("SCHEMA_INVALID", "Invalid schema: resource must be object", {
|
|
34
|
+
path: "resources"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const r = resource;
|
|
38
|
+
if (typeof r.name !== "string") {
|
|
39
|
+
return err(
|
|
40
|
+
"SCHEMA_INVALID",
|
|
41
|
+
"Invalid schema: resource.name must be string",
|
|
42
|
+
{ path: "resources" }
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (resourceNames.has(r.name)) {
|
|
46
|
+
return err(
|
|
47
|
+
"SCHEMA_INVALID",
|
|
48
|
+
`Invalid schema: duplicate resource name: ${r.name}`,
|
|
49
|
+
{ path: "resources" }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
resourceNames.add(r.name);
|
|
53
|
+
if (typeof r.version !== "number" || !Number.isInteger(r.version)) {
|
|
54
|
+
return err(
|
|
55
|
+
"SCHEMA_INVALID",
|
|
56
|
+
"Invalid schema: resource.version must be integer",
|
|
57
|
+
{ path: "resources" }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (!Array.isArray(r.fields)) {
|
|
61
|
+
return err(
|
|
62
|
+
"SCHEMA_INVALID",
|
|
63
|
+
"Invalid schema: resource.fields must be array",
|
|
64
|
+
{ path: "resources" }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
68
|
+
for (const field of r.fields) {
|
|
69
|
+
if (typeof field !== "object" || field === null || Array.isArray(field)) {
|
|
70
|
+
return err("SCHEMA_INVALID", "Invalid schema: field must be object", {
|
|
71
|
+
path: "resources"
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const f = field;
|
|
75
|
+
if (typeof f.name !== "string") {
|
|
76
|
+
return err(
|
|
77
|
+
"SCHEMA_INVALID",
|
|
78
|
+
"Invalid schema: field.name must be string",
|
|
79
|
+
{ path: "resources" }
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (fieldNames.has(f.name)) {
|
|
83
|
+
return err(
|
|
84
|
+
"SCHEMA_INVALID",
|
|
85
|
+
`Invalid schema: duplicate field name: ${f.name}`,
|
|
86
|
+
{ path: "resources" }
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
fieldNames.add(f.name);
|
|
90
|
+
}
|
|
91
|
+
let normalizedIndices;
|
|
92
|
+
if (Array.isArray(r.indices)) {
|
|
93
|
+
normalizedIndices = {
|
|
94
|
+
base: r.indices,
|
|
95
|
+
search: [],
|
|
96
|
+
vector: []
|
|
97
|
+
};
|
|
98
|
+
} else if (r.indices && typeof r.indices === "object") {
|
|
99
|
+
const idx = r.indices;
|
|
100
|
+
normalizedIndices = {
|
|
101
|
+
base: idx.base || [],
|
|
102
|
+
search: idx.search || [],
|
|
103
|
+
vector: idx.vector || []
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
normalizedIndices = { base: [], search: [], vector: [] };
|
|
107
|
+
}
|
|
108
|
+
normalizedResources.push({
|
|
109
|
+
...r,
|
|
110
|
+
indices: normalizedIndices
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const relations = Array.isArray(s.relations) ? s.relations : [];
|
|
114
|
+
return ok({
|
|
115
|
+
resources: normalizedResources,
|
|
116
|
+
relations
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/normalize.ts
|
|
121
|
+
function normalizeDfql(value) {
|
|
122
|
+
if (value === null || value === void 0) {
|
|
123
|
+
return value === null ? null : void 0;
|
|
124
|
+
}
|
|
125
|
+
if (typeof value !== "object") {
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
return value.map((item) => normalizeDfql(item));
|
|
130
|
+
}
|
|
131
|
+
const normalized = {};
|
|
132
|
+
const keys = Object.keys(value).sort();
|
|
133
|
+
for (const key of keys) {
|
|
134
|
+
const val = value[key];
|
|
135
|
+
if (val !== void 0) {
|
|
136
|
+
normalized[key] = normalizeDfql(val);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return normalized;
|
|
140
|
+
}
|
|
141
|
+
function dfqlKey(value) {
|
|
142
|
+
return JSON.stringify(normalizeDfql(value));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/envelope.ts
|
|
146
|
+
function unwrapEnvelope(env) {
|
|
147
|
+
if (env.ok) {
|
|
148
|
+
return env.result;
|
|
149
|
+
}
|
|
150
|
+
throw env.error;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export { dfqlKey, err, normalizeDfql, ok, unwrapEnvelope, validateSchema };
|
|
154
|
+
//# sourceMappingURL=index.js.map
|
|
155
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/schema.ts","../src/normalize.ts","../src/envelope.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * DataFn Error Types and Envelopes\n */\n\nexport type DatafnErrorCode =\n | \"SCHEMA_INVALID\"\n | \"DFQL_INVALID\"\n | \"DFQL_UNKNOWN_RESOURCE\"\n | \"DFQL_UNKNOWN_FIELD\"\n | \"DFQL_UNKNOWN_RELATION\"\n | \"DFQL_UNSUPPORTED\"\n | \"LIMIT_EXCEEDED\"\n | \"FORBIDDEN\"\n | \"NOT_FOUND\"\n | \"CONFLICT\"\n | \"INTERNAL\";\n\nexport type DatafnError = {\n code: DatafnErrorCode;\n message: string;\n details?: unknown;\n};\n\nexport type DatafnEnvelope<T> =\n | { ok: true; result: T }\n | { ok: false; error: DatafnError };\n\n/**\n * Helper functions for creating envelopes\n */\n\nexport function ok<T>(result: T): DatafnEnvelope<T> {\n return { ok: true, result };\n}\n\nexport function err<T = never>(\n code: DatafnErrorCode,\n message: string,\n details?: unknown\n): DatafnEnvelope<T> {\n return {\n ok: false,\n error: {\n code,\n message,\n details: details ?? { path: \"$\" },\n },\n };\n}\n","/**\n * Schema Validation\n *\n * Validates and normalizes DataFn schemas according to SCHEMA-001.\n */\n\nimport type { DatafnSchema, DatafnResourceSchema } from \"./types.js\";\nimport type { DatafnEnvelope } from \"./errors.js\";\nimport { ok, err } from \"./errors.js\";\n\n/**\n * Validates a schema and returns a normalized version.\n *\n * Normalization:\n * - Converts `indices: string[]` to `{ base: string[], search: [], vector: [] }`\n * - Ensures `relations` is present (defaults to [])\n *\n * Validation:\n * - `resources` must be present and be an array\n * - Each resource must have unique `name` and integer `version`\n * - Fields must have unique names within a resource\n */\nexport function validateSchema(schema: unknown): DatafnEnvelope<DatafnSchema> {\n // Check that schema is an object\n if (typeof schema !== \"object\" || schema === null || Array.isArray(schema)) {\n return err(\"SCHEMA_INVALID\", \"Invalid schema: expected object\", {\n path: \"$\",\n });\n }\n\n const s = schema as Record<string, unknown>;\n\n // Check resources exists and is an array\n if (!s.resources || !Array.isArray(s.resources)) {\n return err(\"SCHEMA_INVALID\", \"Invalid schema: missing resources\", {\n path: \"resources\",\n });\n }\n\n const resourceNames = new Set<string>();\n const normalizedResources: DatafnResourceSchema[] = [];\n\n for (const resource of s.resources) {\n if (\n typeof resource !== \"object\" ||\n resource === null ||\n Array.isArray(resource)\n ) {\n return err(\"SCHEMA_INVALID\", \"Invalid schema: resource must be object\", {\n path: \"resources\",\n });\n }\n\n const r = resource as Record<string, unknown>;\n\n // Validate name\n if (typeof r.name !== \"string\") {\n return err(\n \"SCHEMA_INVALID\",\n \"Invalid schema: resource.name must be string\",\n { path: \"resources\" }\n );\n }\n\n // Check for duplicate resource names\n if (resourceNames.has(r.name)) {\n return err(\n \"SCHEMA_INVALID\",\n `Invalid schema: duplicate resource name: ${r.name}`,\n { path: \"resources\" }\n );\n }\n resourceNames.add(r.name);\n\n // Validate version\n if (typeof r.version !== \"number\" || !Number.isInteger(r.version)) {\n return err(\n \"SCHEMA_INVALID\",\n \"Invalid schema: resource.version must be integer\",\n { path: \"resources\" }\n );\n }\n\n // Validate fields\n if (!Array.isArray(r.fields)) {\n return err(\n \"SCHEMA_INVALID\",\n \"Invalid schema: resource.fields must be array\",\n { path: \"resources\" }\n );\n }\n\n const fieldNames = new Set<string>();\n for (const field of r.fields) {\n if (typeof field !== \"object\" || field === null || Array.isArray(field)) {\n return err(\"SCHEMA_INVALID\", \"Invalid schema: field must be object\", {\n path: \"resources\",\n });\n }\n const f = field as Record<string, unknown>;\n if (typeof f.name !== \"string\") {\n return err(\n \"SCHEMA_INVALID\",\n \"Invalid schema: field.name must be string\",\n { path: \"resources\" }\n );\n }\n if (fieldNames.has(f.name)) {\n return err(\n \"SCHEMA_INVALID\",\n `Invalid schema: duplicate field name: ${f.name}`,\n { path: \"resources\" }\n );\n }\n fieldNames.add(f.name);\n }\n\n // Normalize indices\n let normalizedIndices: {\n base: string[];\n search: string[];\n vector: string[];\n };\n if (Array.isArray(r.indices)) {\n normalizedIndices = {\n base: r.indices as string[],\n search: [],\n vector: [],\n };\n } else if (r.indices && typeof r.indices === \"object\") {\n const idx = r.indices as Record<string, unknown>;\n normalizedIndices = {\n base: (idx.base as string[]) || [],\n search: (idx.search as string[]) || [],\n vector: (idx.vector as string[]) || [],\n };\n } else {\n normalizedIndices = { base: [], search: [], vector: [] };\n }\n\n normalizedResources.push({\n ...r,\n indices: normalizedIndices,\n } as DatafnResourceSchema);\n }\n\n // Normalize relations (default to empty array)\n const relations = Array.isArray(s.relations) ? s.relations : [];\n\n return ok({\n resources: normalizedResources,\n relations: relations as DatafnSchema[\"relations\"],\n });\n}\n","/**\n * DFQL Normalization\n *\n * Produces canonical JSON for DFQL objects to enable stable cache keys\n * and deterministic comparisons.\n */\n\n/**\n * Recursively normalizes a value:\n * - Sorts object keys alphabetically\n * - Removes undefined values\n * - Preserves arrays, primitives, and null as-is\n */\nexport function normalizeDfql(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value === null ? null : undefined;\n }\n\n if (typeof value !== \"object\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => normalizeDfql(item));\n }\n\n // Object: sort keys, remove undefined values, recursively normalize\n const normalized: Record<string, unknown> = {};\n const keys = Object.keys(value as Record<string, unknown>).sort();\n\n for (const key of keys) {\n const val = (value as Record<string, unknown>)[key];\n if (val !== undefined) {\n normalized[key] = normalizeDfql(val);\n }\n }\n\n return normalized;\n}\n\n/**\n * Returns a stable string key for a DFQL value.\n * This is the canonical form used for caching and comparison.\n */\nexport function dfqlKey(value: unknown): string {\n return JSON.stringify(normalizeDfql(value));\n}\n","/**\n * DataFn Envelope Utilities\n */\n\nimport type { DatafnEnvelope, DatafnError } from \"./errors.js\";\n\n/**\n * Unwraps a DatafnEnvelope, returning the result for ok:true\n * or throwing the exact error object for ok:false.\n *\n * This provides deterministic error handling for envelope-returning functions.\n *\n * @param env - The envelope to unwrap\n * @returns The result value for success envelopes\n * @throws The exact DatafnError object for failure envelopes\n */\nexport function unwrapEnvelope<T>(env: DatafnEnvelope<T>): T {\n if (env.ok) {\n return env.result;\n }\n // Throw the exact error object (code/message/details.path match)\n throw env.error;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@datafn/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Core types and utilities for datafn - schema validation, DFQL normalization, and shared types",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.cjs",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"build:watch": "tsup --watch",
|
|
23
|
+
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
24
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
25
|
+
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest watch",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.18.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"datafn",
|
|
35
|
+
"schema",
|
|
36
|
+
"validation",
|
|
37
|
+
"dfql"
|
|
38
|
+
],
|
|
39
|
+
"author": "21n",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/21nCo/super-functions.git",
|
|
43
|
+
"directory": "datafn/core"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"sideEffects": false,
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/21nCo/super-functions/issues"
|
|
51
|
+
},
|
|
52
|
+
"superfunctions": {
|
|
53
|
+
"initFunction": "dataFn",
|
|
54
|
+
"schemaVersion": 1,
|
|
55
|
+
"namespace": "datafn"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^24.9.1",
|
|
59
|
+
"tsup": "^8.5.0",
|
|
60
|
+
"typescript": "^5.9.3",
|
|
61
|
+
"vitest": "^3.2.4"
|
|
62
|
+
}
|
|
63
|
+
}
|