@utaba/deep-memory 0.6.1 → 0.8.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/dist/{StorageProvider-CBhwqavV.d.cts → StorageProvider-CRenWERy.d.ts} +11 -1
- package/dist/{StorageProvider-ynfpc5wz.d.ts → StorageProvider-DmKddrEg.d.cts} +11 -1
- package/dist/index.cjs +748 -296
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -26
- package/dist/index.d.ts +83 -26
- package/dist/index.js +746 -296
- package/dist/index.js.map +1 -1
- package/dist/{portability-B-8J4nQQ.d.cts → portability-dhIuWJbJ.d.cts} +123 -10
- package/dist/{portability-B-8J4nQQ.d.ts → portability-dhIuWJbJ.d.ts} +123 -10
- package/dist/providers/index.cjs.map +1 -1
- package/dist/providers/index.d.cts +17 -4
- package/dist/providers/index.d.ts +17 -4
- package/dist/testing/conformance.cjs +36 -0
- package/dist/testing/conformance.cjs.map +1 -1
- package/dist/testing/conformance.d.cts +2 -2
- package/dist/testing/conformance.d.ts +2 -2
- package/dist/testing/conformance.js +36 -0
- package/dist/testing/conformance.js.map +1 -1
- package/dist/{traversal-Llalk03r.d.cts → traversal-Bmv4we_g.d.cts} +1 -1
- package/dist/{traversal-BdmDLMxV.d.ts → traversal-D8SGKq0z.d.ts} +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +7 -7
- package/dist/types/index.d.ts +7 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -125,6 +125,20 @@ var RelationshipConstraintError = class extends DeepMemoryError {
|
|
|
125
125
|
this.targetType = targetType;
|
|
126
126
|
}
|
|
127
127
|
};
|
|
128
|
+
var SelfReferentialRelationshipError = class extends DeepMemoryError {
|
|
129
|
+
entityId;
|
|
130
|
+
relationshipType;
|
|
131
|
+
constructor(entityId, relationshipType) {
|
|
132
|
+
super(
|
|
133
|
+
"SELF_REFERENTIAL_RELATIONSHIP",
|
|
134
|
+
`Self-referential relationship not allowed: "${relationshipType}" from entity "${entityId}" to itself`,
|
|
135
|
+
`Relationships must connect two different entities. Check that sourceEntityId and targetEntityId refer to distinct entities.`
|
|
136
|
+
);
|
|
137
|
+
this.name = "SelfReferentialRelationshipError";
|
|
138
|
+
this.entityId = entityId;
|
|
139
|
+
this.relationshipType = relationshipType;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
128
142
|
var GovernanceDeniedError = class extends DeepMemoryError {
|
|
129
143
|
governanceMode;
|
|
130
144
|
constructor(governanceMode, reason) {
|
|
@@ -267,6 +281,270 @@ function generateRelationshipId() {
|
|
|
267
281
|
return generateId();
|
|
268
282
|
}
|
|
269
283
|
|
|
284
|
+
// src/vocabulary/similarity.ts
|
|
285
|
+
function jaroSimilarity(a, b) {
|
|
286
|
+
if (a === b) return 1;
|
|
287
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
288
|
+
const matchWindow = Math.max(Math.floor(Math.max(a.length, b.length) / 2) - 1, 0);
|
|
289
|
+
const aMatches = new Array(a.length).fill(false);
|
|
290
|
+
const bMatches = new Array(b.length).fill(false);
|
|
291
|
+
let matches = 0;
|
|
292
|
+
let transpositions = 0;
|
|
293
|
+
for (let i = 0; i < a.length; i++) {
|
|
294
|
+
const start = Math.max(0, i - matchWindow);
|
|
295
|
+
const end = Math.min(i + matchWindow + 1, b.length);
|
|
296
|
+
for (let j = start; j < end; j++) {
|
|
297
|
+
if (bMatches[j] || a[i] !== b[j]) continue;
|
|
298
|
+
aMatches[i] = true;
|
|
299
|
+
bMatches[j] = true;
|
|
300
|
+
matches++;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (matches === 0) return 0;
|
|
305
|
+
let k = 0;
|
|
306
|
+
for (let i = 0; i < a.length; i++) {
|
|
307
|
+
if (!aMatches[i]) continue;
|
|
308
|
+
while (!bMatches[k]) k++;
|
|
309
|
+
if (a[i] !== b[k]) transpositions++;
|
|
310
|
+
k++;
|
|
311
|
+
}
|
|
312
|
+
return (matches / a.length + matches / b.length + (matches - transpositions / 2) / matches) / 3;
|
|
313
|
+
}
|
|
314
|
+
function jaroWinklerSimilarity(a, b, scalingFactor = 0.1) {
|
|
315
|
+
const jaro = jaroSimilarity(a, b);
|
|
316
|
+
if (jaro === 0) return 0;
|
|
317
|
+
const prefixLength = Math.min(4, Math.min(a.length, b.length));
|
|
318
|
+
let commonPrefix = 0;
|
|
319
|
+
for (let i = 0; i < prefixLength; i++) {
|
|
320
|
+
if (a[i] === b[i]) {
|
|
321
|
+
commonPrefix++;
|
|
322
|
+
} else {
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return jaro + commonPrefix * Math.min(scalingFactor, 0.25) * (1 - jaro);
|
|
327
|
+
}
|
|
328
|
+
function normalizeTypeName(name) {
|
|
329
|
+
return name.toLowerCase().trim().replace(/[-\s.]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
330
|
+
}
|
|
331
|
+
function toScreamingSnakeCase(name) {
|
|
332
|
+
return name.trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[-\s.]+/g, "_").replace(/[^A-Za-z0-9_]/g, "").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/vocabulary/VocabularyValidator.ts
|
|
336
|
+
function ok() {
|
|
337
|
+
return { valid: true, errors: [] };
|
|
338
|
+
}
|
|
339
|
+
function fail(...errors) {
|
|
340
|
+
return { valid: false, errors };
|
|
341
|
+
}
|
|
342
|
+
function merge(...results) {
|
|
343
|
+
const errors = results.flatMap((r) => r.errors);
|
|
344
|
+
return { valid: errors.length === 0, errors };
|
|
345
|
+
}
|
|
346
|
+
function findClosestType(typeName, types) {
|
|
347
|
+
if (types.length === 0) return void 0;
|
|
348
|
+
const lower = typeName.toLowerCase();
|
|
349
|
+
const match = types.find(
|
|
350
|
+
(t) => t.type.toLowerCase().includes(lower) || lower.includes(t.type.toLowerCase())
|
|
351
|
+
);
|
|
352
|
+
return match?.type;
|
|
353
|
+
}
|
|
354
|
+
function validatePropertySchema(schema) {
|
|
355
|
+
if (schema.embeddable === true && schema.type !== "string") {
|
|
356
|
+
return fail({
|
|
357
|
+
field: `properties.${schema.name}`,
|
|
358
|
+
message: `Property "${schema.name}" cannot be embeddable \u2014 only string properties support embedding (type is "${schema.type}")`
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return ok();
|
|
362
|
+
}
|
|
363
|
+
function validatePropertyValue(name, value, schema) {
|
|
364
|
+
if (value === void 0 || value === null) {
|
|
365
|
+
if (schema.required) {
|
|
366
|
+
return fail({
|
|
367
|
+
field: `properties.${name}`,
|
|
368
|
+
message: `Required property "${name}" is missing`
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return ok();
|
|
372
|
+
}
|
|
373
|
+
switch (schema.type) {
|
|
374
|
+
case "string":
|
|
375
|
+
if (typeof value !== "string") {
|
|
376
|
+
return fail({
|
|
377
|
+
field: `properties.${name}`,
|
|
378
|
+
message: `Property "${name}" must be a string, got ${typeof value}`
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
case "number":
|
|
383
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
384
|
+
return fail({
|
|
385
|
+
field: `properties.${name}`,
|
|
386
|
+
message: `Property "${name}" must be a number, got ${typeof value}`
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
case "boolean":
|
|
391
|
+
if (typeof value !== "boolean") {
|
|
392
|
+
return fail({
|
|
393
|
+
field: `properties.${name}`,
|
|
394
|
+
message: `Property "${name}" must be a boolean, got ${typeof value}`
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
case "date":
|
|
399
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
400
|
+
return fail({
|
|
401
|
+
field: `properties.${name}`,
|
|
402
|
+
message: `Property "${name}" must be a valid ISO date string`
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
break;
|
|
406
|
+
case "enum":
|
|
407
|
+
if (typeof value !== "string") {
|
|
408
|
+
return fail({
|
|
409
|
+
field: `properties.${name}`,
|
|
410
|
+
message: `Property "${name}" must be a string (enum value), got ${typeof value}`
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (schema.enumValues && !schema.enumValues.includes(value)) {
|
|
414
|
+
return fail({
|
|
415
|
+
field: `properties.${name}`,
|
|
416
|
+
message: `Property "${name}" must be one of: ${schema.enumValues.join(", ")}`,
|
|
417
|
+
suggestion: `Valid values: ${schema.enumValues.join(", ")}`
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
return ok();
|
|
423
|
+
}
|
|
424
|
+
function validateProperties(properties, schemas) {
|
|
425
|
+
const results = [];
|
|
426
|
+
const providedProperties = properties ?? {};
|
|
427
|
+
for (const schema of schemas) {
|
|
428
|
+
const value = providedProperties[schema.name];
|
|
429
|
+
results.push(validatePropertyValue(schema.name, value, schema));
|
|
430
|
+
}
|
|
431
|
+
if (schemas.length > 0) {
|
|
432
|
+
const knownNames = new Set(schemas.map((s) => s.name));
|
|
433
|
+
for (const key of Object.keys(providedProperties)) {
|
|
434
|
+
if (!knownNames.has(key)) {
|
|
435
|
+
results.push(fail({
|
|
436
|
+
field: `properties.${key}`,
|
|
437
|
+
message: `Unknown property "${key}"`,
|
|
438
|
+
suggestion: `Known properties: ${schemas.map((s) => s.name).join(", ")}`
|
|
439
|
+
}));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return merge(...results);
|
|
444
|
+
}
|
|
445
|
+
function validateEntity(input, vocabulary) {
|
|
446
|
+
const entityTypeDef = vocabulary.entityTypes.find((et) => et.type === input.entityType);
|
|
447
|
+
if (!entityTypeDef) {
|
|
448
|
+
const closest = findClosestType(input.entityType, vocabulary.entityTypes);
|
|
449
|
+
return fail({
|
|
450
|
+
field: "entityType",
|
|
451
|
+
message: `Entity type "${input.entityType}" does not exist in the vocabulary`,
|
|
452
|
+
suggestion: closest ? `Did you mean "${closest}"?` : `Available types: ${vocabulary.entityTypes.map((et) => et.type).join(", ") || "none"}`
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if (!input.label || input.label.trim().length === 0) {
|
|
456
|
+
return fail({
|
|
457
|
+
field: "label",
|
|
458
|
+
message: "Entity label is required and cannot be empty"
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
return validateProperties(input.properties, entityTypeDef.properties);
|
|
462
|
+
}
|
|
463
|
+
function validateEntityUpdate(input, currentEntityTypeDef, vocabulary) {
|
|
464
|
+
if (input.label !== void 0 && input.label.trim().length === 0) {
|
|
465
|
+
return fail({
|
|
466
|
+
field: "label",
|
|
467
|
+
message: "Entity label cannot be empty"
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
let targetTypeDef = currentEntityTypeDef;
|
|
471
|
+
if (input.entityType !== void 0 && input.entityType !== currentEntityTypeDef.type) {
|
|
472
|
+
const newTypeDef = vocabulary.entityTypes.find((et) => et.type === input.entityType);
|
|
473
|
+
if (!newTypeDef) {
|
|
474
|
+
const closest = findClosestType(input.entityType, vocabulary.entityTypes);
|
|
475
|
+
return fail({
|
|
476
|
+
field: "entityType",
|
|
477
|
+
message: `Entity type "${input.entityType}" does not exist in the vocabulary`,
|
|
478
|
+
suggestion: closest ? `Did you mean "${closest}"?` : `Available types: ${vocabulary.entityTypes.map((et) => et.type).join(", ") || "none"}`
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
targetTypeDef = newTypeDef;
|
|
482
|
+
}
|
|
483
|
+
if (input.properties) {
|
|
484
|
+
const results = [];
|
|
485
|
+
const hasDefinedProps = targetTypeDef.properties.length > 0;
|
|
486
|
+
for (const [key, value] of Object.entries(input.properties)) {
|
|
487
|
+
if (value === null) continue;
|
|
488
|
+
const schema = targetTypeDef.properties.find((p) => p.name === key);
|
|
489
|
+
if (!schema) {
|
|
490
|
+
if (hasDefinedProps) {
|
|
491
|
+
results.push(fail({
|
|
492
|
+
field: `properties.${key}`,
|
|
493
|
+
message: `Unknown property "${key}"`,
|
|
494
|
+
suggestion: `Known properties: ${targetTypeDef.properties.map((p) => p.name).join(", ")}`
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
results.push(validatePropertyValue(key, value, schema));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return merge(...results);
|
|
502
|
+
}
|
|
503
|
+
return ok();
|
|
504
|
+
}
|
|
505
|
+
function validateRelationship(input, vocabulary, sourceEntityType, targetEntityType) {
|
|
506
|
+
const normalizedType = toScreamingSnakeCase(input.relationshipType);
|
|
507
|
+
const relTypeDef = vocabulary.relationshipTypes.find(
|
|
508
|
+
(rt) => rt.type === normalizedType
|
|
509
|
+
);
|
|
510
|
+
if (!relTypeDef) {
|
|
511
|
+
const closest = findClosestType(input.relationshipType, vocabulary.relationshipTypes);
|
|
512
|
+
return fail({
|
|
513
|
+
field: "relationshipType",
|
|
514
|
+
message: `Relationship type "${input.relationshipType}" does not exist in the vocabulary`,
|
|
515
|
+
suggestion: closest ? `Did you mean "${closest}"?` : `Available types: ${vocabulary.relationshipTypes.map((rt) => rt.type).join(", ") || "none"}`
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
const results = [];
|
|
519
|
+
if (relTypeDef.allowedSourceTypes.length > 0 && !relTypeDef.allowedSourceTypes.includes(sourceEntityType)) {
|
|
520
|
+
results.push(
|
|
521
|
+
fail({
|
|
522
|
+
field: "sourceEntityId",
|
|
523
|
+
message: `Entity type "${sourceEntityType}" is not allowed as source for relationship type "${input.relationshipType}"`,
|
|
524
|
+
suggestion: `Allowed source types: ${relTypeDef.allowedSourceTypes.join(", ")}`
|
|
525
|
+
})
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (relTypeDef.allowedTargetTypes.length > 0 && !relTypeDef.allowedTargetTypes.includes(targetEntityType)) {
|
|
529
|
+
results.push(
|
|
530
|
+
fail({
|
|
531
|
+
field: "targetEntityId",
|
|
532
|
+
message: `Entity type "${targetEntityType}" is not allowed as target for relationship type "${input.relationshipType}"`,
|
|
533
|
+
suggestion: `Allowed target types: ${relTypeDef.allowedTargetTypes.join(", ")}`
|
|
534
|
+
})
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
if (relTypeDef.properties) {
|
|
538
|
+
results.push(
|
|
539
|
+
validateProperties(input.properties, relTypeDef.properties)
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
return merge(...results);
|
|
543
|
+
}
|
|
544
|
+
function getEntityTypeDef(entityType, vocabulary) {
|
|
545
|
+
return vocabulary.entityTypes.find((et) => et.type === entityType) ?? null;
|
|
546
|
+
}
|
|
547
|
+
|
|
270
548
|
// src/entities/EntityManager.ts
|
|
271
549
|
var EntityManager = class {
|
|
272
550
|
constructor(repositoryId, vocabularyEngine, provenanceTracker, eventBus, storage, embedding) {
|
|
@@ -283,6 +561,10 @@ var EntityManager = class {
|
|
|
283
561
|
eventBus;
|
|
284
562
|
storage;
|
|
285
563
|
embedding;
|
|
564
|
+
/** Swap the embedding provider, e.g. after a repository re-embed changes the model or dimensionality */
|
|
565
|
+
setEmbeddingProvider(embedding) {
|
|
566
|
+
this.embedding = embedding;
|
|
567
|
+
}
|
|
286
568
|
/** Create one or more entities with vocabulary validation, ID generation, provenance, and events */
|
|
287
569
|
async create(inputs) {
|
|
288
570
|
const results = [];
|
|
@@ -312,7 +594,7 @@ var EntityManager = class {
|
|
|
312
594
|
}
|
|
313
595
|
);
|
|
314
596
|
const provenance = this.provenanceTracker.stampCreate();
|
|
315
|
-
const entityEmbedding = await this.generateEmbedding(input.label, input.summary);
|
|
597
|
+
const entityEmbedding = await this.generateEmbedding(input.label, input.summary, input.properties ?? {}, input.entityType);
|
|
316
598
|
const storedEntity = {
|
|
317
599
|
id,
|
|
318
600
|
slug,
|
|
@@ -358,14 +640,42 @@ var EntityManager = class {
|
|
|
358
640
|
throw new OperationCancelledError("Entity update", hookResult.reason ?? "cancelled by hook");
|
|
359
641
|
}
|
|
360
642
|
const provenance = this.provenanceTracker.stampUpdate(existing.provenance);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
updates.
|
|
365
|
-
|
|
366
|
-
|
|
643
|
+
let mergedProperties = existing.properties;
|
|
644
|
+
if (updates.properties) {
|
|
645
|
+
const merged = { ...existing.properties };
|
|
646
|
+
for (const [key, value] of Object.entries(updates.properties)) {
|
|
647
|
+
if (value === null) {
|
|
648
|
+
delete merged[key];
|
|
649
|
+
} else {
|
|
650
|
+
merged[key] = value;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
mergedProperties = merged;
|
|
654
|
+
}
|
|
655
|
+
const typeChanged = updates.entityType !== void 0 && updates.entityType !== existing.entityType;
|
|
656
|
+
const labelChanged = updates.label !== void 0 && updates.label !== existing.label;
|
|
657
|
+
let newSlug;
|
|
658
|
+
if (typeChanged || labelChanged) {
|
|
659
|
+
const nextType = updates.entityType ?? existing.entityType;
|
|
660
|
+
const nextLabel = updates.label ?? existing.label;
|
|
661
|
+
newSlug = await generateUniqueSlug(
|
|
662
|
+
nextType,
|
|
663
|
+
nextLabel,
|
|
664
|
+
async (candidateSlug) => {
|
|
665
|
+
if (candidateSlug === existing.slug) return false;
|
|
666
|
+
const other = await this.storage.getEntityBySlug(this.repositoryId, candidateSlug);
|
|
667
|
+
return other !== null;
|
|
668
|
+
}
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
const needsReembed = updates.reembed === true || updates.label !== void 0 || updates.summary !== void 0 || updates.properties !== void 0;
|
|
672
|
+
const nextSummary = updates.summary === void 0 ? existing.summary : updates.summary ?? void 0;
|
|
673
|
+
const nextEntityType = updates.entityType ?? existing.entityType;
|
|
674
|
+
const entityEmbedding = needsReembed ? await this.generateEmbedding(updates.label ?? existing.label, nextSummary, mergedProperties, nextEntityType) : void 0;
|
|
367
675
|
const updated = await this.storage.updateEntity(this.repositoryId, entityId, {
|
|
676
|
+
entityType: typeChanged ? updates.entityType : void 0,
|
|
368
677
|
label: updates.label,
|
|
678
|
+
slug: newSlug,
|
|
369
679
|
summary: updates.summary,
|
|
370
680
|
properties: updates.properties ? mergedProperties : void 0,
|
|
371
681
|
data: updates.data,
|
|
@@ -436,18 +746,33 @@ var EntityManager = class {
|
|
|
436
746
|
offset: result.offset
|
|
437
747
|
};
|
|
438
748
|
}
|
|
439
|
-
/** Delete an entity */
|
|
749
|
+
/** Delete an entity — throws EntityNotFoundError if it does not exist */
|
|
440
750
|
async delete(entityId) {
|
|
441
751
|
const existing = await this.storage.getEntity(this.repositoryId, entityId);
|
|
442
752
|
if (!existing) {
|
|
443
753
|
throw new EntityNotFoundError(entityId);
|
|
444
754
|
}
|
|
445
|
-
const hookResult = await this.eventBus.emitHook("entity:deleting", {
|
|
755
|
+
const hookResult = await this.eventBus.emitHook("entity:deleting", { ids: [entityId] });
|
|
446
756
|
if (hookResult.cancelled) {
|
|
447
757
|
throw new OperationCancelledError("Entity deletion", hookResult.reason ?? "cancelled by hook");
|
|
448
758
|
}
|
|
449
759
|
await this.storage.deleteEntity(this.repositoryId, entityId);
|
|
450
|
-
await this.eventBus.emit("entity:deleted", {
|
|
760
|
+
await this.eventBus.emit("entity:deleted", { ids: [entityId] });
|
|
761
|
+
}
|
|
762
|
+
/** Delete multiple entities in a single batch operation */
|
|
763
|
+
async deleteMany(ids) {
|
|
764
|
+
const hookResult = await this.eventBus.emitHook("entity:deleting", { ids });
|
|
765
|
+
if (hookResult.cancelled) {
|
|
766
|
+
throw new OperationCancelledError("Entity deletion", hookResult.reason ?? "cancelled by hook");
|
|
767
|
+
}
|
|
768
|
+
const { deleted, notFound } = await this.storage.deleteEntities(this.repositoryId, ids);
|
|
769
|
+
if (deleted.length > 0) {
|
|
770
|
+
await this.eventBus.emit("entity:deleted", { ids: deleted });
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
deleted,
|
|
774
|
+
failed: notFound.map((id) => ({ id, error: `Entity '${id}' not found` }))
|
|
775
|
+
};
|
|
451
776
|
}
|
|
452
777
|
/**
|
|
453
778
|
* Re-embed a specific set of entities using the current EmbeddingProvider.
|
|
@@ -460,7 +785,12 @@ var EntityManager = class {
|
|
|
460
785
|
const maxRetries = options?.maxRetries ?? 3;
|
|
461
786
|
const storedMap = await this.storage.getEntities(this.repositoryId, entityIds);
|
|
462
787
|
const entries = Array.from(storedMap.entries());
|
|
463
|
-
const
|
|
788
|
+
const vocab = await this.vocabularyEngine.getVocabulary();
|
|
789
|
+
const texts = entries.map(([, e]) => {
|
|
790
|
+
const typeDef = getEntityTypeDef(e.entityType, vocab);
|
|
791
|
+
const embeddableValues = typeDef ? typeDef.properties.filter((p) => p.embeddable === true && typeof e.properties[p.name] === "string").map((p) => e.properties[p.name]) : [];
|
|
792
|
+
return [e.label, e.summary ?? "", ...embeddableValues].filter(Boolean).join(" ");
|
|
793
|
+
});
|
|
464
794
|
let embeddings;
|
|
465
795
|
let attempt = 0;
|
|
466
796
|
while (true) {
|
|
@@ -562,10 +892,13 @@ var EntityManager = class {
|
|
|
562
892
|
dimensions: this.embedding.dimensions()
|
|
563
893
|
};
|
|
564
894
|
}
|
|
565
|
-
/** Generate an embedding vector from label + summary if a provider is available */
|
|
566
|
-
async generateEmbedding(label, summary) {
|
|
895
|
+
/** Generate an embedding vector from label + summary + embeddable string properties if a provider is available */
|
|
896
|
+
async generateEmbedding(label, summary, properties, entityType) {
|
|
567
897
|
if (!this.embedding) return void 0;
|
|
568
|
-
const
|
|
898
|
+
const vocab = await this.vocabularyEngine.getVocabulary();
|
|
899
|
+
const typeDef = getEntityTypeDef(entityType, vocab);
|
|
900
|
+
const embeddableValues = typeDef ? typeDef.properties.filter((p) => p.embeddable === true && typeof properties[p.name] === "string").map((p) => properties[p.name]) : [];
|
|
901
|
+
const text = [label, summary ?? "", ...embeddableValues].filter(Boolean).join(" ");
|
|
569
902
|
return this.embedding.embed(text);
|
|
570
903
|
}
|
|
571
904
|
};
|
|
@@ -602,57 +935,6 @@ function storedToBrief(stored) {
|
|
|
602
935
|
};
|
|
603
936
|
}
|
|
604
937
|
|
|
605
|
-
// src/vocabulary/similarity.ts
|
|
606
|
-
function jaroSimilarity(a, b) {
|
|
607
|
-
if (a === b) return 1;
|
|
608
|
-
if (a.length === 0 || b.length === 0) return 0;
|
|
609
|
-
const matchWindow = Math.max(Math.floor(Math.max(a.length, b.length) / 2) - 1, 0);
|
|
610
|
-
const aMatches = new Array(a.length).fill(false);
|
|
611
|
-
const bMatches = new Array(b.length).fill(false);
|
|
612
|
-
let matches = 0;
|
|
613
|
-
let transpositions = 0;
|
|
614
|
-
for (let i = 0; i < a.length; i++) {
|
|
615
|
-
const start = Math.max(0, i - matchWindow);
|
|
616
|
-
const end = Math.min(i + matchWindow + 1, b.length);
|
|
617
|
-
for (let j = start; j < end; j++) {
|
|
618
|
-
if (bMatches[j] || a[i] !== b[j]) continue;
|
|
619
|
-
aMatches[i] = true;
|
|
620
|
-
bMatches[j] = true;
|
|
621
|
-
matches++;
|
|
622
|
-
break;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
if (matches === 0) return 0;
|
|
626
|
-
let k = 0;
|
|
627
|
-
for (let i = 0; i < a.length; i++) {
|
|
628
|
-
if (!aMatches[i]) continue;
|
|
629
|
-
while (!bMatches[k]) k++;
|
|
630
|
-
if (a[i] !== b[k]) transpositions++;
|
|
631
|
-
k++;
|
|
632
|
-
}
|
|
633
|
-
return (matches / a.length + matches / b.length + (matches - transpositions / 2) / matches) / 3;
|
|
634
|
-
}
|
|
635
|
-
function jaroWinklerSimilarity(a, b, scalingFactor = 0.1) {
|
|
636
|
-
const jaro = jaroSimilarity(a, b);
|
|
637
|
-
if (jaro === 0) return 0;
|
|
638
|
-
const prefixLength = Math.min(4, Math.min(a.length, b.length));
|
|
639
|
-
let commonPrefix = 0;
|
|
640
|
-
for (let i = 0; i < prefixLength; i++) {
|
|
641
|
-
if (a[i] === b[i]) {
|
|
642
|
-
commonPrefix++;
|
|
643
|
-
} else {
|
|
644
|
-
break;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
return jaro + commonPrefix * Math.min(scalingFactor, 0.25) * (1 - jaro);
|
|
648
|
-
}
|
|
649
|
-
function normalizeTypeName(name) {
|
|
650
|
-
return name.toLowerCase().trim().replace(/[-\s.]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
651
|
-
}
|
|
652
|
-
function toScreamingSnakeCase(name) {
|
|
653
|
-
return name.trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[-\s.]+/g, "_").replace(/[^A-Za-z0-9_]/g, "").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
654
|
-
}
|
|
655
|
-
|
|
656
938
|
// src/relationships/RelationshipManager.ts
|
|
657
939
|
var RelationshipManager = class {
|
|
658
940
|
constructor(repositoryId, vocabularyEngine, provenanceTracker, eventBus, storage) {
|
|
@@ -671,6 +953,9 @@ var RelationshipManager = class {
|
|
|
671
953
|
async create(inputs) {
|
|
672
954
|
const results = [];
|
|
673
955
|
for (const input of inputs) {
|
|
956
|
+
if (input.sourceEntityId === input.targetEntityId) {
|
|
957
|
+
throw new SelfReferentialRelationshipError(input.sourceEntityId, input.relationshipType);
|
|
958
|
+
}
|
|
674
959
|
const sourceEntity = await this.storage.getEntity(this.repositoryId, input.sourceEntityId);
|
|
675
960
|
if (!sourceEntity) {
|
|
676
961
|
throw new EntityNotFoundError(input.sourceEntityId);
|
|
@@ -721,21 +1006,20 @@ var RelationshipManager = class {
|
|
|
721
1006
|
}
|
|
722
1007
|
return results;
|
|
723
1008
|
}
|
|
724
|
-
/** Remove a
|
|
725
|
-
async
|
|
726
|
-
const
|
|
727
|
-
if (!existing) {
|
|
728
|
-
throw new RelationshipNotFoundError(relationshipId);
|
|
729
|
-
}
|
|
730
|
-
const hookResult = await this.eventBus.emitHook("relationship:removing", { id: relationshipId });
|
|
1009
|
+
/** Remove one or more relationships in a single batch storage operation */
|
|
1010
|
+
async removeMany(ids) {
|
|
1011
|
+
const hookResult = await this.eventBus.emitHook("relationship:removing", { ids });
|
|
731
1012
|
if (hookResult.cancelled) {
|
|
732
|
-
throw new OperationCancelledError(
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1013
|
+
throw new OperationCancelledError("Relationship removal", hookResult.reason ?? "cancelled by hook");
|
|
1014
|
+
}
|
|
1015
|
+
const { deleted, notFound } = await this.storage.deleteRelationships(this.repositoryId, ids);
|
|
1016
|
+
if (deleted.length > 0) {
|
|
1017
|
+
await this.eventBus.emit("relationship:removed", { ids: deleted });
|
|
736
1018
|
}
|
|
737
|
-
|
|
738
|
-
|
|
1019
|
+
return {
|
|
1020
|
+
removed: deleted,
|
|
1021
|
+
failed: notFound.map((id) => ({ id, error: `Relationship '${id}' not found` }))
|
|
1022
|
+
};
|
|
739
1023
|
}
|
|
740
1024
|
/** Get relationships for an entity with filtering */
|
|
741
1025
|
async getForEntity(entityId, options) {
|
|
@@ -1475,6 +1759,7 @@ var SearchOrchestrator = class {
|
|
|
1475
1759
|
search;
|
|
1476
1760
|
embedding;
|
|
1477
1761
|
eventBus;
|
|
1762
|
+
vocabularyEngine;
|
|
1478
1763
|
defaultSimilarityThreshold;
|
|
1479
1764
|
conceptSearchScanLimit;
|
|
1480
1765
|
constructor(config) {
|
|
@@ -1483,9 +1768,14 @@ var SearchOrchestrator = class {
|
|
|
1483
1768
|
this.search = config.search;
|
|
1484
1769
|
this.embedding = config.embedding;
|
|
1485
1770
|
this.eventBus = config.eventBus;
|
|
1771
|
+
this.vocabularyEngine = config.vocabularyEngine;
|
|
1486
1772
|
this.defaultSimilarityThreshold = config.defaultSimilarityThreshold ?? 0.5;
|
|
1487
1773
|
this.conceptSearchScanLimit = config.conceptSearchScanLimit ?? 1e3;
|
|
1488
1774
|
}
|
|
1775
|
+
/** Swap the embedding provider, e.g. after a repository re-embed changes the model or dimensionality */
|
|
1776
|
+
setEmbeddingProvider(embedding) {
|
|
1777
|
+
this.embedding = embedding;
|
|
1778
|
+
}
|
|
1489
1779
|
/**
|
|
1490
1780
|
* Find entities by label, type, and property filters.
|
|
1491
1781
|
* When a SearchProvider is available, its results are merged with
|
|
@@ -1553,7 +1843,7 @@ var SearchOrchestrator = class {
|
|
|
1553
1843
|
if (entity.embedding && entity.embedding.length > 0) {
|
|
1554
1844
|
score = this.cosineSimilarity(queryEmbedding, entity.embedding);
|
|
1555
1845
|
} else {
|
|
1556
|
-
const entityText =
|
|
1846
|
+
const entityText = await this.buildEmbeddingText(entity.entityType, entity.label, entity.summary, entity.properties);
|
|
1557
1847
|
const entityEmbedding = await this.embedding.embed(entityText);
|
|
1558
1848
|
score = this.cosineSimilarity(queryEmbedding, entityEmbedding);
|
|
1559
1849
|
}
|
|
@@ -1612,6 +1902,22 @@ var SearchOrchestrator = class {
|
|
|
1612
1902
|
offset
|
|
1613
1903
|
};
|
|
1614
1904
|
}
|
|
1905
|
+
/** Build the embedding text for an entity — label + summary + embeddable string properties */
|
|
1906
|
+
async buildEmbeddingText(entityType, label, summary, properties) {
|
|
1907
|
+
const embeddableValues = [];
|
|
1908
|
+
if (this.vocabularyEngine) {
|
|
1909
|
+
const vocab = await this.vocabularyEngine.getVocabulary();
|
|
1910
|
+
const typeDef = getEntityTypeDef(entityType, vocab);
|
|
1911
|
+
if (typeDef) {
|
|
1912
|
+
for (const p of typeDef.properties) {
|
|
1913
|
+
if (p.embeddable === true && typeof properties[p.name] === "string") {
|
|
1914
|
+
embeddableValues.push(properties[p.name]);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
return [label, summary ?? "", ...embeddableValues].filter(Boolean).join(" ");
|
|
1920
|
+
}
|
|
1615
1921
|
/** Cosine similarity between two vectors */
|
|
1616
1922
|
cosineSimilarity(a, b) {
|
|
1617
1923
|
if (this.embedding?.similarity) {
|
|
@@ -1638,6 +1944,177 @@ var SearchOrchestrator = class {
|
|
|
1638
1944
|
}
|
|
1639
1945
|
};
|
|
1640
1946
|
|
|
1947
|
+
// src/validation/RepositoryValidator.ts
|
|
1948
|
+
var DEFAULT_TAKE = 200;
|
|
1949
|
+
var RepositoryValidator = class {
|
|
1950
|
+
constructor(repositoryId, storage, vocabularyEngine) {
|
|
1951
|
+
this.repositoryId = repositoryId;
|
|
1952
|
+
this.storage = storage;
|
|
1953
|
+
this.vocabularyEngine = vocabularyEngine;
|
|
1954
|
+
}
|
|
1955
|
+
repositoryId;
|
|
1956
|
+
storage;
|
|
1957
|
+
vocabularyEngine;
|
|
1958
|
+
async validateEntities(options) {
|
|
1959
|
+
const offset = options?.offset ?? 0;
|
|
1960
|
+
const take = options?.take ?? DEFAULT_TAKE;
|
|
1961
|
+
const delayMs = options?.delayBetweenChunksMs ?? 0;
|
|
1962
|
+
const vocabulary = await this.vocabularyEngine.getVocabulary();
|
|
1963
|
+
const issues = [];
|
|
1964
|
+
let scanned = 0;
|
|
1965
|
+
let skipped = 0;
|
|
1966
|
+
let stoppedEarly = false;
|
|
1967
|
+
for await (const chunk of this.storage.exportAll(this.repositoryId)) {
|
|
1968
|
+
if (chunk.type !== "entities") {
|
|
1969
|
+
break;
|
|
1970
|
+
}
|
|
1971
|
+
const data = chunk.data;
|
|
1972
|
+
for (const entity of data) {
|
|
1973
|
+
scanned++;
|
|
1974
|
+
const result = validateEntity(
|
|
1975
|
+
{
|
|
1976
|
+
entityType: entity.entityType,
|
|
1977
|
+
label: entity.label,
|
|
1978
|
+
properties: entity.properties
|
|
1979
|
+
},
|
|
1980
|
+
vocabulary
|
|
1981
|
+
);
|
|
1982
|
+
if (!result.valid) {
|
|
1983
|
+
if (skipped < offset) {
|
|
1984
|
+
skipped++;
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
issues.push({
|
|
1988
|
+
entityId: entity.id,
|
|
1989
|
+
slug: entity.slug,
|
|
1990
|
+
entityType: entity.entityType,
|
|
1991
|
+
label: entity.label,
|
|
1992
|
+
errors: result.errors
|
|
1993
|
+
});
|
|
1994
|
+
if (issues.length >= take) {
|
|
1995
|
+
stoppedEarly = true;
|
|
1996
|
+
break;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
if (stoppedEarly) break;
|
|
2001
|
+
if (delayMs > 0 && !chunk.isLast) {
|
|
2002
|
+
await new Promise((resolve) => {
|
|
2003
|
+
setTimeout(resolve, delayMs);
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
return {
|
|
2008
|
+
issues,
|
|
2009
|
+
scanned,
|
|
2010
|
+
nextOffset: offset + issues.length,
|
|
2011
|
+
done: !stoppedEarly
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
async validateRelationships(options) {
|
|
2015
|
+
const offset = options?.offset ?? 0;
|
|
2016
|
+
const take = options?.take ?? DEFAULT_TAKE;
|
|
2017
|
+
const delayMs = options?.delayBetweenChunksMs ?? 0;
|
|
2018
|
+
const vocabulary = await this.vocabularyEngine.getVocabulary();
|
|
2019
|
+
const entityMap = /* @__PURE__ */ new Map();
|
|
2020
|
+
const issues = [];
|
|
2021
|
+
let scanned = 0;
|
|
2022
|
+
let skipped = 0;
|
|
2023
|
+
let stoppedEarly = false;
|
|
2024
|
+
for await (const chunk of this.storage.exportAll(this.repositoryId)) {
|
|
2025
|
+
if (chunk.type === "entities") {
|
|
2026
|
+
for (const entity of chunk.data) {
|
|
2027
|
+
entityMap.set(entity.id, {
|
|
2028
|
+
entityType: entity.entityType,
|
|
2029
|
+
label: entity.label,
|
|
2030
|
+
slug: entity.slug
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
if (delayMs > 0 && !chunk.isLast) {
|
|
2034
|
+
await new Promise((resolve) => {
|
|
2035
|
+
setTimeout(resolve, delayMs);
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
continue;
|
|
2039
|
+
}
|
|
2040
|
+
const data = chunk.data;
|
|
2041
|
+
for (const rel of data) {
|
|
2042
|
+
scanned++;
|
|
2043
|
+
const src = entityMap.get(rel.sourceEntityId);
|
|
2044
|
+
const tgt = entityMap.get(rel.targetEntityId);
|
|
2045
|
+
const errors = [];
|
|
2046
|
+
if (!src) {
|
|
2047
|
+
errors.push({
|
|
2048
|
+
field: "sourceEntityId",
|
|
2049
|
+
message: `Source entity "${rel.sourceEntityId}" does not exist in this repository`
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
if (!tgt) {
|
|
2053
|
+
errors.push({
|
|
2054
|
+
field: "targetEntityId",
|
|
2055
|
+
message: `Target entity "${rel.targetEntityId}" does not exist in this repository`
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
if (rel.sourceEntityId === rel.targetEntityId) {
|
|
2059
|
+
errors.push({
|
|
2060
|
+
field: "targetEntityId",
|
|
2061
|
+
message: `Self-referential relationship: source and target are the same entity "${rel.sourceEntityId}"`,
|
|
2062
|
+
suggestion: "Remove the relationship, or repoint it to a different entity."
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
if (src && tgt) {
|
|
2066
|
+
const result = validateRelationship(
|
|
2067
|
+
{
|
|
2068
|
+
relationshipType: rel.relationshipType,
|
|
2069
|
+
sourceEntityId: rel.sourceEntityId,
|
|
2070
|
+
targetEntityId: rel.targetEntityId,
|
|
2071
|
+
properties: rel.properties
|
|
2072
|
+
},
|
|
2073
|
+
vocabulary,
|
|
2074
|
+
src.entityType,
|
|
2075
|
+
tgt.entityType
|
|
2076
|
+
);
|
|
2077
|
+
errors.push(...result.errors);
|
|
2078
|
+
}
|
|
2079
|
+
if (errors.length > 0) {
|
|
2080
|
+
if (skipped < offset) {
|
|
2081
|
+
skipped++;
|
|
2082
|
+
continue;
|
|
2083
|
+
}
|
|
2084
|
+
issues.push({
|
|
2085
|
+
relationshipId: rel.id,
|
|
2086
|
+
relationshipType: rel.relationshipType,
|
|
2087
|
+
sourceEntityId: rel.sourceEntityId,
|
|
2088
|
+
targetEntityId: rel.targetEntityId,
|
|
2089
|
+
sourceLabel: src?.label,
|
|
2090
|
+
targetLabel: tgt?.label,
|
|
2091
|
+
sourceEntityType: src?.entityType,
|
|
2092
|
+
targetEntityType: tgt?.entityType,
|
|
2093
|
+
errors
|
|
2094
|
+
});
|
|
2095
|
+
if (issues.length >= take) {
|
|
2096
|
+
stoppedEarly = true;
|
|
2097
|
+
break;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
if (stoppedEarly) break;
|
|
2102
|
+
if (delayMs > 0 && !chunk.isLast) {
|
|
2103
|
+
await new Promise((resolve) => {
|
|
2104
|
+
setTimeout(resolve, delayMs);
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return {
|
|
2109
|
+
issues,
|
|
2110
|
+
scanned,
|
|
2111
|
+
nextOffset: offset + issues.length,
|
|
2112
|
+
done: !stoppedEarly,
|
|
2113
|
+
entitiesInMap: entityMap.size
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
};
|
|
2117
|
+
|
|
1641
2118
|
// src/core/MemoryRepository.ts
|
|
1642
2119
|
var MemoryRepository = class {
|
|
1643
2120
|
repositoryId;
|
|
@@ -1650,6 +2127,8 @@ var MemoryRepository = class {
|
|
|
1650
2127
|
relationshipManager;
|
|
1651
2128
|
graphTraversal;
|
|
1652
2129
|
searchOrchestrator;
|
|
2130
|
+
embeddingFactory;
|
|
2131
|
+
embedding;
|
|
1653
2132
|
constructor(config) {
|
|
1654
2133
|
this.repositoryId = config.repositoryId;
|
|
1655
2134
|
this.storage = config.storage;
|
|
@@ -1657,6 +2136,8 @@ var MemoryRepository = class {
|
|
|
1657
2136
|
this.vocabularyEngine = config.vocabularyEngine;
|
|
1658
2137
|
this.provenanceTracker = config.provenanceTracker;
|
|
1659
2138
|
this.eventBus = config.eventBus;
|
|
2139
|
+
this.embedding = config.embedding;
|
|
2140
|
+
this.embeddingFactory = config.embeddingFactory;
|
|
1660
2141
|
this.entityManager = new EntityManager(
|
|
1661
2142
|
config.repositoryId,
|
|
1662
2143
|
config.vocabularyEngine,
|
|
@@ -1684,6 +2165,7 @@ var MemoryRepository = class {
|
|
|
1684
2165
|
search: config.search,
|
|
1685
2166
|
embedding: config.embedding,
|
|
1686
2167
|
eventBus: config.eventBus,
|
|
2168
|
+
vocabularyEngine: config.vocabularyEngine,
|
|
1687
2169
|
defaultSimilarityThreshold: config.vocabularyEngine.getGovernanceConfig().defaultSimilarityThreshold
|
|
1688
2170
|
});
|
|
1689
2171
|
}
|
|
@@ -1742,11 +2224,14 @@ var MemoryRepository = class {
|
|
|
1742
2224
|
async findEntities(query) {
|
|
1743
2225
|
return this.searchOrchestrator.findEntities(query);
|
|
1744
2226
|
}
|
|
1745
|
-
async
|
|
1746
|
-
await this.entityManager.
|
|
1747
|
-
if (this.search) {
|
|
1748
|
-
|
|
2227
|
+
async deleteEntities(ids) {
|
|
2228
|
+
const result = await this.entityManager.deleteMany(ids);
|
|
2229
|
+
if (this.search && result.deleted.length > 0) {
|
|
2230
|
+
for (const id of result.deleted) {
|
|
2231
|
+
await this.search.removeEntity(this.repositoryId, id);
|
|
2232
|
+
}
|
|
1749
2233
|
}
|
|
2234
|
+
return result;
|
|
1750
2235
|
}
|
|
1751
2236
|
// ─── Re-embedding ──────────────────────────────────────────────────
|
|
1752
2237
|
/** Re-embed specific entities using the current EmbeddingProvider */
|
|
@@ -1754,11 +2239,45 @@ var MemoryRepository = class {
|
|
|
1754
2239
|
return this.entityManager.reembedEntities(entityIds);
|
|
1755
2240
|
}
|
|
1756
2241
|
/**
|
|
1757
|
-
* Re-embed all entities in the repository
|
|
2242
|
+
* Re-embed all entities in the repository. If `model` or `dimensions` is provided,
|
|
2243
|
+
* the repository's embedding configuration is updated to the new values before
|
|
2244
|
+
* re-embedding, so all subsequent entity writes use the new model/dimensionality.
|
|
2245
|
+
*
|
|
1758
2246
|
* Processes in batches with optional rate limiting. Emits reembed:* events for progress tracking.
|
|
1759
2247
|
* Updates repository metadata with the new model ID and dimensions on completion.
|
|
1760
2248
|
*/
|
|
1761
2249
|
async reembedAll(options) {
|
|
2250
|
+
if (options?.model !== void 0 || options?.dimensions !== void 0) {
|
|
2251
|
+
if (!this.embeddingFactory) {
|
|
2252
|
+
throw new Error(
|
|
2253
|
+
"Cannot change embedding model or dimensions: DeepMemory was constructed without an embeddingFactory. Provide embeddingFactory in DeepMemoryConfig to enable per-repository embedding reconfiguration."
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
const currentModel = this.embedding?.modelId();
|
|
2257
|
+
let currentDimensions;
|
|
2258
|
+
try {
|
|
2259
|
+
currentDimensions = this.embedding?.dimensions();
|
|
2260
|
+
} catch {
|
|
2261
|
+
currentDimensions = void 0;
|
|
2262
|
+
}
|
|
2263
|
+
const nextModel = options.model ?? currentModel;
|
|
2264
|
+
const nextDimensions = options.dimensions ?? currentDimensions;
|
|
2265
|
+
if (!nextModel || nextDimensions === void 0) {
|
|
2266
|
+
throw new Error(
|
|
2267
|
+
`Cannot rebuild embedding provider: model and dimensions must both be known. Got model="${nextModel ?? "undefined"}", dimensions=${nextDimensions ?? "undefined"}.`
|
|
2268
|
+
);
|
|
2269
|
+
}
|
|
2270
|
+
const newProvider = this.embeddingFactory({ model: nextModel, dimensions: nextDimensions });
|
|
2271
|
+
this.embedding = newProvider;
|
|
2272
|
+
this.entityManager.setEmbeddingProvider(newProvider);
|
|
2273
|
+
this.searchOrchestrator.setEmbeddingProvider(newProvider);
|
|
2274
|
+
await this.storage.updateRepository(this.repositoryId, {
|
|
2275
|
+
metadata: {
|
|
2276
|
+
embeddingModelId: nextModel,
|
|
2277
|
+
embeddingDimensions: nextDimensions
|
|
2278
|
+
}
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
1762
2281
|
const stats = await this.getStats();
|
|
1763
2282
|
await this.eventBus.emit("reembed:started", {
|
|
1764
2283
|
repositoryId: this.repositoryId,
|
|
@@ -1804,8 +2323,8 @@ var MemoryRepository = class {
|
|
|
1804
2323
|
async createRelationships(inputs) {
|
|
1805
2324
|
return this.relationshipManager.create(inputs);
|
|
1806
2325
|
}
|
|
1807
|
-
async
|
|
1808
|
-
return this.relationshipManager.
|
|
2326
|
+
async removeRelationships(ids) {
|
|
2327
|
+
return this.relationshipManager.removeMany(ids);
|
|
1809
2328
|
}
|
|
1810
2329
|
async getRelationships(entityId, options) {
|
|
1811
2330
|
return this.relationshipManager.getForEntity(entityId, options);
|
|
@@ -1991,6 +2510,34 @@ var MemoryRepository = class {
|
|
|
1991
2510
|
async getStats() {
|
|
1992
2511
|
return this.storage.getRepositoryStats(this.repositoryId);
|
|
1993
2512
|
}
|
|
2513
|
+
// ─── Validation ────────────────────────────────────────────────────
|
|
2514
|
+
/**
|
|
2515
|
+
* Audit a window of entities against the current vocabulary. Returns a single
|
|
2516
|
+
* page; callers loop until `done` is true. Paging is over scanned entities,
|
|
2517
|
+
* not issues. Does not mutate anything.
|
|
2518
|
+
*/
|
|
2519
|
+
async validateEntities(options) {
|
|
2520
|
+
const validator = new RepositoryValidator(
|
|
2521
|
+
this.repositoryId,
|
|
2522
|
+
this.storage,
|
|
2523
|
+
this.vocabularyEngine
|
|
2524
|
+
);
|
|
2525
|
+
return validator.validateEntities(options);
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Audit a window of relationships against the current vocabulary. Returns a
|
|
2529
|
+
* single page; callers loop until `done` is true. The full entity set is
|
|
2530
|
+
* loaded once per call to resolve orphan and type-mismatch checks, then
|
|
2531
|
+
* offset/take is applied to the relationship stream. Does not mutate anything.
|
|
2532
|
+
*/
|
|
2533
|
+
async validateRelationships(options) {
|
|
2534
|
+
const validator = new RepositoryValidator(
|
|
2535
|
+
this.repositoryId,
|
|
2536
|
+
this.storage,
|
|
2537
|
+
this.vocabularyEngine
|
|
2538
|
+
);
|
|
2539
|
+
return validator.validateRelationships(options);
|
|
2540
|
+
}
|
|
1994
2541
|
// ─── Bulk Operations ──────────────────────────────────────────────
|
|
1995
2542
|
/** Delete all entities and relationships in this repository, preserving the repository and vocabulary */
|
|
1996
2543
|
async deleteAllContents() {
|
|
@@ -2564,195 +3111,6 @@ function generateChangeId() {
|
|
|
2564
3111
|
return `change_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2565
3112
|
}
|
|
2566
3113
|
|
|
2567
|
-
// src/vocabulary/VocabularyValidator.ts
|
|
2568
|
-
function ok() {
|
|
2569
|
-
return { valid: true, errors: [] };
|
|
2570
|
-
}
|
|
2571
|
-
function fail(...errors) {
|
|
2572
|
-
return { valid: false, errors };
|
|
2573
|
-
}
|
|
2574
|
-
function merge(...results) {
|
|
2575
|
-
const errors = results.flatMap((r) => r.errors);
|
|
2576
|
-
return { valid: errors.length === 0, errors };
|
|
2577
|
-
}
|
|
2578
|
-
function findClosestType(typeName, types) {
|
|
2579
|
-
if (types.length === 0) return void 0;
|
|
2580
|
-
const lower = typeName.toLowerCase();
|
|
2581
|
-
const match = types.find(
|
|
2582
|
-
(t) => t.type.toLowerCase().includes(lower) || lower.includes(t.type.toLowerCase())
|
|
2583
|
-
);
|
|
2584
|
-
return match?.type;
|
|
2585
|
-
}
|
|
2586
|
-
function validatePropertyValue(name, value, schema) {
|
|
2587
|
-
if (value === void 0 || value === null) {
|
|
2588
|
-
if (schema.required) {
|
|
2589
|
-
return fail({
|
|
2590
|
-
field: `properties.${name}`,
|
|
2591
|
-
message: `Required property "${name}" is missing`
|
|
2592
|
-
});
|
|
2593
|
-
}
|
|
2594
|
-
return ok();
|
|
2595
|
-
}
|
|
2596
|
-
switch (schema.type) {
|
|
2597
|
-
case "string":
|
|
2598
|
-
if (typeof value !== "string") {
|
|
2599
|
-
return fail({
|
|
2600
|
-
field: `properties.${name}`,
|
|
2601
|
-
message: `Property "${name}" must be a string, got ${typeof value}`
|
|
2602
|
-
});
|
|
2603
|
-
}
|
|
2604
|
-
break;
|
|
2605
|
-
case "number":
|
|
2606
|
-
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
2607
|
-
return fail({
|
|
2608
|
-
field: `properties.${name}`,
|
|
2609
|
-
message: `Property "${name}" must be a number, got ${typeof value}`
|
|
2610
|
-
});
|
|
2611
|
-
}
|
|
2612
|
-
break;
|
|
2613
|
-
case "boolean":
|
|
2614
|
-
if (typeof value !== "boolean") {
|
|
2615
|
-
return fail({
|
|
2616
|
-
field: `properties.${name}`,
|
|
2617
|
-
message: `Property "${name}" must be a boolean, got ${typeof value}`
|
|
2618
|
-
});
|
|
2619
|
-
}
|
|
2620
|
-
break;
|
|
2621
|
-
case "date":
|
|
2622
|
-
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
2623
|
-
return fail({
|
|
2624
|
-
field: `properties.${name}`,
|
|
2625
|
-
message: `Property "${name}" must be a valid ISO date string`
|
|
2626
|
-
});
|
|
2627
|
-
}
|
|
2628
|
-
break;
|
|
2629
|
-
case "enum":
|
|
2630
|
-
if (typeof value !== "string") {
|
|
2631
|
-
return fail({
|
|
2632
|
-
field: `properties.${name}`,
|
|
2633
|
-
message: `Property "${name}" must be a string (enum value), got ${typeof value}`
|
|
2634
|
-
});
|
|
2635
|
-
}
|
|
2636
|
-
if (schema.enumValues && !schema.enumValues.includes(value)) {
|
|
2637
|
-
return fail({
|
|
2638
|
-
field: `properties.${name}`,
|
|
2639
|
-
message: `Property "${name}" must be one of: ${schema.enumValues.join(", ")}`,
|
|
2640
|
-
suggestion: `Valid values: ${schema.enumValues.join(", ")}`
|
|
2641
|
-
});
|
|
2642
|
-
}
|
|
2643
|
-
break;
|
|
2644
|
-
}
|
|
2645
|
-
return ok();
|
|
2646
|
-
}
|
|
2647
|
-
function validateProperties(properties, schemas, context) {
|
|
2648
|
-
const results = [];
|
|
2649
|
-
const providedProperties = properties ?? {};
|
|
2650
|
-
for (const schema of schemas) {
|
|
2651
|
-
const value = providedProperties[schema.name];
|
|
2652
|
-
results.push(validatePropertyValue(schema.name, value, schema));
|
|
2653
|
-
}
|
|
2654
|
-
const knownNames = new Set(schemas.map((s) => s.name));
|
|
2655
|
-
for (const key of Object.keys(providedProperties)) {
|
|
2656
|
-
if (!knownNames.has(key)) {
|
|
2657
|
-
results.push(
|
|
2658
|
-
fail({
|
|
2659
|
-
field: `properties.${key}`,
|
|
2660
|
-
message: `Unknown property "${key}" on ${context}`,
|
|
2661
|
-
suggestion: `Known properties: ${schemas.map((s) => s.name).join(", ") || "none"}`
|
|
2662
|
-
})
|
|
2663
|
-
);
|
|
2664
|
-
}
|
|
2665
|
-
}
|
|
2666
|
-
return merge(...results);
|
|
2667
|
-
}
|
|
2668
|
-
function validateEntity(input, vocabulary) {
|
|
2669
|
-
const entityTypeDef = vocabulary.entityTypes.find((et) => et.type === input.entityType);
|
|
2670
|
-
if (!entityTypeDef) {
|
|
2671
|
-
const closest = findClosestType(input.entityType, vocabulary.entityTypes);
|
|
2672
|
-
return fail({
|
|
2673
|
-
field: "entityType",
|
|
2674
|
-
message: `Entity type "${input.entityType}" does not exist in the vocabulary`,
|
|
2675
|
-
suggestion: closest ? `Did you mean "${closest}"?` : `Available types: ${vocabulary.entityTypes.map((et) => et.type).join(", ") || "none"}`
|
|
2676
|
-
});
|
|
2677
|
-
}
|
|
2678
|
-
if (!input.label || input.label.trim().length === 0) {
|
|
2679
|
-
return fail({
|
|
2680
|
-
field: "label",
|
|
2681
|
-
message: "Entity label is required and cannot be empty"
|
|
2682
|
-
});
|
|
2683
|
-
}
|
|
2684
|
-
return validateProperties(input.properties, entityTypeDef.properties, input.entityType);
|
|
2685
|
-
}
|
|
2686
|
-
function validateEntityUpdate(input, entityTypeDef) {
|
|
2687
|
-
if (input.label !== void 0 && input.label.trim().length === 0) {
|
|
2688
|
-
return fail({
|
|
2689
|
-
field: "label",
|
|
2690
|
-
message: "Entity label cannot be empty"
|
|
2691
|
-
});
|
|
2692
|
-
}
|
|
2693
|
-
if (input.properties) {
|
|
2694
|
-
const results = [];
|
|
2695
|
-
for (const [key, value] of Object.entries(input.properties)) {
|
|
2696
|
-
const schema = entityTypeDef.properties.find((p) => p.name === key);
|
|
2697
|
-
if (!schema) {
|
|
2698
|
-
results.push(
|
|
2699
|
-
fail({
|
|
2700
|
-
field: `properties.${key}`,
|
|
2701
|
-
message: `Unknown property "${key}" on ${entityTypeDef.type}`,
|
|
2702
|
-
suggestion: `Known properties: ${entityTypeDef.properties.map((p) => p.name).join(", ") || "none"}`
|
|
2703
|
-
})
|
|
2704
|
-
);
|
|
2705
|
-
} else {
|
|
2706
|
-
results.push(validatePropertyValue(key, value, schema));
|
|
2707
|
-
}
|
|
2708
|
-
}
|
|
2709
|
-
return merge(...results);
|
|
2710
|
-
}
|
|
2711
|
-
return ok();
|
|
2712
|
-
}
|
|
2713
|
-
function validateRelationship(input, vocabulary, sourceEntityType, targetEntityType) {
|
|
2714
|
-
const normalizedType = toScreamingSnakeCase(input.relationshipType);
|
|
2715
|
-
const relTypeDef = vocabulary.relationshipTypes.find(
|
|
2716
|
-
(rt) => rt.type === normalizedType
|
|
2717
|
-
);
|
|
2718
|
-
if (!relTypeDef) {
|
|
2719
|
-
const closest = findClosestType(input.relationshipType, vocabulary.relationshipTypes);
|
|
2720
|
-
return fail({
|
|
2721
|
-
field: "relationshipType",
|
|
2722
|
-
message: `Relationship type "${input.relationshipType}" does not exist in the vocabulary`,
|
|
2723
|
-
suggestion: closest ? `Did you mean "${closest}"?` : `Available types: ${vocabulary.relationshipTypes.map((rt) => rt.type).join(", ") || "none"}`
|
|
2724
|
-
});
|
|
2725
|
-
}
|
|
2726
|
-
const results = [];
|
|
2727
|
-
if (relTypeDef.allowedSourceTypes.length > 0 && !relTypeDef.allowedSourceTypes.includes(sourceEntityType)) {
|
|
2728
|
-
results.push(
|
|
2729
|
-
fail({
|
|
2730
|
-
field: "sourceEntityId",
|
|
2731
|
-
message: `Entity type "${sourceEntityType}" is not allowed as source for relationship type "${input.relationshipType}"`,
|
|
2732
|
-
suggestion: `Allowed source types: ${relTypeDef.allowedSourceTypes.join(", ")}`
|
|
2733
|
-
})
|
|
2734
|
-
);
|
|
2735
|
-
}
|
|
2736
|
-
if (relTypeDef.allowedTargetTypes.length > 0 && !relTypeDef.allowedTargetTypes.includes(targetEntityType)) {
|
|
2737
|
-
results.push(
|
|
2738
|
-
fail({
|
|
2739
|
-
field: "targetEntityId",
|
|
2740
|
-
message: `Entity type "${targetEntityType}" is not allowed as target for relationship type "${input.relationshipType}"`,
|
|
2741
|
-
suggestion: `Allowed target types: ${relTypeDef.allowedTargetTypes.join(", ")}`
|
|
2742
|
-
})
|
|
2743
|
-
);
|
|
2744
|
-
}
|
|
2745
|
-
if (relTypeDef.properties) {
|
|
2746
|
-
results.push(
|
|
2747
|
-
validateProperties(input.properties, relTypeDef.properties, input.relationshipType)
|
|
2748
|
-
);
|
|
2749
|
-
}
|
|
2750
|
-
return merge(...results);
|
|
2751
|
-
}
|
|
2752
|
-
function getEntityTypeDef(entityType, vocabulary) {
|
|
2753
|
-
return vocabulary.entityTypes.find((et) => et.type === entityType) ?? null;
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
3114
|
// src/core/VocabularyEngine.ts
|
|
2757
3115
|
var VocabularyEngine = class {
|
|
2758
3116
|
repositoryId;
|
|
@@ -2814,7 +3172,7 @@ var VocabularyEngine = class {
|
|
|
2814
3172
|
]
|
|
2815
3173
|
};
|
|
2816
3174
|
}
|
|
2817
|
-
return validateEntityUpdate(input, typeDef);
|
|
3175
|
+
return validateEntityUpdate(input, typeDef, vocabulary);
|
|
2818
3176
|
}
|
|
2819
3177
|
/** Validate a relationship creation input against the vocabulary */
|
|
2820
3178
|
async validateRelationship(input, sourceEntityType, targetEntityType) {
|
|
@@ -2851,6 +3209,10 @@ var VocabularyEngine = class {
|
|
|
2851
3209
|
}
|
|
2852
3210
|
}
|
|
2853
3211
|
}
|
|
3212
|
+
const propertySchemaErrors = this.validateProposalPropertySchemas(proposal);
|
|
3213
|
+
if (propertySchemaErrors) {
|
|
3214
|
+
return propertySchemaErrors;
|
|
3215
|
+
}
|
|
2854
3216
|
const { result, updatedVocabulary } = processProposal(
|
|
2855
3217
|
vocabulary,
|
|
2856
3218
|
proposal,
|
|
@@ -2888,6 +3250,37 @@ var VocabularyEngine = class {
|
|
|
2888
3250
|
}
|
|
2889
3251
|
return { deletedEntities: 0, deletedRelationships: 0 };
|
|
2890
3252
|
}
|
|
3253
|
+
/** Validate all property schemas in a proposal — returns a rejected result on the first invalid schema, or undefined if all pass */
|
|
3254
|
+
validateProposalPropertySchemas(proposal) {
|
|
3255
|
+
const schemas = [];
|
|
3256
|
+
let typeName = "";
|
|
3257
|
+
if (proposal.proposalType === "entity_type" && proposal.entityType) {
|
|
3258
|
+
schemas.push(...proposal.entityType.properties ?? []);
|
|
3259
|
+
typeName = proposal.entityType.type;
|
|
3260
|
+
} else if (proposal.proposalType === "relationship_type" && proposal.relationshipType) {
|
|
3261
|
+
schemas.push(...proposal.relationshipType.properties ?? []);
|
|
3262
|
+
typeName = proposal.relationshipType.type;
|
|
3263
|
+
} else if (proposal.proposalType === "edit_entity_type" && proposal.editEntityType) {
|
|
3264
|
+
schemas.push(...proposal.editEntityType.addProperties ?? []);
|
|
3265
|
+
schemas.push(...proposal.editEntityType.updateProperties ?? []);
|
|
3266
|
+
typeName = proposal.editEntityType.type;
|
|
3267
|
+
} else if (proposal.proposalType === "edit_relationship_type" && proposal.editRelationshipType) {
|
|
3268
|
+
schemas.push(...proposal.editRelationshipType.addProperties ?? []);
|
|
3269
|
+
schemas.push(...proposal.editRelationshipType.updateProperties ?? []);
|
|
3270
|
+
typeName = proposal.editRelationshipType.type;
|
|
3271
|
+
}
|
|
3272
|
+
for (const schema of schemas) {
|
|
3273
|
+
const result = validatePropertySchema(schema);
|
|
3274
|
+
if (!result.valid) {
|
|
3275
|
+
return {
|
|
3276
|
+
status: "rejected",
|
|
3277
|
+
type: typeName,
|
|
3278
|
+
reason: result.errors.map((e) => e.message).join("; ")
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
return void 0;
|
|
3283
|
+
}
|
|
2891
3284
|
getExistingTypesForProposal(proposal, vocabulary) {
|
|
2892
3285
|
if (proposal.proposalType === "entity_type") {
|
|
2893
3286
|
return vocabulary.entityTypes.map((et) => ({
|
|
@@ -3038,7 +3431,7 @@ var EventBus = class {
|
|
|
3038
3431
|
};
|
|
3039
3432
|
|
|
3040
3433
|
// src/portability/RepositoryExporter.ts
|
|
3041
|
-
var LIBRARY_VERSION = true ? "0.
|
|
3434
|
+
var LIBRARY_VERSION = true ? "0.8.0" : "0.1.0";
|
|
3042
3435
|
var RepositoryExporter = class {
|
|
3043
3436
|
storage;
|
|
3044
3437
|
provenance;
|
|
@@ -3688,7 +4081,9 @@ function isValidUuid(value) {
|
|
|
3688
4081
|
var DeepMemory = class {
|
|
3689
4082
|
storage;
|
|
3690
4083
|
search;
|
|
3691
|
-
|
|
4084
|
+
embeddingFactory;
|
|
4085
|
+
defaultEmbeddingModel;
|
|
4086
|
+
defaultEmbeddingDimensions;
|
|
3692
4087
|
/** Reserved for future distributed locking support */
|
|
3693
4088
|
lock;
|
|
3694
4089
|
graphTraversalProvider;
|
|
@@ -3698,12 +4093,22 @@ var DeepMemory = class {
|
|
|
3698
4093
|
constructor(config) {
|
|
3699
4094
|
this.storage = config.storage;
|
|
3700
4095
|
this.search = config.search;
|
|
3701
|
-
this.
|
|
4096
|
+
this.embeddingFactory = config.embeddingFactory;
|
|
4097
|
+
this.defaultEmbeddingModel = config.defaultEmbeddingModel;
|
|
4098
|
+
this.defaultEmbeddingDimensions = config.defaultEmbeddingDimensions;
|
|
3702
4099
|
this.lock = config.lock;
|
|
3703
4100
|
this.graphTraversalProvider = config.graphTraversal;
|
|
3704
4101
|
this.provenance = new ProvenanceTracker(config.provenance);
|
|
3705
4102
|
this.globalEventBus = new EventBus(config.provenance);
|
|
3706
4103
|
}
|
|
4104
|
+
/**
|
|
4105
|
+
* Build an embedding provider for a repository given its stored model + dimensions.
|
|
4106
|
+
* Returns undefined when no factory is configured or when model/dimensions are missing.
|
|
4107
|
+
*/
|
|
4108
|
+
buildEmbeddingForRepository(model, dimensions) {
|
|
4109
|
+
if (!this.embeddingFactory || !model || dimensions === void 0) return void 0;
|
|
4110
|
+
return this.embeddingFactory({ model, dimensions });
|
|
4111
|
+
}
|
|
3707
4112
|
/** Initialize the storage provider and optional providers (call once before use) */
|
|
3708
4113
|
async ensureInitialized() {
|
|
3709
4114
|
if (!this.initialized) {
|
|
@@ -3755,16 +4160,11 @@ var DeepMemory = class {
|
|
|
3755
4160
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3756
4161
|
const governanceConfig = config.governance ?? { mode: "open" };
|
|
3757
4162
|
const metadata = { ...config.metadata };
|
|
3758
|
-
if (this.
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
try {
|
|
3764
|
-
metadata.embeddingDimensions = this.embedding.dimensions();
|
|
3765
|
-
} catch {
|
|
3766
|
-
}
|
|
3767
|
-
}
|
|
4163
|
+
if (!metadata.embeddingModelId && this.defaultEmbeddingModel) {
|
|
4164
|
+
metadata.embeddingModelId = this.defaultEmbeddingModel;
|
|
4165
|
+
}
|
|
4166
|
+
if (metadata.embeddingDimensions === void 0 && this.defaultEmbeddingDimensions !== void 0) {
|
|
4167
|
+
metadata.embeddingDimensions = this.defaultEmbeddingDimensions;
|
|
3768
4168
|
}
|
|
3769
4169
|
await this.storage.createRepository({
|
|
3770
4170
|
repositoryId,
|
|
@@ -3780,18 +4180,23 @@ var DeepMemory = class {
|
|
|
3780
4180
|
});
|
|
3781
4181
|
const vocabulary = config.vocabulary ? buildVocabulary(config.vocabulary, context.actorId) : createEmptyVocabulary(context.actorId);
|
|
3782
4182
|
await this.storage.saveVocabulary(repositoryId, vocabulary);
|
|
4183
|
+
const embedding = this.buildEmbeddingForRepository(
|
|
4184
|
+
metadata.embeddingModelId,
|
|
4185
|
+
metadata.embeddingDimensions
|
|
4186
|
+
);
|
|
3783
4187
|
const eventBus = new EventBus(context, repositoryId);
|
|
3784
4188
|
const vocabularyEngine = new VocabularyEngine({
|
|
3785
4189
|
repositoryId,
|
|
3786
4190
|
storageProvider: this.storage,
|
|
3787
4191
|
governanceConfig,
|
|
3788
|
-
embeddingProvider:
|
|
4192
|
+
embeddingProvider: embedding
|
|
3789
4193
|
});
|
|
3790
4194
|
const repo = new MemoryRepository({
|
|
3791
4195
|
repositoryId,
|
|
3792
4196
|
storage: this.storage,
|
|
3793
4197
|
search: this.search,
|
|
3794
|
-
embedding
|
|
4198
|
+
embedding,
|
|
4199
|
+
embeddingFactory: this.embeddingFactory,
|
|
3795
4200
|
graphTraversal: this.graphTraversalProvider,
|
|
3796
4201
|
vocabularyEngine,
|
|
3797
4202
|
provenanceTracker: this.provenance,
|
|
@@ -3813,17 +4218,22 @@ var DeepMemory = class {
|
|
|
3813
4218
|
}
|
|
3814
4219
|
const context = this.provenance.getContext();
|
|
3815
4220
|
const eventBus = new EventBus(context, repositoryId);
|
|
4221
|
+
const embedding = this.buildEmbeddingForRepository(
|
|
4222
|
+
storedRepo.metadata?.embeddingModelId ?? this.defaultEmbeddingModel,
|
|
4223
|
+
storedRepo.metadata?.embeddingDimensions ?? this.defaultEmbeddingDimensions
|
|
4224
|
+
);
|
|
3816
4225
|
const vocabularyEngine = new VocabularyEngine({
|
|
3817
4226
|
repositoryId,
|
|
3818
4227
|
storageProvider: this.storage,
|
|
3819
4228
|
governanceConfig: storedRepo.governanceConfig,
|
|
3820
|
-
embeddingProvider:
|
|
4229
|
+
embeddingProvider: embedding
|
|
3821
4230
|
});
|
|
3822
4231
|
const repo = new MemoryRepository({
|
|
3823
4232
|
repositoryId,
|
|
3824
4233
|
storage: this.storage,
|
|
3825
4234
|
search: this.search,
|
|
3826
|
-
embedding
|
|
4235
|
+
embedding,
|
|
4236
|
+
embeddingFactory: this.embeddingFactory,
|
|
3827
4237
|
graphTraversal: this.graphTraversalProvider,
|
|
3828
4238
|
vocabularyEngine,
|
|
3829
4239
|
provenanceTracker: this.provenance,
|
|
@@ -4545,11 +4955,12 @@ var InMemoryStorageProvider = class {
|
|
|
4545
4955
|
}
|
|
4546
4956
|
const updated = {
|
|
4547
4957
|
...existing,
|
|
4958
|
+
entityType: updates.entityType ?? existing.entityType,
|
|
4548
4959
|
label: updates.label ?? existing.label,
|
|
4549
|
-
summary: updates.summary
|
|
4960
|
+
summary: updates.summary === void 0 ? existing.summary : updates.summary ?? void 0,
|
|
4550
4961
|
properties: updates.properties ?? existing.properties,
|
|
4551
|
-
data: updates.data
|
|
4552
|
-
dataFormat: updates.dataFormat
|
|
4962
|
+
data: updates.data === void 0 ? existing.data : updates.data ?? void 0,
|
|
4963
|
+
dataFormat: updates.dataFormat === void 0 ? existing.dataFormat : updates.dataFormat ?? void 0,
|
|
4553
4964
|
provenance: updates.provenance,
|
|
4554
4965
|
embedding: updates.embedding ?? existing.embedding
|
|
4555
4966
|
};
|
|
@@ -4676,6 +5087,29 @@ var InMemoryStorageProvider = class {
|
|
|
4676
5087
|
offset
|
|
4677
5088
|
};
|
|
4678
5089
|
}
|
|
5090
|
+
async deleteEntities(repositoryId, ids) {
|
|
5091
|
+
const store = this.getStore(repositoryId);
|
|
5092
|
+
const deleted = [];
|
|
5093
|
+
const notFound = [];
|
|
5094
|
+
const deletedSet = /* @__PURE__ */ new Set();
|
|
5095
|
+
for (const id of ids) {
|
|
5096
|
+
const existing = store.entities.get(id);
|
|
5097
|
+
if (!existing) {
|
|
5098
|
+
notFound.push(id);
|
|
5099
|
+
continue;
|
|
5100
|
+
}
|
|
5101
|
+
store.slugIndex.delete(existing.slug);
|
|
5102
|
+
store.entities.delete(id);
|
|
5103
|
+
deleted.push(id);
|
|
5104
|
+
deletedSet.add(id);
|
|
5105
|
+
}
|
|
5106
|
+
for (const [relId, rel] of store.relationships) {
|
|
5107
|
+
if (deletedSet.has(rel.sourceEntityId) || deletedSet.has(rel.targetEntityId)) {
|
|
5108
|
+
store.relationships.delete(relId);
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5111
|
+
return { deleted, notFound };
|
|
5112
|
+
}
|
|
4679
5113
|
async deleteRelationship(repositoryId, relationshipId) {
|
|
4680
5114
|
const store = this.getStore(repositoryId);
|
|
4681
5115
|
if (!store.relationships.has(relationshipId)) {
|
|
@@ -4683,6 +5117,20 @@ var InMemoryStorageProvider = class {
|
|
|
4683
5117
|
}
|
|
4684
5118
|
store.relationships.delete(relationshipId);
|
|
4685
5119
|
}
|
|
5120
|
+
async deleteRelationships(repositoryId, ids) {
|
|
5121
|
+
const store = this.getStore(repositoryId);
|
|
5122
|
+
const deleted = [];
|
|
5123
|
+
const notFound = [];
|
|
5124
|
+
for (const id of ids) {
|
|
5125
|
+
if (store.relationships.has(id)) {
|
|
5126
|
+
store.relationships.delete(id);
|
|
5127
|
+
deleted.push(id);
|
|
5128
|
+
} else {
|
|
5129
|
+
notFound.push(id);
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
return { deleted, notFound };
|
|
5133
|
+
}
|
|
4686
5134
|
async deleteRelationshipsByType(repositoryId, relationshipType) {
|
|
4687
5135
|
const store = this.getStore(repositoryId);
|
|
4688
5136
|
let deletedRelationships = 0;
|
|
@@ -5093,6 +5541,8 @@ export {
|
|
|
5093
5541
|
RelationshipConstraintError,
|
|
5094
5542
|
RelationshipNotFoundError,
|
|
5095
5543
|
RepositoryNotFoundError,
|
|
5544
|
+
RepositoryValidator,
|
|
5545
|
+
SelfReferentialRelationshipError,
|
|
5096
5546
|
TraversalTimeoutError,
|
|
5097
5547
|
TraversalValidationError,
|
|
5098
5548
|
TraversalVocabularyError,
|