@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 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"]}
@@ -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 };
@@ -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
+ }