ai-database 2.1.1 → 2.3.0
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 +47 -1
- package/README.md +1063 -186
- package/dist/actions.d.ts +2 -2
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +1 -1
- package/dist/actions.js.map +1 -1
- package/dist/ai-promise-db.d.ts +52 -23
- package/dist/ai-promise-db.d.ts.map +1 -1
- package/dist/ai-promise-db.js +185 -164
- package/dist/ai-promise-db.js.map +1 -1
- package/dist/authorization.d.ts.map +1 -1
- package/dist/authorization.js +38 -30
- package/dist/authorization.js.map +1 -1
- package/dist/cascade-orchestrator.d.ts +404 -0
- package/dist/cascade-orchestrator.d.ts.map +1 -0
- package/dist/cascade-orchestrator.js +828 -0
- package/dist/cascade-orchestrator.js.map +1 -0
- package/dist/cascade-write-strategy.d.ts +584 -0
- package/dist/cascade-write-strategy.d.ts.map +1 -0
- package/dist/cascade-write-strategy.js +590 -0
- package/dist/cascade-write-strategy.js.map +1 -0
- package/dist/ch-adapter.d.ts +358 -0
- package/dist/ch-adapter.d.ts.map +1 -0
- package/dist/ch-adapter.js +929 -0
- package/dist/ch-adapter.js.map +1 -0
- package/dist/client/index.d.ts +42 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +43 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client.d.ts +266 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +81 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +64 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +52 -2
- package/dist/constants.js.map +1 -1
- package/dist/dataloader.d.ts +99 -0
- package/dist/dataloader.d.ts.map +1 -0
- package/dist/dataloader.js +225 -0
- package/dist/dataloader.js.map +1 -0
- package/dist/db-provider-port.d.ts +501 -0
- package/dist/db-provider-port.d.ts.map +1 -0
- package/dist/db-provider-port.js +113 -0
- package/dist/db-provider-port.js.map +1 -0
- package/dist/digital-objects-provider.d.ts +49 -0
- package/dist/digital-objects-provider.d.ts.map +1 -0
- package/dist/digital-objects-provider.js +55 -0
- package/dist/digital-objects-provider.js.map +1 -0
- package/dist/do-sqlite-adapter.d.ts +402 -0
- package/dist/do-sqlite-adapter.d.ts.map +1 -0
- package/dist/do-sqlite-adapter.js +745 -0
- package/dist/do-sqlite-adapter.js.map +1 -0
- package/dist/docs-rels/custom-types.d.ts +134 -0
- package/dist/docs-rels/custom-types.d.ts.map +1 -0
- package/dist/docs-rels/custom-types.js +70 -0
- package/dist/docs-rels/custom-types.js.map +1 -0
- package/dist/docs-rels/index.d.ts +16 -0
- package/dist/docs-rels/index.d.ts.map +1 -0
- package/dist/docs-rels/index.js +16 -0
- package/dist/docs-rels/index.js.map +1 -0
- package/dist/docs-rels/migrations/index.d.ts +30 -0
- package/dist/docs-rels/migrations/index.d.ts.map +1 -0
- package/dist/docs-rels/migrations/index.js +128 -0
- package/dist/docs-rels/migrations/index.js.map +1 -0
- package/dist/docs-rels/schema.d.ts +2961 -0
- package/dist/docs-rels/schema.d.ts.map +1 -0
- package/dist/docs-rels/schema.js +244 -0
- package/dist/docs-rels/schema.js.map +1 -0
- package/dist/durable-clickhouse.d.ts.map +1 -1
- package/dist/durable-clickhouse.js +16 -13
- package/dist/durable-clickhouse.js.map +1 -1
- package/dist/durable-promise.d.ts.map +1 -1
- package/dist/durable-promise.js +34 -15
- package/dist/durable-promise.js.map +1 -1
- package/dist/errors.d.ts +127 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +210 -0
- package/dist/errors.js.map +1 -0
- package/dist/eventbridge.d.ts +117 -0
- package/dist/eventbridge.d.ts.map +1 -0
- package/dist/eventbridge.js +238 -0
- package/dist/eventbridge.js.map +1 -0
- package/dist/events.d.ts +2 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +1 -1
- package/dist/events.js.map +1 -1
- package/dist/execution-queue.d.ts.map +1 -1
- package/dist/execution-queue.js +4 -5
- package/dist/execution-queue.js.map +1 -1
- package/dist/index.d.ts +37 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +112 -6
- package/dist/index.js.map +1 -1
- package/dist/linguistic.d.ts +3 -108
- package/dist/linguistic.d.ts.map +1 -1
- package/dist/linguistic.js +3 -372
- package/dist/linguistic.js.map +1 -1
- package/dist/logger.d.ts +132 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +137 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory-provider.d.ts +129 -0
- package/dist/memory-provider.d.ts.map +1 -1
- package/dist/memory-provider.js +592 -257
- package/dist/memory-provider.js.map +1 -1
- package/dist/pg-adapter.d.ts +424 -0
- package/dist/pg-adapter.d.ts.map +1 -0
- package/dist/pg-adapter.js +921 -0
- package/dist/pg-adapter.js.map +1 -0
- package/dist/pipelines-iceberg-emitter.d.ts +327 -0
- package/dist/pipelines-iceberg-emitter.d.ts.map +1 -0
- package/dist/pipelines-iceberg-emitter.js +351 -0
- package/dist/pipelines-iceberg-emitter.js.map +1 -0
- package/dist/provider-capabilities.d.ts +146 -0
- package/dist/provider-capabilities.d.ts.map +1 -0
- package/dist/provider-capabilities.js +214 -0
- package/dist/provider-capabilities.js.map +1 -0
- package/dist/rdb-provider-adapter.d.ts +195 -0
- package/dist/rdb-provider-adapter.d.ts.map +1 -0
- package/dist/rdb-provider-adapter.js +291 -0
- package/dist/rdb-provider-adapter.js.map +1 -0
- package/dist/schema/cascade.d.ts +49 -10
- package/dist/schema/cascade.d.ts.map +1 -1
- package/dist/schema/cascade.js +491 -273
- package/dist/schema/cascade.js.map +1 -1
- package/dist/schema/definition-caches.d.ts +24 -0
- package/dist/schema/definition-caches.d.ts.map +1 -0
- package/dist/schema/definition-caches.js +26 -0
- package/dist/schema/definition-caches.js.map +1 -0
- package/dist/schema/dependency-graph.d.ts +45 -0
- package/dist/schema/dependency-graph.d.ts.map +1 -0
- package/dist/schema/dependency-graph.js +47 -0
- package/dist/schema/dependency-graph.js.map +1 -0
- package/dist/schema/diff.d.ts +103 -0
- package/dist/schema/diff.d.ts.map +1 -0
- package/dist/schema/diff.js +329 -0
- package/dist/schema/diff.js.map +1 -0
- package/dist/schema/entity-operations.d.ts +99 -0
- package/dist/schema/entity-operations.d.ts.map +1 -0
- package/dist/schema/entity-operations.js +818 -0
- package/dist/schema/entity-operations.js.map +1 -0
- package/dist/schema/generation-context.d.ts +202 -0
- package/dist/schema/generation-context.d.ts.map +1 -0
- package/dist/schema/generation-context.js +393 -0
- package/dist/schema/generation-context.js.map +1 -0
- package/dist/schema/index.d.ts +32 -34
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +462 -519
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/migration.d.ts +205 -0
- package/dist/schema/migration.d.ts.map +1 -0
- package/dist/schema/migration.js +327 -0
- package/dist/schema/migration.js.map +1 -0
- package/dist/schema/nl-query-generator.d.ts +68 -0
- package/dist/schema/nl-query-generator.d.ts.map +1 -0
- package/dist/schema/nl-query-generator.js +362 -0
- package/dist/schema/nl-query-generator.js.map +1 -0
- package/dist/schema/nl-query.d.ts +65 -0
- package/dist/schema/nl-query.d.ts.map +1 -0
- package/dist/schema/nl-query.js +178 -0
- package/dist/schema/nl-query.js.map +1 -0
- package/dist/schema/parse.d.ts.map +1 -1
- package/dist/schema/parse.js +152 -89
- package/dist/schema/parse.js.map +1 -1
- package/dist/schema/provider.d.ts +38 -0
- package/dist/schema/provider.d.ts.map +1 -1
- package/dist/schema/provider.js +15 -7
- package/dist/schema/provider.js.map +1 -1
- package/dist/schema/resolve.d.ts +46 -5
- package/dist/schema/resolve.d.ts.map +1 -1
- package/dist/schema/resolve.js +334 -117
- package/dist/schema/resolve.js.map +1 -1
- package/dist/schema/search-utils.d.ts +76 -0
- package/dist/schema/search-utils.d.ts.map +1 -0
- package/dist/schema/search-utils.js +86 -0
- package/dist/schema/search-utils.js.map +1 -0
- package/dist/schema/seed.d.ts +53 -0
- package/dist/schema/seed.d.ts.map +1 -0
- package/dist/schema/seed.js +94 -0
- package/dist/schema/seed.js.map +1 -0
- package/dist/schema/semantic.d.ts +11 -0
- package/dist/schema/semantic.d.ts.map +1 -1
- package/dist/schema/semantic.js +262 -68
- package/dist/schema/semantic.js.map +1 -1
- package/dist/schema/sub-apis.d.ts +52 -0
- package/dist/schema/sub-apis.d.ts.map +1 -0
- package/dist/schema/sub-apis.js +216 -0
- package/dist/schema/sub-apis.js.map +1 -0
- package/dist/schema/system-entities.d.ts +42 -0
- package/dist/schema/system-entities.d.ts.map +1 -0
- package/dist/schema/system-entities.js +101 -0
- package/dist/schema/system-entities.js.map +1 -0
- package/dist/schema/types.d.ts +91 -9
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/union-fallback.d.ts +219 -0
- package/dist/schema/union-fallback.d.ts.map +1 -0
- package/dist/schema/union-fallback.js +331 -0
- package/dist/schema/union-fallback.js.map +1 -0
- package/dist/schema/value-generators/ai.d.ts +54 -0
- package/dist/schema/value-generators/ai.d.ts.map +1 -0
- package/dist/schema/value-generators/ai.js +136 -0
- package/dist/schema/value-generators/ai.js.map +1 -0
- package/dist/schema/value-generators/index.d.ts +126 -0
- package/dist/schema/value-generators/index.d.ts.map +1 -0
- package/dist/schema/value-generators/index.js +219 -0
- package/dist/schema/value-generators/index.js.map +1 -0
- package/dist/schema/value-generators/placeholder.d.ts +52 -0
- package/dist/schema/value-generators/placeholder.d.ts.map +1 -0
- package/dist/schema/value-generators/placeholder.js +328 -0
- package/dist/schema/value-generators/placeholder.js.map +1 -0
- package/dist/schema/value-generators/types.d.ts +116 -0
- package/dist/schema/value-generators/types.d.ts.map +1 -0
- package/dist/schema/value-generators/types.js +11 -0
- package/dist/schema/value-generators/types.js.map +1 -0
- package/dist/schema/verb-derivation.d.ts +167 -0
- package/dist/schema/verb-derivation.d.ts.map +1 -0
- package/dist/schema/verb-derivation.js +281 -0
- package/dist/schema/verb-derivation.js.map +1 -0
- package/dist/schema/version.d.ts +111 -0
- package/dist/schema/version.d.ts.map +1 -0
- package/dist/schema/version.js +190 -0
- package/dist/schema/version.js.map +1 -0
- package/dist/schema.d.ts +1095 -23
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2854 -38
- package/dist/schema.js.map +1 -1
- package/dist/semantic-vectors.d.ts +39 -0
- package/dist/semantic-vectors.d.ts.map +1 -0
- package/dist/semantic-vectors.js +334 -0
- package/dist/semantic-vectors.js.map +1 -0
- package/dist/semantic.d.ts +29 -1
- package/dist/semantic.d.ts.map +1 -1
- package/dist/semantic.js +26 -16
- package/dist/semantic.js.map +1 -1
- package/dist/telemetry.d.ts +128 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +305 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/tests.d.ts.map +1 -1
- package/dist/tests.js +30 -22
- package/dist/tests.js.map +1 -1
- package/dist/type-guards.d.ts +212 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +318 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/types.d.ts +33 -245
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +62 -72
- package/dist/types.js.map +1 -1
- package/dist/validation.d.ts +165 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +639 -0
- package/dist/validation.js.map +1 -0
- package/dist/worker/db-provider.d.ts +168 -0
- package/dist/worker/db-provider.d.ts.map +1 -0
- package/dist/worker/db-provider.js +277 -0
- package/dist/worker/db-provider.js.map +1 -0
- package/dist/worker/index.d.ts +35 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +37 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker.d.ts +779 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +2786 -0
- package/dist/worker.js.map +1 -0
- package/package.json +38 -8
- package/src/docs-rels/migrations/0001-init.sql +125 -0
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Operations Factory
|
|
3
|
+
*
|
|
4
|
+
* Contains the createEntityOperations<T>() function that creates typed CRUD operations
|
|
5
|
+
* for database entities. This includes get, list, find, create, update, upsert, delete,
|
|
6
|
+
* search, forEach, draft, and resolve operations.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import { resolveProvider, hasSemanticSearch, hasHybridSearch, hasTransactionSupport, } from './provider.js';
|
|
11
|
+
import { hydrateEntity, resolveReferenceSpec, isPromptField } from './resolve.js';
|
|
12
|
+
import { resolveForwardExact, generateAIFields, generateContextAwareValue, generateNaturalLanguageContent, generateEntity, } from './cascade.js';
|
|
13
|
+
import { resolveBackwardFuzzy, resolveForwardFuzzy, getFuzzyThreshold } from './semantic.js';
|
|
14
|
+
import { isNotFoundError, isEntityExistsError, wrapDatabaseError, DatabaseError, CapabilityNotSupportedError, } from '../errors.js';
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Helper Functions
|
|
17
|
+
// =============================================================================
|
|
18
|
+
/** Metadata field suffixes that should be stripped from entity data */
|
|
19
|
+
const METADATA_SUFFIXES = ['$matchedType', '$score', '$fallbackUsed', '$searchedTypes'];
|
|
20
|
+
/**
|
|
21
|
+
* Check if a key is a metadata field that should be excluded from entity data
|
|
22
|
+
*/
|
|
23
|
+
function isMetadataField(key) {
|
|
24
|
+
if (key.startsWith('$'))
|
|
25
|
+
return true;
|
|
26
|
+
return METADATA_SUFFIXES.some((suffix) => key.endsWith(suffix));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Remove metadata fields from entity data
|
|
30
|
+
*/
|
|
31
|
+
function cleanEntityData(data) {
|
|
32
|
+
const cleaned = {};
|
|
33
|
+
for (const [key, value] of Object.entries(data)) {
|
|
34
|
+
if (!isMetadataField(key)) {
|
|
35
|
+
cleaned[key] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return cleaned;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Apply where filters to a list of records
|
|
42
|
+
*/
|
|
43
|
+
function applyWhereFilter(records, where) {
|
|
44
|
+
return records.filter((record) => Object.entries(where).every(([key, value]) => record[key] === value));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Apply pagination (offset/limit) to a list of records
|
|
48
|
+
*/
|
|
49
|
+
function applyPagination(records, offset, limit) {
|
|
50
|
+
if (!limit)
|
|
51
|
+
return records;
|
|
52
|
+
const start = offset ?? 0;
|
|
53
|
+
return records.slice(start, start + limit);
|
|
54
|
+
}
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Entity Operations Factory
|
|
57
|
+
// =============================================================================
|
|
58
|
+
/**
|
|
59
|
+
* Create typed CRUD operations for a database entity
|
|
60
|
+
*
|
|
61
|
+
* This function creates the full set of operations for working with a specific
|
|
62
|
+
* entity type in the database, including:
|
|
63
|
+
* - get/list/find for reading
|
|
64
|
+
* - create/update/upsert/delete for writing
|
|
65
|
+
* - search/semanticSearch/hybridSearch for querying
|
|
66
|
+
* - forEach for iteration
|
|
67
|
+
* - draft/resolve for two-phase entity creation
|
|
68
|
+
*
|
|
69
|
+
* @param typeName - The type name for the entity
|
|
70
|
+
* @param entity - The parsed entity definition
|
|
71
|
+
* @param schema - The full parsed schema
|
|
72
|
+
* @returns EntityOperations<T> with all available methods
|
|
73
|
+
*/
|
|
74
|
+
export function createEntityOperations(typeName, entity, schema) {
|
|
75
|
+
return {
|
|
76
|
+
async get(id) {
|
|
77
|
+
const provider = await resolveProvider();
|
|
78
|
+
const result = await provider.get(typeName, id);
|
|
79
|
+
if (!result)
|
|
80
|
+
return null;
|
|
81
|
+
return hydrateEntity(result, entity, schema, resolveProvider);
|
|
82
|
+
},
|
|
83
|
+
async list(options) {
|
|
84
|
+
try {
|
|
85
|
+
const provider = await resolveProvider();
|
|
86
|
+
const results = await provider.list(typeName, options);
|
|
87
|
+
return Promise.all(results.map((r) => hydrateEntity(r, entity, schema, resolveProvider)));
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// Handle error with callback if provided
|
|
91
|
+
if (options?.onError) {
|
|
92
|
+
const wrappedError = error instanceof Error ? error : new Error(String(error));
|
|
93
|
+
const fallback = options.onError(wrappedError);
|
|
94
|
+
return (fallback ?? []);
|
|
95
|
+
}
|
|
96
|
+
// Suppress errors if requested
|
|
97
|
+
if (options?.suppressErrors) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
async find(where) {
|
|
104
|
+
const provider = await resolveProvider();
|
|
105
|
+
const results = await provider.list(typeName, {
|
|
106
|
+
where: where,
|
|
107
|
+
});
|
|
108
|
+
return Promise.all(results.map((r) => hydrateEntity(r, entity, schema, resolveProvider)));
|
|
109
|
+
},
|
|
110
|
+
async search(query, options) {
|
|
111
|
+
const provider = await resolveProvider();
|
|
112
|
+
const results = await provider.search(typeName, query, options);
|
|
113
|
+
return Promise.all(results.map((r) => hydrateEntity(r, entity, schema, resolveProvider)));
|
|
114
|
+
},
|
|
115
|
+
async create(idOrData, maybeData, maybeOptions) {
|
|
116
|
+
const providedId = typeof idOrData === 'string' ? idOrData : undefined;
|
|
117
|
+
const data = typeof idOrData === 'string'
|
|
118
|
+
? maybeData
|
|
119
|
+
: idOrData;
|
|
120
|
+
// Options can be passed as third arg when id is provided, or as second arg when id is not
|
|
121
|
+
const options = typeof idOrData === 'string' ? maybeOptions : maybeData;
|
|
122
|
+
const entityId = providedId || crypto.randomUUID();
|
|
123
|
+
try {
|
|
124
|
+
const provider = await resolveProvider();
|
|
125
|
+
// Use transaction if provider supports it, to ensure atomicity of cascade
|
|
126
|
+
const useTransaction = hasTransactionSupport(provider);
|
|
127
|
+
const txn = useTransaction ? await provider.beginTransaction() : null;
|
|
128
|
+
// The target for write operations: transaction if available, otherwise provider directly
|
|
129
|
+
const writeTarget = txn ?? provider;
|
|
130
|
+
try {
|
|
131
|
+
let finalData;
|
|
132
|
+
let pendingRelations = [];
|
|
133
|
+
let fuzzyPendingRelations = [];
|
|
134
|
+
// Check if data has already been through draft/resolve (called from schema.ts wrapper)
|
|
135
|
+
if (options?._preResolved) {
|
|
136
|
+
// Data is already resolved - skip draft/resolve phases
|
|
137
|
+
const cleanedData = cleanEntityData(data);
|
|
138
|
+
// Still need to resolve backward fuzzy references and generate AI fields
|
|
139
|
+
const backwardResolvedData = await resolveBackwardFuzzy(typeName, cleanedData, entity, schema, provider);
|
|
140
|
+
finalData = await generateAIFields(backwardResolvedData, typeName, entity, schema, provider);
|
|
141
|
+
// Extract pending relations from already-resolved data by checking field values
|
|
142
|
+
// that look like entity IDs and match relation field definitions
|
|
143
|
+
for (const [fieldName, field] of entity.fields) {
|
|
144
|
+
if (field.isRelation && field.relatedType) {
|
|
145
|
+
const resolvedValue = cleanedData[fieldName];
|
|
146
|
+
if (!resolvedValue)
|
|
147
|
+
continue;
|
|
148
|
+
const isFuzzy = field.operator === '~>' || (field.matchMode && field.matchMode === 'fuzzy');
|
|
149
|
+
if (Array.isArray(resolvedValue)) {
|
|
150
|
+
for (const targetId of resolvedValue) {
|
|
151
|
+
if (typeof targetId === 'string') {
|
|
152
|
+
if (isFuzzy) {
|
|
153
|
+
fuzzyPendingRelations.push({
|
|
154
|
+
fieldName,
|
|
155
|
+
targetType: field.relatedType,
|
|
156
|
+
targetId,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
pendingRelations.push({
|
|
161
|
+
fieldName,
|
|
162
|
+
targetType: field.relatedType,
|
|
163
|
+
targetId,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else if (typeof resolvedValue === 'string') {
|
|
170
|
+
if (isFuzzy) {
|
|
171
|
+
fuzzyPendingRelations.push({
|
|
172
|
+
fieldName,
|
|
173
|
+
targetType: field.relatedType,
|
|
174
|
+
targetId: resolvedValue,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
pendingRelations.push({
|
|
179
|
+
fieldName,
|
|
180
|
+
targetType: field.relatedType,
|
|
181
|
+
targetId: resolvedValue,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// Phase 1: Draft - generate entity with $refs for unresolved references
|
|
190
|
+
// Use _skipPromptlessRefs to avoid generating refs for array fields without prompts
|
|
191
|
+
// (those are handled by cascade generation when enabled)
|
|
192
|
+
const draft = await this.draft(data, {
|
|
193
|
+
_skipPromptlessRefs: true,
|
|
194
|
+
});
|
|
195
|
+
// Phase 2: Resolve - resolve $refs to actual entity IDs
|
|
196
|
+
// Inject $id and $type into draft for context during resolution
|
|
197
|
+
const draftWithContext = draft;
|
|
198
|
+
draftWithContext['$id'] = entityId;
|
|
199
|
+
draftWithContext['$type'] = typeName;
|
|
200
|
+
const resolved = await this.resolve(draft);
|
|
201
|
+
const resolvedData = resolved;
|
|
202
|
+
// Extract pending relations from resolved data
|
|
203
|
+
// The resolve phase sets metadata fields like ${field}$matchedType and ${field}$score
|
|
204
|
+
const refs = draft['$refs'];
|
|
205
|
+
if (refs) {
|
|
206
|
+
for (const [fieldName, refSpec] of Object.entries(refs)) {
|
|
207
|
+
const resolvedValue = resolvedData[fieldName];
|
|
208
|
+
if (!resolvedValue)
|
|
209
|
+
continue;
|
|
210
|
+
if (Array.isArray(refSpec)) {
|
|
211
|
+
// Array of references
|
|
212
|
+
const resolvedIds = Array.isArray(resolvedValue) ? resolvedValue : [resolvedValue];
|
|
213
|
+
for (let i = 0; i < resolvedIds.length; i++) {
|
|
214
|
+
const targetId = resolvedIds[i];
|
|
215
|
+
const spec = refSpec[i] || refSpec[0];
|
|
216
|
+
if (spec.matchMode === 'fuzzy') {
|
|
217
|
+
const matchedType = resolvedData[`${fieldName}$matchedType`];
|
|
218
|
+
const similarity = resolvedData[`${fieldName}$score`];
|
|
219
|
+
fuzzyPendingRelations.push({
|
|
220
|
+
fieldName,
|
|
221
|
+
targetType: matchedType || spec.type,
|
|
222
|
+
targetId,
|
|
223
|
+
...(similarity !== undefined && { similarity }),
|
|
224
|
+
...(matchedType !== undefined && { matchedType }),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
pendingRelations.push({
|
|
229
|
+
fieldName,
|
|
230
|
+
targetType: spec.type,
|
|
231
|
+
targetId,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// Single reference
|
|
238
|
+
const targetId = resolvedValue;
|
|
239
|
+
if (refSpec.matchMode === 'fuzzy') {
|
|
240
|
+
const matchedType = resolvedData[`${fieldName}$matchedType`];
|
|
241
|
+
const similarity = resolvedData[`${fieldName}$score`];
|
|
242
|
+
fuzzyPendingRelations.push({
|
|
243
|
+
fieldName,
|
|
244
|
+
targetType: matchedType || refSpec.type,
|
|
245
|
+
targetId,
|
|
246
|
+
...(similarity !== undefined && { similarity }),
|
|
247
|
+
...(matchedType !== undefined && { matchedType }),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
pendingRelations.push({
|
|
252
|
+
fieldName,
|
|
253
|
+
targetType: refSpec.type,
|
|
254
|
+
targetId,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Clean up resolution metadata from the data before further processing
|
|
261
|
+
const cleanedData = cleanEntityData(resolvedData);
|
|
262
|
+
// Phase 3: Resolve backward fuzzy references (<~)
|
|
263
|
+
const backwardResolvedData = await resolveBackwardFuzzy(typeName, cleanedData, entity, schema, provider);
|
|
264
|
+
// Phase 4: Generate AI fields based on $instructions and $context
|
|
265
|
+
finalData = await generateAIFields(backwardResolvedData, typeName, entity, schema, provider);
|
|
266
|
+
}
|
|
267
|
+
// Phase 5: Persist the entity
|
|
268
|
+
const result = await writeTarget.create(typeName, entityId, finalData);
|
|
269
|
+
// Phase 6: Create relationship edges for exact forward references
|
|
270
|
+
for (const rel of pendingRelations) {
|
|
271
|
+
await writeTarget.relate(typeName, entityId, rel.fieldName, rel.targetType, rel.targetId);
|
|
272
|
+
}
|
|
273
|
+
// Phase 7: Create relationship edges for fuzzy forward references
|
|
274
|
+
const createdEdgeIds = new Set();
|
|
275
|
+
for (const rel of fuzzyPendingRelations) {
|
|
276
|
+
await writeTarget.relate(typeName, entityId, rel.fieldName, rel.targetType, rel.targetId, {
|
|
277
|
+
matchMode: 'fuzzy',
|
|
278
|
+
...(rel.similarity !== undefined && { similarity: rel.similarity }),
|
|
279
|
+
...(rel.matchedType !== undefined && { matchedType: rel.matchedType }),
|
|
280
|
+
});
|
|
281
|
+
const edgeId = `${typeName}_${rel.fieldName}_${entityId}_${rel.targetId}`;
|
|
282
|
+
if (!createdEdgeIds.has(edgeId)) {
|
|
283
|
+
createdEdgeIds.add(edgeId);
|
|
284
|
+
try {
|
|
285
|
+
await writeTarget.create('Edge', edgeId, {
|
|
286
|
+
from: typeName,
|
|
287
|
+
name: rel.fieldName,
|
|
288
|
+
to: rel.targetType,
|
|
289
|
+
direction: 'forward',
|
|
290
|
+
matchMode: 'fuzzy',
|
|
291
|
+
similarity: rel.similarity,
|
|
292
|
+
matchedType: rel.matchedType,
|
|
293
|
+
fromId: entityId,
|
|
294
|
+
toId: rel.targetId,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
// Only ignore actual duplicate key errors, propagate other errors
|
|
299
|
+
if (!isEntityExistsError(error)) {
|
|
300
|
+
throw wrapDatabaseError(error, 'create', 'Edge', edgeId);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Commit the transaction if we used one
|
|
306
|
+
if (txn)
|
|
307
|
+
await txn.commit();
|
|
308
|
+
return hydrateEntity(result, entity, schema, resolveProvider);
|
|
309
|
+
}
|
|
310
|
+
catch (innerError) {
|
|
311
|
+
// Rollback on any error if we have a transaction
|
|
312
|
+
if (txn) {
|
|
313
|
+
try {
|
|
314
|
+
await txn.rollback();
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
// Ignore rollback errors
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
throw innerError;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
// Wrap provider errors with context
|
|
325
|
+
if (error instanceof DatabaseError)
|
|
326
|
+
throw error;
|
|
327
|
+
throw wrapDatabaseError(error, 'create', typeName, entityId);
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
async update(id, data) {
|
|
331
|
+
try {
|
|
332
|
+
const provider = await resolveProvider();
|
|
333
|
+
const result = await provider.update(typeName, id, data);
|
|
334
|
+
return hydrateEntity(result, entity, schema, resolveProvider);
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
if (error instanceof DatabaseError)
|
|
338
|
+
throw error;
|
|
339
|
+
throw wrapDatabaseError(error, 'update', typeName, id);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
async upsert(id, data) {
|
|
343
|
+
try {
|
|
344
|
+
const provider = await resolveProvider();
|
|
345
|
+
const existing = await provider.get(typeName, id);
|
|
346
|
+
if (existing) {
|
|
347
|
+
const result = await provider.update(typeName, id, data);
|
|
348
|
+
return hydrateEntity(result, entity, schema, resolveProvider);
|
|
349
|
+
}
|
|
350
|
+
const result = await provider.create(typeName, id, data);
|
|
351
|
+
return hydrateEntity(result, entity, schema, resolveProvider);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
if (error instanceof DatabaseError)
|
|
355
|
+
throw error;
|
|
356
|
+
throw wrapDatabaseError(error, 'upsert', typeName, id);
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
async delete(id) {
|
|
360
|
+
try {
|
|
361
|
+
const provider = await resolveProvider();
|
|
362
|
+
return provider.delete(typeName, id);
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
if (error instanceof DatabaseError)
|
|
366
|
+
throw error;
|
|
367
|
+
throw wrapDatabaseError(error, 'delete', typeName, id);
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
async forEach(optionsOrCallback, maybeCallback) {
|
|
371
|
+
const options = typeof optionsOrCallback === 'function' ? undefined : optionsOrCallback;
|
|
372
|
+
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
|
|
373
|
+
const items = await this.list(options);
|
|
374
|
+
for (const item of items) {
|
|
375
|
+
await callback(item);
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
async semanticSearch(query, options) {
|
|
379
|
+
const provider = await resolveProvider();
|
|
380
|
+
if (!hasSemanticSearch(provider)) {
|
|
381
|
+
throw new CapabilityNotSupportedError('hasSemanticSearch', `Semantic search is not supported by the current provider. ` +
|
|
382
|
+
`The provider does not implement the semanticSearch method required for vector similarity search.`, `Use the regular search() method instead, which performs basic text matching.`);
|
|
383
|
+
}
|
|
384
|
+
const results = await provider.semanticSearch(typeName, query, options);
|
|
385
|
+
return Promise.all(results.map((r) => ({
|
|
386
|
+
...hydrateEntity(r, entity, schema, resolveProvider),
|
|
387
|
+
$score: r.$score,
|
|
388
|
+
})));
|
|
389
|
+
},
|
|
390
|
+
async hybridSearch(query, options) {
|
|
391
|
+
const provider = await resolveProvider();
|
|
392
|
+
if (!hasHybridSearch(provider)) {
|
|
393
|
+
throw new CapabilityNotSupportedError('hasHybridSearch', `Hybrid search is not supported by the current provider. ` +
|
|
394
|
+
`The provider does not implement the hybridSearch method required for combined FTS and vector search.`, `Use the regular search() method instead, which performs basic text matching.`);
|
|
395
|
+
}
|
|
396
|
+
const results = await provider.hybridSearch(typeName, query, options);
|
|
397
|
+
return Promise.all(results.map((r) => ({
|
|
398
|
+
...hydrateEntity(r, entity, schema, resolveProvider),
|
|
399
|
+
$rrfScore: r.$rrfScore,
|
|
400
|
+
$ftsRank: r.$ftsRank,
|
|
401
|
+
$semanticRank: r.$semanticRank,
|
|
402
|
+
$score: r.$score,
|
|
403
|
+
})));
|
|
404
|
+
},
|
|
405
|
+
async draft(data, options) {
|
|
406
|
+
const draftData = { ...data, $phase: 'draft' };
|
|
407
|
+
const refs = {};
|
|
408
|
+
// Get the raw schema to detect prompt fields and source instructions
|
|
409
|
+
const rawSchema = entity.schema || {};
|
|
410
|
+
const sourceInstructions = rawSchema['$instructions'];
|
|
411
|
+
const hasContextDependencies = Array.isArray(rawSchema['$context']) && rawSchema['$context'].length > 0;
|
|
412
|
+
for (const [fieldName, field] of entity.fields) {
|
|
413
|
+
if (draftData[fieldName] !== undefined && draftData[fieldName] !== null) {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (field.operator && field.relatedType) {
|
|
417
|
+
// Skip optional relation fields - they shouldn't auto-generate
|
|
418
|
+
if (field.isOptional)
|
|
419
|
+
continue;
|
|
420
|
+
// Skip backward references - they're resolved lazily via hydrateEntity
|
|
421
|
+
// Backward refs find entities that reference US, not entities we create
|
|
422
|
+
if (field.operator === '<-' || field.operator === '<~')
|
|
423
|
+
continue;
|
|
424
|
+
// Relationship field with operator
|
|
425
|
+
const matchMode = field.matchMode ?? (field.operator.includes('~') ? 'fuzzy' : 'exact');
|
|
426
|
+
if (field.isArray) {
|
|
427
|
+
// Get hint value for array fuzzy matching (e.g., categoriesHint for categories field)
|
|
428
|
+
const hintKey = `${fieldName}Hint`;
|
|
429
|
+
const hintValue = data[hintKey];
|
|
430
|
+
// Get fuzzy threshold from entity schema
|
|
431
|
+
const threshold = field.threshold ?? getFuzzyThreshold(entity);
|
|
432
|
+
// If hint is an array, create one ref spec per hint item
|
|
433
|
+
// Skip promptless fields only when _skipPromptlessRefs is set (internal create() without cascade)
|
|
434
|
+
const shouldSkipPromptless = options?._skipPromptlessRefs && !field.prompt;
|
|
435
|
+
const hints = Array.isArray(hintValue)
|
|
436
|
+
? hintValue
|
|
437
|
+
: hintValue
|
|
438
|
+
? [hintValue]
|
|
439
|
+
: shouldSkipPromptless
|
|
440
|
+
? []
|
|
441
|
+
: [
|
|
442
|
+
generateNaturalLanguageContent(fieldName, field.prompt, field.relatedType, data),
|
|
443
|
+
];
|
|
444
|
+
const refSpecs = hints.map((hint) => {
|
|
445
|
+
const generatedText = String(hint);
|
|
446
|
+
const spec = {
|
|
447
|
+
field: fieldName,
|
|
448
|
+
operator: field.operator,
|
|
449
|
+
type: field.relatedType,
|
|
450
|
+
matchMode,
|
|
451
|
+
resolved: false,
|
|
452
|
+
...(field.unionTypes !== undefined && { unionTypes: field.unionTypes }),
|
|
453
|
+
...(field.prompt !== undefined && { prompt: field.prompt }),
|
|
454
|
+
...(generatedText !== undefined && { generatedText }),
|
|
455
|
+
...(sourceInstructions !== undefined && { sourceInstructions }),
|
|
456
|
+
...(threshold !== undefined && { threshold }),
|
|
457
|
+
};
|
|
458
|
+
return spec;
|
|
459
|
+
});
|
|
460
|
+
// Store the combined generated text for the draft display
|
|
461
|
+
draftData[fieldName] = hints.map(String).join(', ');
|
|
462
|
+
refs[fieldName] = refSpecs;
|
|
463
|
+
if (options?.stream && options.onChunk) {
|
|
464
|
+
for (const spec of refSpecs) {
|
|
465
|
+
if (spec.generatedText) {
|
|
466
|
+
options.onChunk(spec.generatedText);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
// Get hint value for fuzzy matching (e.g., contentHint for content field)
|
|
473
|
+
const hintKey = `${fieldName}Hint`;
|
|
474
|
+
const hintValue = data[hintKey];
|
|
475
|
+
// Get fuzzy threshold from entity schema
|
|
476
|
+
const threshold = field.threshold ?? getFuzzyThreshold(entity);
|
|
477
|
+
// Use hint if available, otherwise generate natural language content
|
|
478
|
+
const generatedText = hintValue ||
|
|
479
|
+
generateNaturalLanguageContent(fieldName, field.prompt, field.relatedType, data);
|
|
480
|
+
draftData[fieldName] = generatedText;
|
|
481
|
+
const spec = {
|
|
482
|
+
field: fieldName,
|
|
483
|
+
operator: field.operator,
|
|
484
|
+
type: field.relatedType,
|
|
485
|
+
matchMode,
|
|
486
|
+
resolved: false,
|
|
487
|
+
...(field.unionTypes !== undefined && { unionTypes: field.unionTypes }),
|
|
488
|
+
...(field.prompt !== undefined && { prompt: field.prompt }),
|
|
489
|
+
...(generatedText !== undefined && { generatedText }),
|
|
490
|
+
...(sourceInstructions !== undefined && { sourceInstructions }),
|
|
491
|
+
...(threshold !== undefined && { threshold }),
|
|
492
|
+
};
|
|
493
|
+
refs[fieldName] = spec;
|
|
494
|
+
if (options?.stream && options.onChunk) {
|
|
495
|
+
options.onChunk(generatedText);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else if (!field.isRelation) {
|
|
500
|
+
// Non-relationship field - check if it's a prompt field
|
|
501
|
+
const isPrompt = isPromptField(field);
|
|
502
|
+
if (isPrompt && !hasContextDependencies) {
|
|
503
|
+
// Generate content for prompt field using the type as the prompt
|
|
504
|
+
// NOTE: Skip generation when entity has $context dependencies, as those fields
|
|
505
|
+
// need the pre-fetched context to generate properly (done in generateAIFields)
|
|
506
|
+
const generatedText = generateContextAwareValue(fieldName, typeName, field.type, field.type, data);
|
|
507
|
+
draftData[fieldName] = generatedText;
|
|
508
|
+
if (options?.stream && options.onChunk) {
|
|
509
|
+
options.onChunk(generatedText);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
draftData['$refs'] = refs;
|
|
515
|
+
return draftData;
|
|
516
|
+
},
|
|
517
|
+
async resolve(draft, options) {
|
|
518
|
+
// Draft<T> interface requires $phase: 'draft', so we can access it directly
|
|
519
|
+
if (draft.$phase !== 'draft') {
|
|
520
|
+
throw new Error('Cannot resolve entity: not a draft (missing $phase: "draft")');
|
|
521
|
+
}
|
|
522
|
+
const provider = await resolveProvider();
|
|
523
|
+
const resolved = { ...draft };
|
|
524
|
+
const errors = [];
|
|
525
|
+
delete resolved['$refs'];
|
|
526
|
+
resolved['$phase'] = 'resolved';
|
|
527
|
+
const refs = draft.$refs;
|
|
528
|
+
for (const [fieldName, refSpec] of Object.entries(refs)) {
|
|
529
|
+
try {
|
|
530
|
+
if (Array.isArray(refSpec)) {
|
|
531
|
+
const resolvedIds = [];
|
|
532
|
+
for (const spec of refSpec) {
|
|
533
|
+
const resolvedId = await resolveReferenceSpec(spec, resolved, schema, provider, generateContextAwareValue, generateEntity);
|
|
534
|
+
if (resolvedId) {
|
|
535
|
+
resolvedIds.push(resolvedId);
|
|
536
|
+
options?.onResolved?.(fieldName, resolvedId);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
resolved[fieldName] = resolvedIds;
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
const resolvedId = await resolveReferenceSpec(refSpec, resolved, schema, provider, generateContextAwareValue, generateEntity);
|
|
543
|
+
if (resolvedId) {
|
|
544
|
+
resolved[fieldName] = resolvedId;
|
|
545
|
+
options?.onResolved?.(fieldName, resolvedId);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch (err) {
|
|
550
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
551
|
+
if (options?.onError === 'skip') {
|
|
552
|
+
errors.push({ field: fieldName, error: errorMsg });
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
throw err;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (errors.length > 0 || options?.onError === 'skip') {
|
|
560
|
+
// resolved is typed as Record<string, unknown>, so we can assign $errors directly
|
|
561
|
+
resolved['$errors'] = errors;
|
|
562
|
+
}
|
|
563
|
+
return resolved;
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
// =============================================================================
|
|
568
|
+
// Edge Entity Operations Factory
|
|
569
|
+
// =============================================================================
|
|
570
|
+
/**
|
|
571
|
+
* Create specialized operations for the Edge entity type
|
|
572
|
+
*
|
|
573
|
+
* Edge entities are auto-generated from schema relationships and have
|
|
574
|
+
* restricted write operations (no manual create/update/delete).
|
|
575
|
+
*
|
|
576
|
+
* @param schemaEdgeRecords - Edge records derived from the schema
|
|
577
|
+
* @param getProvider - Function to get the database provider
|
|
578
|
+
* @returns EntityOperations for Edge type
|
|
579
|
+
*/
|
|
580
|
+
/**
|
|
581
|
+
* Create specialized operations for the Noun entity type
|
|
582
|
+
*
|
|
583
|
+
* Noun entities are auto-generated from schema entity types and have
|
|
584
|
+
* restricted write operations (no manual create/update/delete).
|
|
585
|
+
*
|
|
586
|
+
* @param nounRecords - Noun records derived from the schema entity types
|
|
587
|
+
* @returns EntityOperations for Noun type
|
|
588
|
+
*/
|
|
589
|
+
export function createNounEntityOperations(nounRecords) {
|
|
590
|
+
const addNounMetadata = (n) => ({
|
|
591
|
+
...n,
|
|
592
|
+
$id: n['$id'] || n['name'],
|
|
593
|
+
$type: 'Noun',
|
|
594
|
+
});
|
|
595
|
+
return {
|
|
596
|
+
async get(id) {
|
|
597
|
+
return nounRecords.find((n) => n['name'] === id || n['$id'] === id) ?? null;
|
|
598
|
+
},
|
|
599
|
+
async list(options) {
|
|
600
|
+
let results = [...nounRecords];
|
|
601
|
+
if (options?.where) {
|
|
602
|
+
results = applyWhereFilter(results, options.where);
|
|
603
|
+
}
|
|
604
|
+
results = applyPagination(results, options?.offset, options?.limit);
|
|
605
|
+
return results.map(addNounMetadata);
|
|
606
|
+
},
|
|
607
|
+
async find(where) {
|
|
608
|
+
return applyWhereFilter([...nounRecords], where).map(addNounMetadata);
|
|
609
|
+
},
|
|
610
|
+
async search(query) {
|
|
611
|
+
const queryLower = query.toLowerCase();
|
|
612
|
+
return nounRecords
|
|
613
|
+
.filter((n) => String(n['name']).toLowerCase().includes(queryLower) ||
|
|
614
|
+
String(n['singular']).toLowerCase().includes(queryLower) ||
|
|
615
|
+
String(n['plural']).toLowerCase().includes(queryLower) ||
|
|
616
|
+
String(n['description'] || '')
|
|
617
|
+
.toLowerCase()
|
|
618
|
+
.includes(queryLower))
|
|
619
|
+
.map(addNounMetadata);
|
|
620
|
+
},
|
|
621
|
+
async create() {
|
|
622
|
+
throw new Error('Cannot manually create Noun records - they are auto-generated from schema');
|
|
623
|
+
},
|
|
624
|
+
async update() {
|
|
625
|
+
throw new Error('Cannot manually update Noun records - they are auto-generated from schema');
|
|
626
|
+
},
|
|
627
|
+
async upsert() {
|
|
628
|
+
throw new Error('Cannot manually upsert Noun records - they are auto-generated from schema');
|
|
629
|
+
},
|
|
630
|
+
async delete() {
|
|
631
|
+
throw new Error('Cannot manually delete Noun records - they are auto-generated from schema');
|
|
632
|
+
},
|
|
633
|
+
async forEach(optionsOrCallback, maybeCallback) {
|
|
634
|
+
const options = typeof optionsOrCallback === 'function' ? undefined : optionsOrCallback;
|
|
635
|
+
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
|
|
636
|
+
for (const item of await this.list(options)) {
|
|
637
|
+
await callback(item);
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
async semanticSearch() {
|
|
641
|
+
return [];
|
|
642
|
+
},
|
|
643
|
+
async hybridSearch() {
|
|
644
|
+
return [];
|
|
645
|
+
},
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Create specialized operations for the Verb entity type
|
|
650
|
+
*
|
|
651
|
+
* Verb entities are the standard verb definitions (create, update, delete, etc.)
|
|
652
|
+
* and any custom verbs defined through the verbs API.
|
|
653
|
+
*
|
|
654
|
+
* @param verbRecords - Verb records with conjugation forms
|
|
655
|
+
* @returns EntityOperations for Verb type
|
|
656
|
+
*/
|
|
657
|
+
export function createVerbEntityOperations(verbRecords) {
|
|
658
|
+
const addVerbMetadata = (v) => ({
|
|
659
|
+
...v,
|
|
660
|
+
$id: v['$id'] || v['action'],
|
|
661
|
+
$type: 'Verb',
|
|
662
|
+
});
|
|
663
|
+
return {
|
|
664
|
+
async get(id) {
|
|
665
|
+
return verbRecords.find((v) => v['action'] === id || v['$id'] === id) ?? null;
|
|
666
|
+
},
|
|
667
|
+
async list(options) {
|
|
668
|
+
let results = [...verbRecords];
|
|
669
|
+
if (options?.where) {
|
|
670
|
+
results = applyWhereFilter(results, options.where);
|
|
671
|
+
}
|
|
672
|
+
results = applyPagination(results, options?.offset, options?.limit);
|
|
673
|
+
return results.map(addVerbMetadata);
|
|
674
|
+
},
|
|
675
|
+
async find(where) {
|
|
676
|
+
return applyWhereFilter([...verbRecords], where).map(addVerbMetadata);
|
|
677
|
+
},
|
|
678
|
+
async search(query) {
|
|
679
|
+
const queryLower = query.toLowerCase();
|
|
680
|
+
return verbRecords
|
|
681
|
+
.filter((v) => String(v['action']).toLowerCase().includes(queryLower) ||
|
|
682
|
+
String(v['actor'] || '')
|
|
683
|
+
.toLowerCase()
|
|
684
|
+
.includes(queryLower) ||
|
|
685
|
+
String(v['activity'] || '')
|
|
686
|
+
.toLowerCase()
|
|
687
|
+
.includes(queryLower) ||
|
|
688
|
+
String(v['description'] || '')
|
|
689
|
+
.toLowerCase()
|
|
690
|
+
.includes(queryLower))
|
|
691
|
+
.map(addVerbMetadata);
|
|
692
|
+
},
|
|
693
|
+
async create() {
|
|
694
|
+
throw new Error('Cannot manually create Verb records - use verbs.define() instead');
|
|
695
|
+
},
|
|
696
|
+
async update() {
|
|
697
|
+
throw new Error('Cannot manually update Verb records - use verbs.define() instead');
|
|
698
|
+
},
|
|
699
|
+
async upsert() {
|
|
700
|
+
throw new Error('Cannot manually upsert Verb records - use verbs.define() instead');
|
|
701
|
+
},
|
|
702
|
+
async delete() {
|
|
703
|
+
throw new Error('Cannot manually delete Verb records');
|
|
704
|
+
},
|
|
705
|
+
async forEach(optionsOrCallback, maybeCallback) {
|
|
706
|
+
const options = typeof optionsOrCallback === 'function' ? undefined : optionsOrCallback;
|
|
707
|
+
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
|
|
708
|
+
for (const item of await this.list(options)) {
|
|
709
|
+
await callback(item);
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
async semanticSearch() {
|
|
713
|
+
return [];
|
|
714
|
+
},
|
|
715
|
+
async hybridSearch() {
|
|
716
|
+
return [];
|
|
717
|
+
},
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
export function createEdgeEntityOperations(schemaEdgeRecords, getProvider) {
|
|
721
|
+
const addEdgeMetadata = (e) => ({
|
|
722
|
+
...e,
|
|
723
|
+
$id: e['$id'] || `${e['from']}:${e['name']}`,
|
|
724
|
+
$type: 'Edge',
|
|
725
|
+
});
|
|
726
|
+
/**
|
|
727
|
+
* Get runtime edges from the provider.
|
|
728
|
+
* Returns empty array for not-found errors (Edge table may not exist yet).
|
|
729
|
+
*/
|
|
730
|
+
async function getRuntimeEdges(suppressErrors) {
|
|
731
|
+
try {
|
|
732
|
+
const provider = await getProvider();
|
|
733
|
+
return await provider.list('Edge');
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
if (isNotFoundError(error) || suppressErrors) {
|
|
737
|
+
return [];
|
|
738
|
+
}
|
|
739
|
+
throw wrapDatabaseError(error, 'list', 'Edge');
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Merge schema edges with runtime edges, preferring runtime versions.
|
|
744
|
+
*/
|
|
745
|
+
async function getAllEdges(suppressErrors) {
|
|
746
|
+
const runtimeEdges = await getRuntimeEdges(suppressErrors);
|
|
747
|
+
const runtimeEdgeKeys = new Set(runtimeEdges.map((e) => `${e['from']}:${e['name']}`));
|
|
748
|
+
const filteredSchemaEdges = schemaEdgeRecords.filter((e) => {
|
|
749
|
+
const key = `${e['from']}:${e['name']}`;
|
|
750
|
+
const hasRuntimeVersion = runtimeEdgeKeys.has(key);
|
|
751
|
+
// Exclude schema edges that have runtime versions (unless it's a fuzzy match override)
|
|
752
|
+
return !hasRuntimeVersion || (hasRuntimeVersion && e['matchMode'] !== 'fuzzy');
|
|
753
|
+
});
|
|
754
|
+
return [...filteredSchemaEdges, ...runtimeEdges];
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
async get(id) {
|
|
758
|
+
const runtimeEdges = await getRuntimeEdges();
|
|
759
|
+
const runtimeMatch = runtimeEdges.find((e) => e['$id'] === id || `${e['from']}:${e['name']}` === id);
|
|
760
|
+
if (runtimeMatch)
|
|
761
|
+
return { ...runtimeMatch, $type: 'Edge' };
|
|
762
|
+
return schemaEdgeRecords.find((e) => `${e['from']}:${e['name']}` === id) ?? null;
|
|
763
|
+
},
|
|
764
|
+
async list(options) {
|
|
765
|
+
try {
|
|
766
|
+
let results = await getAllEdges(options?.suppressErrors);
|
|
767
|
+
if (options?.where) {
|
|
768
|
+
results = applyWhereFilter(results, options.where);
|
|
769
|
+
}
|
|
770
|
+
return results.map(addEdgeMetadata);
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
773
|
+
if (options?.onError) {
|
|
774
|
+
const fallback = options.onError(error instanceof Error ? error : new Error(String(error)));
|
|
775
|
+
return (fallback ?? []);
|
|
776
|
+
}
|
|
777
|
+
throw error;
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
async find(where) {
|
|
781
|
+
return applyWhereFilter(await getAllEdges(), where).map(addEdgeMetadata);
|
|
782
|
+
},
|
|
783
|
+
async search(query) {
|
|
784
|
+
const queryLower = query.toLowerCase();
|
|
785
|
+
return (await getAllEdges())
|
|
786
|
+
.filter((e) => String(e['from']).toLowerCase().includes(queryLower) ||
|
|
787
|
+
String(e['name']).toLowerCase().includes(queryLower) ||
|
|
788
|
+
String(e['to']).toLowerCase().includes(queryLower))
|
|
789
|
+
.map(addEdgeMetadata);
|
|
790
|
+
},
|
|
791
|
+
async create() {
|
|
792
|
+
throw new Error('Cannot manually create Edge records - they are auto-generated');
|
|
793
|
+
},
|
|
794
|
+
async update() {
|
|
795
|
+
throw new Error('Cannot manually update Edge records - they are auto-generated');
|
|
796
|
+
},
|
|
797
|
+
async upsert() {
|
|
798
|
+
throw new Error('Cannot manually upsert Edge records - they are auto-generated');
|
|
799
|
+
},
|
|
800
|
+
async delete() {
|
|
801
|
+
throw new Error('Cannot manually delete Edge records - they are auto-generated');
|
|
802
|
+
},
|
|
803
|
+
async forEach(optionsOrCallback, maybeCallback) {
|
|
804
|
+
const options = typeof optionsOrCallback === 'function' ? undefined : optionsOrCallback;
|
|
805
|
+
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
|
|
806
|
+
for (const item of await this.list(options)) {
|
|
807
|
+
await callback(item);
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
async semanticSearch() {
|
|
811
|
+
return [];
|
|
812
|
+
},
|
|
813
|
+
async hybridSearch() {
|
|
814
|
+
return [];
|
|
815
|
+
},
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
//# sourceMappingURL=entity-operations.js.map
|