ai-database 2.0.2 → 2.1.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/CHANGELOG.md +36 -0
- package/dist/actions.d.ts +247 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +260 -0
- package/dist/actions.js.map +1 -0
- package/dist/ai-promise-db.d.ts +34 -2
- package/dist/ai-promise-db.d.ts.map +1 -1
- package/dist/ai-promise-db.js +511 -66
- package/dist/ai-promise-db.js.map +1 -1
- package/dist/constants.d.ts +16 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +16 -0
- package/dist/constants.js.map +1 -0
- package/dist/events.d.ts +153 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +154 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/memory-provider.d.ts +144 -2
- package/dist/memory-provider.d.ts.map +1 -1
- package/dist/memory-provider.js +569 -13
- package/dist/memory-provider.js.map +1 -1
- package/dist/schema/cascade.d.ts +96 -0
- package/dist/schema/cascade.d.ts.map +1 -0
- package/dist/schema/cascade.js +528 -0
- package/dist/schema/cascade.js.map +1 -0
- package/dist/schema/index.d.ts +197 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +1211 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/parse.d.ts +225 -0
- package/dist/schema/parse.d.ts.map +1 -0
- package/dist/schema/parse.js +732 -0
- package/dist/schema/parse.js.map +1 -0
- package/dist/schema/provider.d.ts +176 -0
- package/dist/schema/provider.d.ts.map +1 -0
- package/dist/schema/provider.js +258 -0
- package/dist/schema/provider.js.map +1 -0
- package/dist/schema/resolve.d.ts +87 -0
- package/dist/schema/resolve.d.ts.map +1 -0
- package/dist/schema/resolve.js +474 -0
- package/dist/schema/resolve.js.map +1 -0
- package/dist/schema/semantic.d.ts +53 -0
- package/dist/schema/semantic.d.ts.map +1 -0
- package/dist/schema/semantic.js +247 -0
- package/dist/schema/semantic.js.map +1 -0
- package/dist/schema/types.d.ts +528 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +9 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/schema.d.ts +24 -867
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +41 -1124
- package/dist/schema.js.map +1 -1
- package/dist/semantic.d.ts +175 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +338 -0
- package/dist/semantic.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +13 -4
- package/.turbo/turbo-build.log +0 -5
- package/TESTING.md +0 -410
- package/TEST_SUMMARY.md +0 -250
- package/TODO.md +0 -128
- package/src/ai-promise-db.ts +0 -1243
- package/src/authorization.ts +0 -1102
- package/src/durable-clickhouse.ts +0 -596
- package/src/durable-promise.ts +0 -582
- package/src/execution-queue.ts +0 -608
- package/src/index.test.ts +0 -868
- package/src/index.ts +0 -337
- package/src/linguistic.ts +0 -404
- package/src/memory-provider.test.ts +0 -1036
- package/src/memory-provider.ts +0 -1119
- package/src/schema.test.ts +0 -1254
- package/src/schema.ts +0 -2296
- package/src/tests.ts +0 -725
- package/src/types.ts +0 -1177
- package/test/README.md +0 -153
- package/test/edge-cases.test.ts +0 -646
- package/test/provider-resolution.test.ts +0 -402
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -19
|
@@ -0,0 +1,1211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-first Database Definition
|
|
3
|
+
*
|
|
4
|
+
* Declarative schema with automatic bi-directional relationships.
|
|
5
|
+
* Uses mdxld conventions for entity structure.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const { db } = DB({
|
|
10
|
+
* Post: {
|
|
11
|
+
* title: 'string',
|
|
12
|
+
* author: 'Author.posts', // one-to-many: Post.author -> Author, Author.posts -> Post[]
|
|
13
|
+
* tags: ['Tag.posts'], // many-to-many: Post.tags -> Tag[], Tag.posts -> Post[]
|
|
14
|
+
* },
|
|
15
|
+
* Author: {
|
|
16
|
+
* name: 'string',
|
|
17
|
+
* // posts: Post[] auto-created from backref
|
|
18
|
+
* },
|
|
19
|
+
* Tag: {
|
|
20
|
+
* name: 'string',
|
|
21
|
+
* // posts: Post[] auto-created from backref
|
|
22
|
+
* }
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* // Typed access
|
|
26
|
+
* const post = await db.Post.get('123')
|
|
27
|
+
* post.author // Author (single)
|
|
28
|
+
* post.tags // Tag[] (array)
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @packageDocumentation
|
|
32
|
+
*/
|
|
33
|
+
import { DBPromise, wrapEntityOperations, setSchemaRelationInfo } from '../ai-promise-db.js';
|
|
34
|
+
// Re-export parse functions
|
|
35
|
+
export { parseOperator, parseField, parseSchema, isPrimitiveType } from './parse.js';
|
|
36
|
+
export { setProvider, resolveProvider, hasSemanticSearch, hasHybridSearch, hasEventsAPI, hasActionsAPI, hasArtifactsAPI, hasEmbeddingsConfig, } from './provider.js';
|
|
37
|
+
// Re-export resolve functions
|
|
38
|
+
export { isEntityId, inferTypeFromField, resolveContextPath, resolveInstructions, prefetchContext, isPromptField, resolveNestedPending, resolveReferenceSpec, hydrateEntity, } from './resolve.js';
|
|
39
|
+
// Re-export cascade functions
|
|
40
|
+
export { generateContextAwareValue, generateAIFields, generateEntity, resolveForwardExact, generateNaturalLanguageContent, } from './cascade.js';
|
|
41
|
+
// Re-export semantic functions
|
|
42
|
+
export { resolveBackwardFuzzy, resolveForwardFuzzy } from './semantic.js';
|
|
43
|
+
import { Verbs, parseUrl } from '../types.js';
|
|
44
|
+
import { inferNoun, getTypeMeta, conjugate } from '../linguistic.js';
|
|
45
|
+
import { parseSchema } from './parse.js';
|
|
46
|
+
import { resolveProvider, hasSemanticSearch, hasHybridSearch, hasEventsAPI, hasActionsAPI, hasArtifactsAPI, hasEmbeddingsConfig, } from './provider.js';
|
|
47
|
+
import { hydrateEntity, resolveReferenceSpec } from './resolve.js';
|
|
48
|
+
import { resolveForwardExact, generateAIFields, generateContextAwareValue, generateNaturalLanguageContent } from './cascade.js';
|
|
49
|
+
import { resolveBackwardFuzzy, resolveForwardFuzzy } from './semantic.js';
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Noun/Verb Helpers
|
|
52
|
+
// =============================================================================
|
|
53
|
+
/**
|
|
54
|
+
* Create a Noun definition with type inference
|
|
55
|
+
*/
|
|
56
|
+
export function defineNoun(noun) {
|
|
57
|
+
return noun;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a Verb definition with type inference
|
|
61
|
+
*/
|
|
62
|
+
export function defineVerb(verb) {
|
|
63
|
+
return verb;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Convert a Noun to an EntitySchema for use with DB()
|
|
67
|
+
*/
|
|
68
|
+
export function nounToSchema(noun) {
|
|
69
|
+
const schema = {};
|
|
70
|
+
// Add properties
|
|
71
|
+
if (noun.properties) {
|
|
72
|
+
for (const [name, prop] of Object.entries(noun.properties)) {
|
|
73
|
+
let type = prop.type;
|
|
74
|
+
if (prop.array)
|
|
75
|
+
type = `${type}[]`;
|
|
76
|
+
if (prop.optional)
|
|
77
|
+
type = `${type}?`;
|
|
78
|
+
schema[name] = type;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Add relationships
|
|
82
|
+
if (noun.relationships) {
|
|
83
|
+
for (const [name, rel] of Object.entries(noun.relationships)) {
|
|
84
|
+
const baseType = rel.type.replace('[]', '');
|
|
85
|
+
const isArray = rel.type.endsWith('[]');
|
|
86
|
+
if (rel.backref) {
|
|
87
|
+
schema[name] = isArray ? [`${baseType}.${rel.backref}`] : `${baseType}.${rel.backref}`;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
schema[name] = rel.type;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return schema;
|
|
95
|
+
}
|
|
96
|
+
// =============================================================================
|
|
97
|
+
// Built-in Schema Types
|
|
98
|
+
// =============================================================================
|
|
99
|
+
/**
|
|
100
|
+
* Built-in Thing schema - base type for all entities
|
|
101
|
+
*/
|
|
102
|
+
export const ThingSchema = {
|
|
103
|
+
type: 'Noun.things',
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Built-in Noun schema for storing type definitions
|
|
107
|
+
*/
|
|
108
|
+
export const NounSchema = {
|
|
109
|
+
name: 'string',
|
|
110
|
+
singular: 'string',
|
|
111
|
+
plural: 'string',
|
|
112
|
+
slug: 'string',
|
|
113
|
+
slugPlural: 'string',
|
|
114
|
+
description: 'string?',
|
|
115
|
+
properties: 'json?',
|
|
116
|
+
relationships: 'json?',
|
|
117
|
+
actions: 'json?',
|
|
118
|
+
events: 'json?',
|
|
119
|
+
metadata: 'json?',
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Built-in Verb schema for storing action definitions
|
|
123
|
+
*/
|
|
124
|
+
export const VerbSchema = {
|
|
125
|
+
action: 'string',
|
|
126
|
+
actor: 'string?',
|
|
127
|
+
act: 'string?',
|
|
128
|
+
activity: 'string?',
|
|
129
|
+
result: 'string?',
|
|
130
|
+
reverse: 'json?',
|
|
131
|
+
inverse: 'string?',
|
|
132
|
+
description: 'string?',
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Built-in Edge schema for relationships between types
|
|
136
|
+
*/
|
|
137
|
+
export const EdgeSchema = {
|
|
138
|
+
from: 'string',
|
|
139
|
+
name: 'string',
|
|
140
|
+
to: 'string',
|
|
141
|
+
backref: 'string?',
|
|
142
|
+
cardinality: 'string',
|
|
143
|
+
direction: 'string',
|
|
144
|
+
matchMode: 'string?',
|
|
145
|
+
required: 'boolean?',
|
|
146
|
+
description: 'string?',
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* System types that are auto-created in every database
|
|
150
|
+
*/
|
|
151
|
+
export const SystemSchema = {
|
|
152
|
+
Thing: ThingSchema,
|
|
153
|
+
Noun: NounSchema,
|
|
154
|
+
Verb: VerbSchema,
|
|
155
|
+
Edge: EdgeSchema,
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Create Edge records from schema relationships
|
|
159
|
+
*/
|
|
160
|
+
export function createEdgeRecords(typeName, schema, parsedEntity) {
|
|
161
|
+
const edges = [];
|
|
162
|
+
for (const [fieldName, field] of parsedEntity.fields) {
|
|
163
|
+
if (field.isRelation && field.relatedType) {
|
|
164
|
+
const direction = field.direction ?? 'forward';
|
|
165
|
+
const matchMode = field.matchMode ?? 'exact';
|
|
166
|
+
const isBackward = direction === 'backward';
|
|
167
|
+
const from = isBackward ? field.relatedType : typeName;
|
|
168
|
+
const to = isBackward ? typeName : field.relatedType;
|
|
169
|
+
let cardinality;
|
|
170
|
+
if (field.isArray) {
|
|
171
|
+
cardinality = field.backref ? 'many-to-many' : 'one-to-many';
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
cardinality = 'many-to-one';
|
|
175
|
+
}
|
|
176
|
+
edges.push({
|
|
177
|
+
from,
|
|
178
|
+
name: fieldName,
|
|
179
|
+
to,
|
|
180
|
+
backref: field.backref,
|
|
181
|
+
cardinality,
|
|
182
|
+
direction,
|
|
183
|
+
matchMode,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return edges;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Create a Noun record from a type name and optional schema
|
|
191
|
+
*/
|
|
192
|
+
export function createNounRecord(typeName, schema, nounDef) {
|
|
193
|
+
const meta = getTypeMeta(typeName);
|
|
194
|
+
const inferred = inferNoun(typeName);
|
|
195
|
+
return {
|
|
196
|
+
name: typeName,
|
|
197
|
+
singular: nounDef?.singular ?? meta.singular,
|
|
198
|
+
plural: nounDef?.plural ?? meta.plural,
|
|
199
|
+
slug: meta.slug,
|
|
200
|
+
slugPlural: meta.slugPlural,
|
|
201
|
+
description: nounDef?.description,
|
|
202
|
+
properties: nounDef?.properties ?? (schema ? schemaToProperties(schema) : undefined),
|
|
203
|
+
relationships: nounDef?.relationships,
|
|
204
|
+
actions: nounDef?.actions ?? inferred.actions,
|
|
205
|
+
events: nounDef?.events ?? inferred.events,
|
|
206
|
+
metadata: nounDef?.metadata,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Convert EntitySchema to NounProperty format
|
|
211
|
+
*/
|
|
212
|
+
function schemaToProperties(schema) {
|
|
213
|
+
const properties = {};
|
|
214
|
+
for (const [name, def] of Object.entries(schema)) {
|
|
215
|
+
const defStr = Array.isArray(def) ? def[0] : def;
|
|
216
|
+
const isOptional = defStr.endsWith('?');
|
|
217
|
+
const isArray = defStr.endsWith('[]') || Array.isArray(def);
|
|
218
|
+
const baseType = defStr.replace(/[\?\[\]]/g, '').split('.')[0];
|
|
219
|
+
properties[name] = {
|
|
220
|
+
type: baseType,
|
|
221
|
+
optional: isOptional,
|
|
222
|
+
array: isArray,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return properties;
|
|
226
|
+
}
|
|
227
|
+
// =============================================================================
|
|
228
|
+
// Natural Language Query Implementation
|
|
229
|
+
// =============================================================================
|
|
230
|
+
let nlQueryGenerator = null;
|
|
231
|
+
/**
|
|
232
|
+
* Set the AI generator for natural language queries
|
|
233
|
+
*/
|
|
234
|
+
export function setNLQueryGenerator(generator) {
|
|
235
|
+
nlQueryGenerator = generator;
|
|
236
|
+
}
|
|
237
|
+
function buildNLQueryContext(schema, targetType) {
|
|
238
|
+
const types = [];
|
|
239
|
+
for (const [name, entity] of schema.entities) {
|
|
240
|
+
const fields = [];
|
|
241
|
+
const relationships = [];
|
|
242
|
+
for (const [fieldName, field] of entity.fields) {
|
|
243
|
+
if (field.isRelation && field.relatedType) {
|
|
244
|
+
relationships.push({
|
|
245
|
+
name: fieldName,
|
|
246
|
+
to: field.relatedType,
|
|
247
|
+
cardinality: field.isArray ? 'many' : 'one',
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
fields.push(fieldName);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const meta = getTypeMeta(name);
|
|
255
|
+
types.push({
|
|
256
|
+
name,
|
|
257
|
+
singular: meta.singular,
|
|
258
|
+
plural: meta.plural,
|
|
259
|
+
fields,
|
|
260
|
+
relationships,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return { types, targetType };
|
|
264
|
+
}
|
|
265
|
+
async function executeNLQuery(question, schema, targetType) {
|
|
266
|
+
if (!nlQueryGenerator) {
|
|
267
|
+
const provider = await resolveProvider();
|
|
268
|
+
const results = [];
|
|
269
|
+
// Simple heuristic for common "list all" patterns in fallback mode
|
|
270
|
+
const lowerQuestion = question.toLowerCase().trim();
|
|
271
|
+
const isListAllQuery = /^(show|list|get|find|display)\s+(all|every|the)?\s*/i.test(lowerQuestion) ||
|
|
272
|
+
lowerQuestion === '' ||
|
|
273
|
+
/\ball\b/i.test(lowerQuestion);
|
|
274
|
+
if (targetType) {
|
|
275
|
+
if (isListAllQuery) {
|
|
276
|
+
// For "show all X" queries, just list everything
|
|
277
|
+
const listResults = await provider.list(targetType);
|
|
278
|
+
results.push(...listResults);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
const searchResults = await provider.search(targetType, question);
|
|
282
|
+
results.push(...searchResults);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
for (const [typeName] of schema.entities) {
|
|
287
|
+
if (isListAllQuery) {
|
|
288
|
+
const listResults = await provider.list(typeName);
|
|
289
|
+
results.push(...listResults);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const searchResults = await provider.search(typeName, question);
|
|
293
|
+
results.push(...searchResults);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
interpretation: `Search for "${question}"`,
|
|
299
|
+
confidence: 0.5,
|
|
300
|
+
results,
|
|
301
|
+
explanation: 'Fallback to keyword search (no AI generator configured)',
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
const context = buildNLQueryContext(schema, targetType);
|
|
305
|
+
const plan = await nlQueryGenerator(question, context);
|
|
306
|
+
const provider = await resolveProvider();
|
|
307
|
+
const results = [];
|
|
308
|
+
for (const typeName of plan.types) {
|
|
309
|
+
let typeResults;
|
|
310
|
+
if (plan.search) {
|
|
311
|
+
typeResults = await provider.search(typeName, plan.search, {
|
|
312
|
+
where: plan.filters,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
typeResults = await provider.list(typeName, {
|
|
317
|
+
where: plan.filters,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
results.push(...typeResults);
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
interpretation: plan.interpretation,
|
|
324
|
+
confidence: plan.confidence,
|
|
325
|
+
results,
|
|
326
|
+
query: JSON.stringify({ types: plan.types, filters: plan.filters, search: plan.search }),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function createNLQueryFn(schema, typeName) {
|
|
330
|
+
return async (strings, ...values) => {
|
|
331
|
+
const question = strings.reduce((acc, str, i) => {
|
|
332
|
+
return acc + str + (values[i] !== undefined ? String(values[i]) : '');
|
|
333
|
+
}, '');
|
|
334
|
+
return executeNLQuery(question, schema, typeName);
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
// =============================================================================
|
|
338
|
+
// Edge Entity Operations
|
|
339
|
+
// =============================================================================
|
|
340
|
+
function createEdgeEntityOperations(schemaEdgeRecords, getProvider) {
|
|
341
|
+
async function getRuntimeEdges() {
|
|
342
|
+
try {
|
|
343
|
+
const provider = await getProvider();
|
|
344
|
+
const runtimeEdges = await provider.list('Edge');
|
|
345
|
+
return runtimeEdges;
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async function getAllEdges() {
|
|
352
|
+
const runtimeEdges = await getRuntimeEdges();
|
|
353
|
+
const runtimeEdgeKeys = new Set(runtimeEdges.map(e => `${e.from}:${e.name}`));
|
|
354
|
+
const filteredSchemaEdges = schemaEdgeRecords.filter(e => {
|
|
355
|
+
const key = `${e.from}:${e.name}`;
|
|
356
|
+
const hasRuntimeVersion = runtimeEdgeKeys.has(key);
|
|
357
|
+
if (hasRuntimeVersion && e.matchMode === 'fuzzy') {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
return !hasRuntimeVersion;
|
|
361
|
+
});
|
|
362
|
+
return [...filteredSchemaEdges, ...runtimeEdges];
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
async get(id) {
|
|
366
|
+
const runtimeEdges = await getRuntimeEdges();
|
|
367
|
+
const runtimeMatch = runtimeEdges.find(e => e.$id === id || `${e.from}:${e.name}` === id);
|
|
368
|
+
if (runtimeMatch)
|
|
369
|
+
return { ...runtimeMatch, $type: 'Edge' };
|
|
370
|
+
return schemaEdgeRecords.find(e => `${e.from}:${e.name}` === id) ?? null;
|
|
371
|
+
},
|
|
372
|
+
async list(options) {
|
|
373
|
+
let results = await getAllEdges();
|
|
374
|
+
if (options?.where) {
|
|
375
|
+
for (const [key, value] of Object.entries(options.where)) {
|
|
376
|
+
results = results.filter(e => e[key] === value);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return results.map(e => ({
|
|
380
|
+
...e,
|
|
381
|
+
$id: e.$id || `${e.from}:${e.name}`,
|
|
382
|
+
$type: 'Edge',
|
|
383
|
+
}));
|
|
384
|
+
},
|
|
385
|
+
async find(where) {
|
|
386
|
+
let results = await getAllEdges();
|
|
387
|
+
for (const [key, value] of Object.entries(where)) {
|
|
388
|
+
results = results.filter(e => e[key] === value);
|
|
389
|
+
}
|
|
390
|
+
return results.map(e => ({
|
|
391
|
+
...e,
|
|
392
|
+
$id: e.$id || `${e.from}:${e.name}`,
|
|
393
|
+
$type: 'Edge',
|
|
394
|
+
}));
|
|
395
|
+
},
|
|
396
|
+
async search(query) {
|
|
397
|
+
const allEdges = await getAllEdges();
|
|
398
|
+
const queryLower = query.toLowerCase();
|
|
399
|
+
return allEdges
|
|
400
|
+
.filter(e => String(e.from).toLowerCase().includes(queryLower) ||
|
|
401
|
+
String(e.name).toLowerCase().includes(queryLower) ||
|
|
402
|
+
String(e.to).toLowerCase().includes(queryLower))
|
|
403
|
+
.map(e => ({
|
|
404
|
+
...e,
|
|
405
|
+
$id: e.$id || `${e.from}:${e.name}`,
|
|
406
|
+
$type: 'Edge',
|
|
407
|
+
}));
|
|
408
|
+
},
|
|
409
|
+
async create() {
|
|
410
|
+
throw new Error('Cannot manually create Edge records - they are auto-generated');
|
|
411
|
+
},
|
|
412
|
+
async update() {
|
|
413
|
+
throw new Error('Cannot manually update Edge records - they are auto-generated');
|
|
414
|
+
},
|
|
415
|
+
async upsert() {
|
|
416
|
+
throw new Error('Cannot manually upsert Edge records - they are auto-generated');
|
|
417
|
+
},
|
|
418
|
+
async delete() {
|
|
419
|
+
throw new Error('Cannot manually delete Edge records - they are auto-generated');
|
|
420
|
+
},
|
|
421
|
+
async forEach(optionsOrCallback, maybeCallback) {
|
|
422
|
+
const options = typeof optionsOrCallback === 'function' ? undefined : optionsOrCallback;
|
|
423
|
+
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
|
|
424
|
+
const items = await this.list(options);
|
|
425
|
+
for (const item of items) {
|
|
426
|
+
await callback(item);
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
async semanticSearch() {
|
|
430
|
+
return [];
|
|
431
|
+
},
|
|
432
|
+
async hybridSearch() {
|
|
433
|
+
return [];
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
// =============================================================================
|
|
438
|
+
// Entity Operations Factory
|
|
439
|
+
// =============================================================================
|
|
440
|
+
function createEntityOperations(typeName, entity, schema) {
|
|
441
|
+
return {
|
|
442
|
+
async get(id) {
|
|
443
|
+
const provider = await resolveProvider();
|
|
444
|
+
const result = await provider.get(typeName, id);
|
|
445
|
+
if (!result)
|
|
446
|
+
return null;
|
|
447
|
+
return hydrateEntity(result, entity, schema, resolveProvider);
|
|
448
|
+
},
|
|
449
|
+
async list(options) {
|
|
450
|
+
const provider = await resolveProvider();
|
|
451
|
+
const results = await provider.list(typeName, options);
|
|
452
|
+
return Promise.all(results.map((r) => hydrateEntity(r, entity, schema, resolveProvider)));
|
|
453
|
+
},
|
|
454
|
+
async find(where) {
|
|
455
|
+
const provider = await resolveProvider();
|
|
456
|
+
const results = await provider.list(typeName, {
|
|
457
|
+
where: where,
|
|
458
|
+
});
|
|
459
|
+
return Promise.all(results.map((r) => hydrateEntity(r, entity, schema, resolveProvider)));
|
|
460
|
+
},
|
|
461
|
+
async search(query, options) {
|
|
462
|
+
const provider = await resolveProvider();
|
|
463
|
+
const results = await provider.search(typeName, query, options);
|
|
464
|
+
return Promise.all(results.map((r) => hydrateEntity(r, entity, schema, resolveProvider)));
|
|
465
|
+
},
|
|
466
|
+
async create(idOrData, maybeData) {
|
|
467
|
+
const provider = await resolveProvider();
|
|
468
|
+
const providedId = typeof idOrData === 'string' ? idOrData : undefined;
|
|
469
|
+
const data = typeof idOrData === 'string'
|
|
470
|
+
? maybeData
|
|
471
|
+
: idOrData;
|
|
472
|
+
const entityId = providedId || crypto.randomUUID();
|
|
473
|
+
const { data: resolvedData, pendingRelations } = await resolveForwardExact(typeName, data, entity, schema, provider, entityId);
|
|
474
|
+
const { data: fuzzyResolvedData, pendingRelations: fuzzyPendingRelations } = await resolveForwardFuzzy(typeName, resolvedData, entity, schema, provider, entityId);
|
|
475
|
+
const backwardResolvedData = await resolveBackwardFuzzy(typeName, fuzzyResolvedData, entity, schema, provider);
|
|
476
|
+
const finalData = await generateAIFields(backwardResolvedData, typeName, entity, schema, provider);
|
|
477
|
+
const result = await provider.create(typeName, entityId, finalData);
|
|
478
|
+
for (const rel of pendingRelations) {
|
|
479
|
+
await provider.relate(typeName, entityId, rel.fieldName, rel.targetType, rel.targetId);
|
|
480
|
+
}
|
|
481
|
+
const createdEdgeIds = new Set();
|
|
482
|
+
for (const rel of fuzzyPendingRelations) {
|
|
483
|
+
await provider.relate(typeName, entityId, rel.fieldName, rel.targetType, rel.targetId, {
|
|
484
|
+
matchMode: 'fuzzy',
|
|
485
|
+
similarity: rel.similarity
|
|
486
|
+
});
|
|
487
|
+
const edgeId = `${typeName}:${rel.fieldName}:${entityId}:${rel.targetId}`;
|
|
488
|
+
if (!createdEdgeIds.has(edgeId)) {
|
|
489
|
+
createdEdgeIds.add(edgeId);
|
|
490
|
+
try {
|
|
491
|
+
await provider.create('Edge', edgeId, {
|
|
492
|
+
from: typeName,
|
|
493
|
+
name: rel.fieldName,
|
|
494
|
+
to: rel.targetType,
|
|
495
|
+
direction: 'forward',
|
|
496
|
+
matchMode: 'fuzzy',
|
|
497
|
+
similarity: rel.similarity,
|
|
498
|
+
fromId: entityId,
|
|
499
|
+
toId: rel.targetId,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
// Edge already exists
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return hydrateEntity(result, entity, schema, resolveProvider);
|
|
508
|
+
},
|
|
509
|
+
async update(id, data) {
|
|
510
|
+
const provider = await resolveProvider();
|
|
511
|
+
const result = await provider.update(typeName, id, data);
|
|
512
|
+
return hydrateEntity(result, entity, schema, resolveProvider);
|
|
513
|
+
},
|
|
514
|
+
async upsert(id, data) {
|
|
515
|
+
const provider = await resolveProvider();
|
|
516
|
+
const existing = await provider.get(typeName, id);
|
|
517
|
+
if (existing) {
|
|
518
|
+
const result = await provider.update(typeName, id, data);
|
|
519
|
+
return hydrateEntity(result, entity, schema, resolveProvider);
|
|
520
|
+
}
|
|
521
|
+
const result = await provider.create(typeName, id, data);
|
|
522
|
+
return hydrateEntity(result, entity, schema, resolveProvider);
|
|
523
|
+
},
|
|
524
|
+
async delete(id) {
|
|
525
|
+
const provider = await resolveProvider();
|
|
526
|
+
return provider.delete(typeName, id);
|
|
527
|
+
},
|
|
528
|
+
async forEach(optionsOrCallback, maybeCallback) {
|
|
529
|
+
const options = typeof optionsOrCallback === 'function' ? undefined : optionsOrCallback;
|
|
530
|
+
const callback = typeof optionsOrCallback === 'function'
|
|
531
|
+
? optionsOrCallback
|
|
532
|
+
: maybeCallback;
|
|
533
|
+
const items = await this.list(options);
|
|
534
|
+
for (const item of items) {
|
|
535
|
+
await callback(item);
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
async semanticSearch(query, options) {
|
|
539
|
+
const provider = await resolveProvider();
|
|
540
|
+
if (hasSemanticSearch(provider)) {
|
|
541
|
+
const results = await provider.semanticSearch(typeName, query, options);
|
|
542
|
+
return Promise.all(results.map((r) => ({
|
|
543
|
+
...hydrateEntity(r, entity, schema, resolveProvider),
|
|
544
|
+
$score: r.$score,
|
|
545
|
+
})));
|
|
546
|
+
}
|
|
547
|
+
return [];
|
|
548
|
+
},
|
|
549
|
+
async hybridSearch(query, options) {
|
|
550
|
+
const provider = await resolveProvider();
|
|
551
|
+
if (hasHybridSearch(provider)) {
|
|
552
|
+
const results = await provider.hybridSearch(typeName, query, options);
|
|
553
|
+
return Promise.all(results.map((r) => ({
|
|
554
|
+
...hydrateEntity(r, entity, schema, resolveProvider),
|
|
555
|
+
$rrfScore: r.$rrfScore,
|
|
556
|
+
$ftsRank: r.$ftsRank,
|
|
557
|
+
$semanticRank: r.$semanticRank,
|
|
558
|
+
$score: r.$score,
|
|
559
|
+
})));
|
|
560
|
+
}
|
|
561
|
+
return [];
|
|
562
|
+
},
|
|
563
|
+
async draft(data, options) {
|
|
564
|
+
const draftData = { ...data, $phase: 'draft' };
|
|
565
|
+
const refs = {};
|
|
566
|
+
for (const [fieldName, field] of entity.fields) {
|
|
567
|
+
if (draftData[fieldName] !== undefined && draftData[fieldName] !== null) {
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (field.operator && field.relatedType) {
|
|
571
|
+
const matchMode = field.matchMode ?? (field.operator.includes('~') ? 'fuzzy' : 'exact');
|
|
572
|
+
if (field.isArray) {
|
|
573
|
+
const generatedText = generateNaturalLanguageContent(fieldName, field.prompt, field.relatedType, data);
|
|
574
|
+
draftData[fieldName] = generatedText;
|
|
575
|
+
const refSpec = {
|
|
576
|
+
field: fieldName,
|
|
577
|
+
operator: field.operator,
|
|
578
|
+
type: field.relatedType,
|
|
579
|
+
matchMode,
|
|
580
|
+
resolved: false,
|
|
581
|
+
prompt: field.prompt,
|
|
582
|
+
generatedText,
|
|
583
|
+
};
|
|
584
|
+
refs[fieldName] = [refSpec];
|
|
585
|
+
if (options?.stream && options.onChunk) {
|
|
586
|
+
options.onChunk(generatedText);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
const generatedText = generateNaturalLanguageContent(fieldName, field.prompt, field.relatedType, data);
|
|
591
|
+
draftData[fieldName] = generatedText;
|
|
592
|
+
refs[fieldName] = {
|
|
593
|
+
field: fieldName,
|
|
594
|
+
operator: field.operator,
|
|
595
|
+
type: field.relatedType,
|
|
596
|
+
matchMode,
|
|
597
|
+
resolved: false,
|
|
598
|
+
prompt: field.prompt,
|
|
599
|
+
generatedText,
|
|
600
|
+
};
|
|
601
|
+
if (options?.stream && options.onChunk) {
|
|
602
|
+
options.onChunk(generatedText);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
draftData.$refs = refs;
|
|
608
|
+
return draftData;
|
|
609
|
+
},
|
|
610
|
+
async resolve(draft, options) {
|
|
611
|
+
// Draft<T> interface requires $phase: 'draft', so we can access it directly
|
|
612
|
+
if (draft.$phase !== 'draft') {
|
|
613
|
+
throw new Error('Cannot resolve entity: not a draft (missing $phase: "draft")');
|
|
614
|
+
}
|
|
615
|
+
const provider = await resolveProvider();
|
|
616
|
+
const resolved = { ...draft };
|
|
617
|
+
const errors = [];
|
|
618
|
+
delete resolved.$refs;
|
|
619
|
+
resolved.$phase = 'resolved';
|
|
620
|
+
const refs = draft.$refs;
|
|
621
|
+
for (const [fieldName, refSpec] of Object.entries(refs)) {
|
|
622
|
+
try {
|
|
623
|
+
if (Array.isArray(refSpec)) {
|
|
624
|
+
const resolvedIds = [];
|
|
625
|
+
for (const spec of refSpec) {
|
|
626
|
+
const resolvedId = await resolveReferenceSpec(spec, resolved, schema, provider, generateContextAwareValue);
|
|
627
|
+
if (resolvedId) {
|
|
628
|
+
resolvedIds.push(resolvedId);
|
|
629
|
+
options?.onResolved?.(fieldName, resolvedId);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
resolved[fieldName] = resolvedIds;
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
const resolvedId = await resolveReferenceSpec(refSpec, resolved, schema, provider, generateContextAwareValue);
|
|
636
|
+
if (resolvedId) {
|
|
637
|
+
resolved[fieldName] = resolvedId;
|
|
638
|
+
options?.onResolved?.(fieldName, resolvedId);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
catch (err) {
|
|
643
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
644
|
+
if (options?.onError === 'skip') {
|
|
645
|
+
errors.push({ field: fieldName, error: errorMsg });
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
throw err;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (errors.length > 0 || options?.onError === 'skip') {
|
|
653
|
+
// resolved is typed as Record<string, unknown>, so we can assign $errors directly
|
|
654
|
+
resolved.$errors = errors;
|
|
655
|
+
}
|
|
656
|
+
return resolved;
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
// =============================================================================
|
|
661
|
+
// DB Factory
|
|
662
|
+
// =============================================================================
|
|
663
|
+
/**
|
|
664
|
+
* Create a typed database from a schema definition
|
|
665
|
+
*/
|
|
666
|
+
export function DB(schema, options) {
|
|
667
|
+
const parsedSchema = parseSchema(schema);
|
|
668
|
+
// Add Edge entity to the parsed schema
|
|
669
|
+
const edgeEntity = {
|
|
670
|
+
name: 'Edge',
|
|
671
|
+
fields: new Map([
|
|
672
|
+
['from', { name: 'from', type: 'string', isArray: false, isOptional: false, isRelation: false }],
|
|
673
|
+
['name', { name: 'name', type: 'string', isArray: false, isOptional: false, isRelation: false }],
|
|
674
|
+
['to', { name: 'to', type: 'string', isArray: false, isOptional: false, isRelation: false }],
|
|
675
|
+
['backref', { name: 'backref', type: 'string', isArray: false, isOptional: true, isRelation: false }],
|
|
676
|
+
['cardinality', { name: 'cardinality', type: 'string', isArray: false, isOptional: false, isRelation: false }],
|
|
677
|
+
['direction', { name: 'direction', type: 'string', isArray: false, isOptional: false, isRelation: false }],
|
|
678
|
+
['matchMode', { name: 'matchMode', type: 'string', isArray: false, isOptional: true, isRelation: false }],
|
|
679
|
+
]),
|
|
680
|
+
};
|
|
681
|
+
parsedSchema.entities.set('Edge', edgeEntity);
|
|
682
|
+
// Configure provider with embeddings settings if provided
|
|
683
|
+
if (options?.embeddings) {
|
|
684
|
+
resolveProvider().then(provider => {
|
|
685
|
+
if (hasEmbeddingsConfig(provider)) {
|
|
686
|
+
provider.setEmbeddingsConfig(options.embeddings);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
// Collect all edge records from the schema
|
|
691
|
+
const allEdgeRecords = [];
|
|
692
|
+
for (const [entityName, entity] of parsedSchema.entities) {
|
|
693
|
+
if (entityName !== 'Edge') {
|
|
694
|
+
const edgeRecords = createEdgeRecords(entityName, schema[entityName] ?? {}, entity);
|
|
695
|
+
allEdgeRecords.push(...edgeRecords);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// Build and set schema relation info for batch loading
|
|
699
|
+
// Maps entityType -> fieldName -> relatedType
|
|
700
|
+
const relationInfo = new Map();
|
|
701
|
+
for (const [entityName, entity] of parsedSchema.entities) {
|
|
702
|
+
const fieldRelations = new Map();
|
|
703
|
+
for (const [fieldName, field] of entity.fields) {
|
|
704
|
+
if (field.isRelation && field.relatedType) {
|
|
705
|
+
fieldRelations.set(fieldName, field.relatedType);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (fieldRelations.size > 0) {
|
|
709
|
+
relationInfo.set(entityName, fieldRelations);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
setSchemaRelationInfo(relationInfo);
|
|
713
|
+
// Create Actions API early for internal use by wrapEntityOperations
|
|
714
|
+
// This API adapts DBAction to ForEachActionsAPI interface
|
|
715
|
+
const actionsAPI = {
|
|
716
|
+
async create(data) {
|
|
717
|
+
const provider = await resolveProvider();
|
|
718
|
+
if (hasActionsAPI(provider)) {
|
|
719
|
+
const action = await provider.createAction(data);
|
|
720
|
+
return { id: action.id };
|
|
721
|
+
}
|
|
722
|
+
throw new Error('Provider does not support actions');
|
|
723
|
+
},
|
|
724
|
+
async get(id) {
|
|
725
|
+
const provider = await resolveProvider();
|
|
726
|
+
if (hasActionsAPI(provider)) {
|
|
727
|
+
const action = await provider.getAction(id);
|
|
728
|
+
if (!action)
|
|
729
|
+
return null;
|
|
730
|
+
// Adapt DBAction to ForEachActionState
|
|
731
|
+
return {
|
|
732
|
+
id: action.id,
|
|
733
|
+
type: action.action ?? action.type ?? 'unknown',
|
|
734
|
+
status: action.status,
|
|
735
|
+
progress: action.progress,
|
|
736
|
+
total: action.total,
|
|
737
|
+
data: action.objectData ?? {},
|
|
738
|
+
result: action.result,
|
|
739
|
+
error: action.error,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
return null;
|
|
743
|
+
},
|
|
744
|
+
async update(id, updates) {
|
|
745
|
+
const provider = await resolveProvider();
|
|
746
|
+
if (hasActionsAPI(provider)) {
|
|
747
|
+
await provider.updateAction(id, {
|
|
748
|
+
status: updates.status,
|
|
749
|
+
progress: updates.progress,
|
|
750
|
+
// ForEachResult needs to be converted to Record<string, unknown> for DBAction.result
|
|
751
|
+
result: updates.result,
|
|
752
|
+
error: updates.error,
|
|
753
|
+
});
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
throw new Error('Provider does not support actions');
|
|
757
|
+
},
|
|
758
|
+
};
|
|
759
|
+
// Create entity operations
|
|
760
|
+
// Using Record<string, unknown> with [key: string]: unknown allows flexible method access
|
|
761
|
+
// while being safer than `any`. The actual operations implement EntityOperations<T>
|
|
762
|
+
// but we lose the generic when storing by entity name.
|
|
763
|
+
//
|
|
764
|
+
// IMPORTANT: Each entity must be both:
|
|
765
|
+
// 1. Callable as a tagged template literal: db.Lead`query`
|
|
766
|
+
// 2. An object with methods: db.Lead.get(), db.Lead.list(), etc.
|
|
767
|
+
//
|
|
768
|
+
// We achieve this by creating a function and attaching methods to it.
|
|
769
|
+
const entityOperations = {};
|
|
770
|
+
const eventHandlersForOps = new Map();
|
|
771
|
+
function emitInternalEventForOps(eventType, data) {
|
|
772
|
+
const handlers = eventHandlersForOps.get(eventType);
|
|
773
|
+
if (handlers) {
|
|
774
|
+
for (const handler of handlers) {
|
|
775
|
+
try {
|
|
776
|
+
handler(data);
|
|
777
|
+
}
|
|
778
|
+
catch (e) {
|
|
779
|
+
console.error(`Error in event handler for ${eventType}:`, e);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Make entity operations callable as a tagged template literal
|
|
786
|
+
* This allows both: db.Lead.get('id') and db.Lead`natural language query`
|
|
787
|
+
*/
|
|
788
|
+
function makeCallableEntityOps(ops, entityName) {
|
|
789
|
+
// Create the NL query function for this entity type
|
|
790
|
+
const nlQueryFn = createNLQueryFn(parsedSchema, entityName);
|
|
791
|
+
// Create a function that acts as the tagged template literal handler
|
|
792
|
+
const callableOps = function (strings, ...values) {
|
|
793
|
+
return nlQueryFn(strings, ...values);
|
|
794
|
+
};
|
|
795
|
+
// Copy all methods from the wrapped operations to the function
|
|
796
|
+
Object.assign(callableOps, ops);
|
|
797
|
+
return callableOps;
|
|
798
|
+
}
|
|
799
|
+
for (const [entityName, entity] of parsedSchema.entities) {
|
|
800
|
+
if (entityName === 'Edge') {
|
|
801
|
+
const edgeOps = createEdgeEntityOperations(allEdgeRecords, resolveProvider);
|
|
802
|
+
const wrappedEdgeOps = wrapEntityOperations(entityName, edgeOps, actionsAPI);
|
|
803
|
+
entityOperations[entityName] = makeCallableEntityOps(wrappedEdgeOps, entityName);
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
const baseOps = createEntityOperations(entityName, entity, parsedSchema);
|
|
807
|
+
const wrappedOps = wrapEntityOperations(entityName, baseOps, actionsAPI);
|
|
808
|
+
// baseOps has optional draft/resolve methods - EntityOperations<T> defines them as optional
|
|
809
|
+
const draftMethod = baseOps.draft;
|
|
810
|
+
const resolveMethod = baseOps.resolve;
|
|
811
|
+
const draftFn = async (data, options) => {
|
|
812
|
+
if (!draftMethod) {
|
|
813
|
+
throw new Error(`Draft method not available for ${entityName}`);
|
|
814
|
+
}
|
|
815
|
+
const draft = await draftMethod(data, options);
|
|
816
|
+
draft.$type = entityName;
|
|
817
|
+
emitInternalEventForOps('draft', draft);
|
|
818
|
+
return draft;
|
|
819
|
+
};
|
|
820
|
+
wrappedOps.draft = draftFn;
|
|
821
|
+
const resolveFn = async (draft, options) => {
|
|
822
|
+
if (!resolveMethod) {
|
|
823
|
+
throw new Error(`Resolve method not available for ${entityName}`);
|
|
824
|
+
}
|
|
825
|
+
const resolved = await resolveMethod(draft, options);
|
|
826
|
+
if (resolved && typeof resolved === 'object') {
|
|
827
|
+
resolved.$type = entityName;
|
|
828
|
+
}
|
|
829
|
+
emitInternalEventForOps('resolve', resolved);
|
|
830
|
+
return resolved;
|
|
831
|
+
};
|
|
832
|
+
wrappedOps.resolve = resolveFn;
|
|
833
|
+
const originalCreate = wrappedOps.create;
|
|
834
|
+
wrappedOps.create = async (...args) => {
|
|
835
|
+
let id;
|
|
836
|
+
let data;
|
|
837
|
+
let options;
|
|
838
|
+
if (typeof args[0] === 'string') {
|
|
839
|
+
id = args[0];
|
|
840
|
+
data = args[1];
|
|
841
|
+
options = args[2];
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
data = args[0];
|
|
845
|
+
options = args[1];
|
|
846
|
+
}
|
|
847
|
+
if (options?.draftOnly) {
|
|
848
|
+
const draft = await draftFn(data);
|
|
849
|
+
return draft;
|
|
850
|
+
}
|
|
851
|
+
const effectiveMaxDepth = options?.maxDepth ?? (options?.cascade ? 3 : 0);
|
|
852
|
+
if (options?.cascade && effectiveMaxDepth > 0) {
|
|
853
|
+
const provider = await resolveProvider();
|
|
854
|
+
const entityDef = parsedSchema.entities.get(entityName);
|
|
855
|
+
if (entityDef) {
|
|
856
|
+
// CreateEntityOptions now includes _cascadeState as an optional property
|
|
857
|
+
const cascadeState = options._cascadeState ?? {
|
|
858
|
+
totalEntitiesCreated: 0,
|
|
859
|
+
initialMaxDepth: effectiveMaxDepth,
|
|
860
|
+
rootOnProgress: options.onProgress,
|
|
861
|
+
rootOnError: options.onError,
|
|
862
|
+
stopOnError: options.stopOnError,
|
|
863
|
+
cascadeTypes: options.cascadeTypes,
|
|
864
|
+
};
|
|
865
|
+
const currentDepth = cascadeState.initialMaxDepth - effectiveMaxDepth;
|
|
866
|
+
const cascadeData = { ...data };
|
|
867
|
+
for (const [fieldName, field] of entityDef.fields) {
|
|
868
|
+
if (cascadeData[fieldName] !== undefined)
|
|
869
|
+
continue;
|
|
870
|
+
if (field.operator === '->' && field.relatedType) {
|
|
871
|
+
if (cascadeState.cascadeTypes && !cascadeState.cascadeTypes.includes(field.relatedType)) {
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
const relatedEntity = parsedSchema.entities.get(field.relatedType);
|
|
875
|
+
if (!relatedEntity)
|
|
876
|
+
continue;
|
|
877
|
+
try {
|
|
878
|
+
cascadeState.rootOnProgress?.({
|
|
879
|
+
phase: 'generating',
|
|
880
|
+
depth: currentDepth + 1,
|
|
881
|
+
currentType: field.relatedType,
|
|
882
|
+
totalEntitiesCreated: cascadeState.totalEntitiesCreated,
|
|
883
|
+
});
|
|
884
|
+
const childEntityData = {};
|
|
885
|
+
const parentInstructions = entityDef.schema?.$instructions;
|
|
886
|
+
const childInstructions = relatedEntity.schema?.$instructions;
|
|
887
|
+
const contextParts = [];
|
|
888
|
+
if (parentInstructions)
|
|
889
|
+
contextParts.push(parentInstructions);
|
|
890
|
+
if (childInstructions)
|
|
891
|
+
contextParts.push(childInstructions);
|
|
892
|
+
for (const [key, value] of Object.entries(cascadeData)) {
|
|
893
|
+
if (!key.startsWith('$') && !key.startsWith('_') && typeof value === 'string' && value) {
|
|
894
|
+
contextParts.push(`${key}: ${value}`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
const fullContext = contextParts.join(' | ');
|
|
898
|
+
for (const [childFieldName, childField] of relatedEntity.fields) {
|
|
899
|
+
if (!childField.isRelation && childField.type === 'string') {
|
|
900
|
+
childEntityData[childFieldName] = generateContextAwareValue(childFieldName, field.relatedType, fullContext, undefined, cascadeData);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
const childOptions = {
|
|
904
|
+
...options,
|
|
905
|
+
maxDepth: effectiveMaxDepth - 1,
|
|
906
|
+
_cascadeState: cascadeState,
|
|
907
|
+
};
|
|
908
|
+
const relatedOps = entityOperations[field.relatedType];
|
|
909
|
+
const createFn = relatedOps?.create;
|
|
910
|
+
const childEntity = createFn ? await createFn(childEntityData, childOptions) : undefined;
|
|
911
|
+
if (childEntity?.$id) {
|
|
912
|
+
cascadeState.totalEntitiesCreated++;
|
|
913
|
+
if (field.isArray) {
|
|
914
|
+
cascadeData[fieldName] = [childEntity.$id];
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
cascadeData[fieldName] = childEntity.$id;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
catch (error) {
|
|
922
|
+
cascadeState.rootOnError?.(error);
|
|
923
|
+
if (cascadeState.stopOnError) {
|
|
924
|
+
throw error;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
let result;
|
|
930
|
+
if (id) {
|
|
931
|
+
result = await originalCreate.call(wrappedOps, id, cascadeData);
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
result = await originalCreate.call(wrappedOps, cascadeData);
|
|
935
|
+
}
|
|
936
|
+
cascadeState.totalEntitiesCreated++;
|
|
937
|
+
if (currentDepth === 0) {
|
|
938
|
+
cascadeState.rootOnProgress?.({
|
|
939
|
+
phase: 'complete',
|
|
940
|
+
depth: currentDepth,
|
|
941
|
+
totalEntitiesCreated: cascadeState.totalEntitiesCreated,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
return result;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return originalCreate.call(wrappedOps, ...args);
|
|
948
|
+
};
|
|
949
|
+
// Make the entity operations callable as a tagged template literal
|
|
950
|
+
entityOperations[entityName] = makeCallableEntityOps(wrappedOps, entityName);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
// Noun definitions cache
|
|
954
|
+
const nounDefinitions = new Map();
|
|
955
|
+
for (const [entityName] of parsedSchema.entities) {
|
|
956
|
+
const noun = inferNoun(entityName);
|
|
957
|
+
nounDefinitions.set(entityName, noun);
|
|
958
|
+
}
|
|
959
|
+
// Verb definitions cache
|
|
960
|
+
const verbDefinitions = new Map(Object.entries(Verbs).map(([k, v]) => [k, v]));
|
|
961
|
+
function onInternal(eventType, handler) {
|
|
962
|
+
if (!eventHandlersForOps.has(eventType)) {
|
|
963
|
+
eventHandlersForOps.set(eventType, new Set());
|
|
964
|
+
}
|
|
965
|
+
eventHandlersForOps.get(eventType).add(handler);
|
|
966
|
+
return () => {
|
|
967
|
+
eventHandlersForOps.get(eventType)?.delete(handler);
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
const db = {
|
|
971
|
+
$schema: parsedSchema,
|
|
972
|
+
async get(url) {
|
|
973
|
+
const provider = await resolveProvider();
|
|
974
|
+
const parsed = parseUrl(url);
|
|
975
|
+
return provider.get(parsed.type, parsed.id);
|
|
976
|
+
},
|
|
977
|
+
async search(query, options) {
|
|
978
|
+
const provider = await resolveProvider();
|
|
979
|
+
const results = [];
|
|
980
|
+
for (const [typeName] of parsedSchema.entities) {
|
|
981
|
+
const typeResults = await provider.search(typeName, query, options);
|
|
982
|
+
results.push(...typeResults);
|
|
983
|
+
}
|
|
984
|
+
return results;
|
|
985
|
+
},
|
|
986
|
+
async semanticSearch(query, options) {
|
|
987
|
+
const provider = await resolveProvider();
|
|
988
|
+
const results = [];
|
|
989
|
+
if (hasSemanticSearch(provider)) {
|
|
990
|
+
for (const [typeName] of parsedSchema.entities) {
|
|
991
|
+
const typeResults = await provider.semanticSearch(typeName, query, options);
|
|
992
|
+
results.push(...typeResults);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
results.sort((a, b) => b.$score - a.$score);
|
|
996
|
+
const limit = options?.limit ?? results.length;
|
|
997
|
+
return results.slice(0, limit);
|
|
998
|
+
},
|
|
999
|
+
async count(type, where) {
|
|
1000
|
+
const provider = await resolveProvider();
|
|
1001
|
+
const results = await provider.list(type, { where });
|
|
1002
|
+
return results.length;
|
|
1003
|
+
},
|
|
1004
|
+
async forEach(options, callback) {
|
|
1005
|
+
const provider = await resolveProvider();
|
|
1006
|
+
const results = await provider.list(options.type, { where: options.where });
|
|
1007
|
+
const concurrency = options.concurrency ?? 1;
|
|
1008
|
+
if (concurrency === 1) {
|
|
1009
|
+
for (const entity of results) {
|
|
1010
|
+
await callback(entity);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
const { Semaphore } = await import('../memory-provider.js');
|
|
1015
|
+
const semaphore = new Semaphore(concurrency);
|
|
1016
|
+
await semaphore.map(results, callback);
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
1019
|
+
async set(type, id, data) {
|
|
1020
|
+
const provider = await resolveProvider();
|
|
1021
|
+
const existing = await provider.get(type, id);
|
|
1022
|
+
if (existing) {
|
|
1023
|
+
return provider.update(type, id, data);
|
|
1024
|
+
}
|
|
1025
|
+
return provider.create(type, id, data);
|
|
1026
|
+
},
|
|
1027
|
+
async generate(options) {
|
|
1028
|
+
const provider = await resolveProvider();
|
|
1029
|
+
if (options.mode === 'background') {
|
|
1030
|
+
const { createMemoryProvider } = await import('../memory-provider.js');
|
|
1031
|
+
const memProvider = provider;
|
|
1032
|
+
if ('createAction' in memProvider) {
|
|
1033
|
+
return memProvider.createAction({
|
|
1034
|
+
type: 'generate',
|
|
1035
|
+
data: options,
|
|
1036
|
+
total: options.count ?? 1,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return provider.create(options.type, undefined, options.data ?? {});
|
|
1041
|
+
},
|
|
1042
|
+
ask: createNLQueryFn(parsedSchema),
|
|
1043
|
+
on: onInternal,
|
|
1044
|
+
...entityOperations,
|
|
1045
|
+
};
|
|
1046
|
+
// Create Events API
|
|
1047
|
+
const events = {
|
|
1048
|
+
on(pattern, handler) {
|
|
1049
|
+
let unsubscribe = () => { };
|
|
1050
|
+
resolveProvider().then((provider) => {
|
|
1051
|
+
if (hasEventsAPI(provider)) {
|
|
1052
|
+
unsubscribe = provider.on(pattern, handler);
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
return () => unsubscribe();
|
|
1056
|
+
},
|
|
1057
|
+
async emit(optionsOrType, data) {
|
|
1058
|
+
const provider = await resolveProvider();
|
|
1059
|
+
if (hasEventsAPI(provider)) {
|
|
1060
|
+
// The provider.emit has overloads: (options: CreateEventOptions) or (type: string, data: unknown)
|
|
1061
|
+
if (typeof optionsOrType === 'string') {
|
|
1062
|
+
return provider.emit(optionsOrType, data);
|
|
1063
|
+
}
|
|
1064
|
+
return provider.emit(optionsOrType);
|
|
1065
|
+
}
|
|
1066
|
+
const now = new Date();
|
|
1067
|
+
if (typeof optionsOrType === 'string') {
|
|
1068
|
+
return {
|
|
1069
|
+
id: crypto.randomUUID(),
|
|
1070
|
+
actor: 'system',
|
|
1071
|
+
event: optionsOrType,
|
|
1072
|
+
objectData: data,
|
|
1073
|
+
timestamp: now,
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
return {
|
|
1077
|
+
id: crypto.randomUUID(),
|
|
1078
|
+
actor: optionsOrType.actor,
|
|
1079
|
+
actorData: optionsOrType.actorData,
|
|
1080
|
+
event: optionsOrType.event,
|
|
1081
|
+
object: optionsOrType.object,
|
|
1082
|
+
objectData: optionsOrType.objectData,
|
|
1083
|
+
result: optionsOrType.result,
|
|
1084
|
+
resultData: optionsOrType.resultData,
|
|
1085
|
+
meta: optionsOrType.meta,
|
|
1086
|
+
timestamp: now,
|
|
1087
|
+
};
|
|
1088
|
+
},
|
|
1089
|
+
async list(options) {
|
|
1090
|
+
const provider = await resolveProvider();
|
|
1091
|
+
if (hasEventsAPI(provider)) {
|
|
1092
|
+
return provider.listEvents(options);
|
|
1093
|
+
}
|
|
1094
|
+
return [];
|
|
1095
|
+
},
|
|
1096
|
+
async replay(options) {
|
|
1097
|
+
const provider = await resolveProvider();
|
|
1098
|
+
if (hasEventsAPI(provider)) {
|
|
1099
|
+
await provider.replayEvents(options);
|
|
1100
|
+
}
|
|
1101
|
+
},
|
|
1102
|
+
};
|
|
1103
|
+
// Create Actions API (public version with full DBAction types)
|
|
1104
|
+
const actions = {
|
|
1105
|
+
async create(options) {
|
|
1106
|
+
const provider = await resolveProvider();
|
|
1107
|
+
if (hasActionsAPI(provider)) {
|
|
1108
|
+
return provider.createAction(options);
|
|
1109
|
+
}
|
|
1110
|
+
throw new Error('Provider does not support actions');
|
|
1111
|
+
},
|
|
1112
|
+
async get(id) {
|
|
1113
|
+
const provider = await resolveProvider();
|
|
1114
|
+
if (hasActionsAPI(provider)) {
|
|
1115
|
+
return provider.getAction(id);
|
|
1116
|
+
}
|
|
1117
|
+
return null;
|
|
1118
|
+
},
|
|
1119
|
+
async update(id, updates) {
|
|
1120
|
+
const provider = await resolveProvider();
|
|
1121
|
+
if (hasActionsAPI(provider)) {
|
|
1122
|
+
return provider.updateAction(id, updates);
|
|
1123
|
+
}
|
|
1124
|
+
throw new Error('Provider does not support actions');
|
|
1125
|
+
},
|
|
1126
|
+
async list(options) {
|
|
1127
|
+
const provider = await resolveProvider();
|
|
1128
|
+
if (hasActionsAPI(provider)) {
|
|
1129
|
+
return provider.listActions(options);
|
|
1130
|
+
}
|
|
1131
|
+
return [];
|
|
1132
|
+
},
|
|
1133
|
+
async retry(id) {
|
|
1134
|
+
const provider = await resolveProvider();
|
|
1135
|
+
if (hasActionsAPI(provider)) {
|
|
1136
|
+
return provider.retryAction(id);
|
|
1137
|
+
}
|
|
1138
|
+
throw new Error('Provider does not support actions');
|
|
1139
|
+
},
|
|
1140
|
+
async cancel(id) {
|
|
1141
|
+
const provider = await resolveProvider();
|
|
1142
|
+
if (hasActionsAPI(provider)) {
|
|
1143
|
+
await provider.cancelAction(id);
|
|
1144
|
+
}
|
|
1145
|
+
},
|
|
1146
|
+
conjugate,
|
|
1147
|
+
};
|
|
1148
|
+
// Create Artifacts API
|
|
1149
|
+
const artifacts = {
|
|
1150
|
+
async get(url, type) {
|
|
1151
|
+
const provider = await resolveProvider();
|
|
1152
|
+
if (hasArtifactsAPI(provider)) {
|
|
1153
|
+
return provider.getArtifact(url, type);
|
|
1154
|
+
}
|
|
1155
|
+
return null;
|
|
1156
|
+
},
|
|
1157
|
+
async set(url, type, data) {
|
|
1158
|
+
const provider = await resolveProvider();
|
|
1159
|
+
if (hasArtifactsAPI(provider)) {
|
|
1160
|
+
await provider.setArtifact(url, type, data);
|
|
1161
|
+
}
|
|
1162
|
+
},
|
|
1163
|
+
async delete(url, type) {
|
|
1164
|
+
const provider = await resolveProvider();
|
|
1165
|
+
if (hasArtifactsAPI(provider)) {
|
|
1166
|
+
await provider.deleteArtifact(url, type);
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
async list(url) {
|
|
1170
|
+
const provider = await resolveProvider();
|
|
1171
|
+
if (hasArtifactsAPI(provider)) {
|
|
1172
|
+
return provider.listArtifacts(url);
|
|
1173
|
+
}
|
|
1174
|
+
return [];
|
|
1175
|
+
},
|
|
1176
|
+
};
|
|
1177
|
+
// Create Nouns API
|
|
1178
|
+
const nouns = {
|
|
1179
|
+
async get(name) {
|
|
1180
|
+
return nounDefinitions.get(name) ?? null;
|
|
1181
|
+
},
|
|
1182
|
+
async list() {
|
|
1183
|
+
return Array.from(nounDefinitions.values());
|
|
1184
|
+
},
|
|
1185
|
+
async define(noun) {
|
|
1186
|
+
nounDefinitions.set(noun.singular, noun);
|
|
1187
|
+
},
|
|
1188
|
+
};
|
|
1189
|
+
// Create Verbs API
|
|
1190
|
+
const verbs = {
|
|
1191
|
+
get(action) {
|
|
1192
|
+
return verbDefinitions.get(action) ?? null;
|
|
1193
|
+
},
|
|
1194
|
+
list() {
|
|
1195
|
+
return Array.from(verbDefinitions.values());
|
|
1196
|
+
},
|
|
1197
|
+
define(verb) {
|
|
1198
|
+
verbDefinitions.set(verb.action, verb);
|
|
1199
|
+
},
|
|
1200
|
+
conjugate,
|
|
1201
|
+
};
|
|
1202
|
+
return Object.assign(db, {
|
|
1203
|
+
db,
|
|
1204
|
+
events,
|
|
1205
|
+
actions,
|
|
1206
|
+
artifacts,
|
|
1207
|
+
nouns,
|
|
1208
|
+
verbs,
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
//# sourceMappingURL=index.js.map
|