@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.cjs
CHANGED
|
@@ -43,6 +43,8 @@ __export(src_exports, {
|
|
|
43
43
|
RelationshipConstraintError: () => RelationshipConstraintError,
|
|
44
44
|
RelationshipNotFoundError: () => RelationshipNotFoundError,
|
|
45
45
|
RepositoryNotFoundError: () => RepositoryNotFoundError,
|
|
46
|
+
RepositoryValidator: () => RepositoryValidator,
|
|
47
|
+
SelfReferentialRelationshipError: () => SelfReferentialRelationshipError,
|
|
46
48
|
TraversalTimeoutError: () => TraversalTimeoutError,
|
|
47
49
|
TraversalValidationError: () => TraversalValidationError,
|
|
48
50
|
TraversalVocabularyError: () => TraversalVocabularyError,
|
|
@@ -181,6 +183,20 @@ var RelationshipConstraintError = class extends DeepMemoryError {
|
|
|
181
183
|
this.targetType = targetType;
|
|
182
184
|
}
|
|
183
185
|
};
|
|
186
|
+
var SelfReferentialRelationshipError = class extends DeepMemoryError {
|
|
187
|
+
entityId;
|
|
188
|
+
relationshipType;
|
|
189
|
+
constructor(entityId, relationshipType) {
|
|
190
|
+
super(
|
|
191
|
+
"SELF_REFERENTIAL_RELATIONSHIP",
|
|
192
|
+
`Self-referential relationship not allowed: "${relationshipType}" from entity "${entityId}" to itself`,
|
|
193
|
+
`Relationships must connect two different entities. Check that sourceEntityId and targetEntityId refer to distinct entities.`
|
|
194
|
+
);
|
|
195
|
+
this.name = "SelfReferentialRelationshipError";
|
|
196
|
+
this.entityId = entityId;
|
|
197
|
+
this.relationshipType = relationshipType;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
184
200
|
var GovernanceDeniedError = class extends DeepMemoryError {
|
|
185
201
|
governanceMode;
|
|
186
202
|
constructor(governanceMode, reason) {
|
|
@@ -323,6 +339,270 @@ function generateRelationshipId() {
|
|
|
323
339
|
return generateId();
|
|
324
340
|
}
|
|
325
341
|
|
|
342
|
+
// src/vocabulary/similarity.ts
|
|
343
|
+
function jaroSimilarity(a, b) {
|
|
344
|
+
if (a === b) return 1;
|
|
345
|
+
if (a.length === 0 || b.length === 0) return 0;
|
|
346
|
+
const matchWindow = Math.max(Math.floor(Math.max(a.length, b.length) / 2) - 1, 0);
|
|
347
|
+
const aMatches = new Array(a.length).fill(false);
|
|
348
|
+
const bMatches = new Array(b.length).fill(false);
|
|
349
|
+
let matches = 0;
|
|
350
|
+
let transpositions = 0;
|
|
351
|
+
for (let i = 0; i < a.length; i++) {
|
|
352
|
+
const start = Math.max(0, i - matchWindow);
|
|
353
|
+
const end = Math.min(i + matchWindow + 1, b.length);
|
|
354
|
+
for (let j = start; j < end; j++) {
|
|
355
|
+
if (bMatches[j] || a[i] !== b[j]) continue;
|
|
356
|
+
aMatches[i] = true;
|
|
357
|
+
bMatches[j] = true;
|
|
358
|
+
matches++;
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (matches === 0) return 0;
|
|
363
|
+
let k = 0;
|
|
364
|
+
for (let i = 0; i < a.length; i++) {
|
|
365
|
+
if (!aMatches[i]) continue;
|
|
366
|
+
while (!bMatches[k]) k++;
|
|
367
|
+
if (a[i] !== b[k]) transpositions++;
|
|
368
|
+
k++;
|
|
369
|
+
}
|
|
370
|
+
return (matches / a.length + matches / b.length + (matches - transpositions / 2) / matches) / 3;
|
|
371
|
+
}
|
|
372
|
+
function jaroWinklerSimilarity(a, b, scalingFactor = 0.1) {
|
|
373
|
+
const jaro = jaroSimilarity(a, b);
|
|
374
|
+
if (jaro === 0) return 0;
|
|
375
|
+
const prefixLength = Math.min(4, Math.min(a.length, b.length));
|
|
376
|
+
let commonPrefix = 0;
|
|
377
|
+
for (let i = 0; i < prefixLength; i++) {
|
|
378
|
+
if (a[i] === b[i]) {
|
|
379
|
+
commonPrefix++;
|
|
380
|
+
} else {
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return jaro + commonPrefix * Math.min(scalingFactor, 0.25) * (1 - jaro);
|
|
385
|
+
}
|
|
386
|
+
function normalizeTypeName(name) {
|
|
387
|
+
return name.toLowerCase().trim().replace(/[-\s.]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
388
|
+
}
|
|
389
|
+
function toScreamingSnakeCase(name) {
|
|
390
|
+
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();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/vocabulary/VocabularyValidator.ts
|
|
394
|
+
function ok() {
|
|
395
|
+
return { valid: true, errors: [] };
|
|
396
|
+
}
|
|
397
|
+
function fail(...errors) {
|
|
398
|
+
return { valid: false, errors };
|
|
399
|
+
}
|
|
400
|
+
function merge(...results) {
|
|
401
|
+
const errors = results.flatMap((r) => r.errors);
|
|
402
|
+
return { valid: errors.length === 0, errors };
|
|
403
|
+
}
|
|
404
|
+
function findClosestType(typeName, types) {
|
|
405
|
+
if (types.length === 0) return void 0;
|
|
406
|
+
const lower = typeName.toLowerCase();
|
|
407
|
+
const match = types.find(
|
|
408
|
+
(t) => t.type.toLowerCase().includes(lower) || lower.includes(t.type.toLowerCase())
|
|
409
|
+
);
|
|
410
|
+
return match?.type;
|
|
411
|
+
}
|
|
412
|
+
function validatePropertySchema(schema) {
|
|
413
|
+
if (schema.embeddable === true && schema.type !== "string") {
|
|
414
|
+
return fail({
|
|
415
|
+
field: `properties.${schema.name}`,
|
|
416
|
+
message: `Property "${schema.name}" cannot be embeddable \u2014 only string properties support embedding (type is "${schema.type}")`
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return ok();
|
|
420
|
+
}
|
|
421
|
+
function validatePropertyValue(name, value, schema) {
|
|
422
|
+
if (value === void 0 || value === null) {
|
|
423
|
+
if (schema.required) {
|
|
424
|
+
return fail({
|
|
425
|
+
field: `properties.${name}`,
|
|
426
|
+
message: `Required property "${name}" is missing`
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return ok();
|
|
430
|
+
}
|
|
431
|
+
switch (schema.type) {
|
|
432
|
+
case "string":
|
|
433
|
+
if (typeof value !== "string") {
|
|
434
|
+
return fail({
|
|
435
|
+
field: `properties.${name}`,
|
|
436
|
+
message: `Property "${name}" must be a string, got ${typeof value}`
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
break;
|
|
440
|
+
case "number":
|
|
441
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
442
|
+
return fail({
|
|
443
|
+
field: `properties.${name}`,
|
|
444
|
+
message: `Property "${name}" must be a number, got ${typeof value}`
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
break;
|
|
448
|
+
case "boolean":
|
|
449
|
+
if (typeof value !== "boolean") {
|
|
450
|
+
return fail({
|
|
451
|
+
field: `properties.${name}`,
|
|
452
|
+
message: `Property "${name}" must be a boolean, got ${typeof value}`
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
break;
|
|
456
|
+
case "date":
|
|
457
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
458
|
+
return fail({
|
|
459
|
+
field: `properties.${name}`,
|
|
460
|
+
message: `Property "${name}" must be a valid ISO date string`
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
break;
|
|
464
|
+
case "enum":
|
|
465
|
+
if (typeof value !== "string") {
|
|
466
|
+
return fail({
|
|
467
|
+
field: `properties.${name}`,
|
|
468
|
+
message: `Property "${name}" must be a string (enum value), got ${typeof value}`
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
if (schema.enumValues && !schema.enumValues.includes(value)) {
|
|
472
|
+
return fail({
|
|
473
|
+
field: `properties.${name}`,
|
|
474
|
+
message: `Property "${name}" must be one of: ${schema.enumValues.join(", ")}`,
|
|
475
|
+
suggestion: `Valid values: ${schema.enumValues.join(", ")}`
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
return ok();
|
|
481
|
+
}
|
|
482
|
+
function validateProperties(properties, schemas) {
|
|
483
|
+
const results = [];
|
|
484
|
+
const providedProperties = properties ?? {};
|
|
485
|
+
for (const schema of schemas) {
|
|
486
|
+
const value = providedProperties[schema.name];
|
|
487
|
+
results.push(validatePropertyValue(schema.name, value, schema));
|
|
488
|
+
}
|
|
489
|
+
if (schemas.length > 0) {
|
|
490
|
+
const knownNames = new Set(schemas.map((s) => s.name));
|
|
491
|
+
for (const key of Object.keys(providedProperties)) {
|
|
492
|
+
if (!knownNames.has(key)) {
|
|
493
|
+
results.push(fail({
|
|
494
|
+
field: `properties.${key}`,
|
|
495
|
+
message: `Unknown property "${key}"`,
|
|
496
|
+
suggestion: `Known properties: ${schemas.map((s) => s.name).join(", ")}`
|
|
497
|
+
}));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return merge(...results);
|
|
502
|
+
}
|
|
503
|
+
function validateEntity(input, vocabulary) {
|
|
504
|
+
const entityTypeDef = vocabulary.entityTypes.find((et) => et.type === input.entityType);
|
|
505
|
+
if (!entityTypeDef) {
|
|
506
|
+
const closest = findClosestType(input.entityType, vocabulary.entityTypes);
|
|
507
|
+
return fail({
|
|
508
|
+
field: "entityType",
|
|
509
|
+
message: `Entity type "${input.entityType}" does not exist in the vocabulary`,
|
|
510
|
+
suggestion: closest ? `Did you mean "${closest}"?` : `Available types: ${vocabulary.entityTypes.map((et) => et.type).join(", ") || "none"}`
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
if (!input.label || input.label.trim().length === 0) {
|
|
514
|
+
return fail({
|
|
515
|
+
field: "label",
|
|
516
|
+
message: "Entity label is required and cannot be empty"
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
return validateProperties(input.properties, entityTypeDef.properties);
|
|
520
|
+
}
|
|
521
|
+
function validateEntityUpdate(input, currentEntityTypeDef, vocabulary) {
|
|
522
|
+
if (input.label !== void 0 && input.label.trim().length === 0) {
|
|
523
|
+
return fail({
|
|
524
|
+
field: "label",
|
|
525
|
+
message: "Entity label cannot be empty"
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
let targetTypeDef = currentEntityTypeDef;
|
|
529
|
+
if (input.entityType !== void 0 && input.entityType !== currentEntityTypeDef.type) {
|
|
530
|
+
const newTypeDef = vocabulary.entityTypes.find((et) => et.type === input.entityType);
|
|
531
|
+
if (!newTypeDef) {
|
|
532
|
+
const closest = findClosestType(input.entityType, vocabulary.entityTypes);
|
|
533
|
+
return fail({
|
|
534
|
+
field: "entityType",
|
|
535
|
+
message: `Entity type "${input.entityType}" does not exist in the vocabulary`,
|
|
536
|
+
suggestion: closest ? `Did you mean "${closest}"?` : `Available types: ${vocabulary.entityTypes.map((et) => et.type).join(", ") || "none"}`
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
targetTypeDef = newTypeDef;
|
|
540
|
+
}
|
|
541
|
+
if (input.properties) {
|
|
542
|
+
const results = [];
|
|
543
|
+
const hasDefinedProps = targetTypeDef.properties.length > 0;
|
|
544
|
+
for (const [key, value] of Object.entries(input.properties)) {
|
|
545
|
+
if (value === null) continue;
|
|
546
|
+
const schema = targetTypeDef.properties.find((p) => p.name === key);
|
|
547
|
+
if (!schema) {
|
|
548
|
+
if (hasDefinedProps) {
|
|
549
|
+
results.push(fail({
|
|
550
|
+
field: `properties.${key}`,
|
|
551
|
+
message: `Unknown property "${key}"`,
|
|
552
|
+
suggestion: `Known properties: ${targetTypeDef.properties.map((p) => p.name).join(", ")}`
|
|
553
|
+
}));
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
results.push(validatePropertyValue(key, value, schema));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return merge(...results);
|
|
560
|
+
}
|
|
561
|
+
return ok();
|
|
562
|
+
}
|
|
563
|
+
function validateRelationship(input, vocabulary, sourceEntityType, targetEntityType) {
|
|
564
|
+
const normalizedType = toScreamingSnakeCase(input.relationshipType);
|
|
565
|
+
const relTypeDef = vocabulary.relationshipTypes.find(
|
|
566
|
+
(rt) => rt.type === normalizedType
|
|
567
|
+
);
|
|
568
|
+
if (!relTypeDef) {
|
|
569
|
+
const closest = findClosestType(input.relationshipType, vocabulary.relationshipTypes);
|
|
570
|
+
return fail({
|
|
571
|
+
field: "relationshipType",
|
|
572
|
+
message: `Relationship type "${input.relationshipType}" does not exist in the vocabulary`,
|
|
573
|
+
suggestion: closest ? `Did you mean "${closest}"?` : `Available types: ${vocabulary.relationshipTypes.map((rt) => rt.type).join(", ") || "none"}`
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
const results = [];
|
|
577
|
+
if (relTypeDef.allowedSourceTypes.length > 0 && !relTypeDef.allowedSourceTypes.includes(sourceEntityType)) {
|
|
578
|
+
results.push(
|
|
579
|
+
fail({
|
|
580
|
+
field: "sourceEntityId",
|
|
581
|
+
message: `Entity type "${sourceEntityType}" is not allowed as source for relationship type "${input.relationshipType}"`,
|
|
582
|
+
suggestion: `Allowed source types: ${relTypeDef.allowedSourceTypes.join(", ")}`
|
|
583
|
+
})
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
if (relTypeDef.allowedTargetTypes.length > 0 && !relTypeDef.allowedTargetTypes.includes(targetEntityType)) {
|
|
587
|
+
results.push(
|
|
588
|
+
fail({
|
|
589
|
+
field: "targetEntityId",
|
|
590
|
+
message: `Entity type "${targetEntityType}" is not allowed as target for relationship type "${input.relationshipType}"`,
|
|
591
|
+
suggestion: `Allowed target types: ${relTypeDef.allowedTargetTypes.join(", ")}`
|
|
592
|
+
})
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
if (relTypeDef.properties) {
|
|
596
|
+
results.push(
|
|
597
|
+
validateProperties(input.properties, relTypeDef.properties)
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
return merge(...results);
|
|
601
|
+
}
|
|
602
|
+
function getEntityTypeDef(entityType, vocabulary) {
|
|
603
|
+
return vocabulary.entityTypes.find((et) => et.type === entityType) ?? null;
|
|
604
|
+
}
|
|
605
|
+
|
|
326
606
|
// src/entities/EntityManager.ts
|
|
327
607
|
var EntityManager = class {
|
|
328
608
|
constructor(repositoryId, vocabularyEngine, provenanceTracker, eventBus, storage, embedding) {
|
|
@@ -339,6 +619,10 @@ var EntityManager = class {
|
|
|
339
619
|
eventBus;
|
|
340
620
|
storage;
|
|
341
621
|
embedding;
|
|
622
|
+
/** Swap the embedding provider, e.g. after a repository re-embed changes the model or dimensionality */
|
|
623
|
+
setEmbeddingProvider(embedding) {
|
|
624
|
+
this.embedding = embedding;
|
|
625
|
+
}
|
|
342
626
|
/** Create one or more entities with vocabulary validation, ID generation, provenance, and events */
|
|
343
627
|
async create(inputs) {
|
|
344
628
|
const results = [];
|
|
@@ -368,7 +652,7 @@ var EntityManager = class {
|
|
|
368
652
|
}
|
|
369
653
|
);
|
|
370
654
|
const provenance = this.provenanceTracker.stampCreate();
|
|
371
|
-
const entityEmbedding = await this.generateEmbedding(input.label, input.summary);
|
|
655
|
+
const entityEmbedding = await this.generateEmbedding(input.label, input.summary, input.properties ?? {}, input.entityType);
|
|
372
656
|
const storedEntity = {
|
|
373
657
|
id,
|
|
374
658
|
slug,
|
|
@@ -414,14 +698,42 @@ var EntityManager = class {
|
|
|
414
698
|
throw new OperationCancelledError("Entity update", hookResult.reason ?? "cancelled by hook");
|
|
415
699
|
}
|
|
416
700
|
const provenance = this.provenanceTracker.stampUpdate(existing.provenance);
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
updates.
|
|
421
|
-
|
|
422
|
-
|
|
701
|
+
let mergedProperties = existing.properties;
|
|
702
|
+
if (updates.properties) {
|
|
703
|
+
const merged = { ...existing.properties };
|
|
704
|
+
for (const [key, value] of Object.entries(updates.properties)) {
|
|
705
|
+
if (value === null) {
|
|
706
|
+
delete merged[key];
|
|
707
|
+
} else {
|
|
708
|
+
merged[key] = value;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
mergedProperties = merged;
|
|
712
|
+
}
|
|
713
|
+
const typeChanged = updates.entityType !== void 0 && updates.entityType !== existing.entityType;
|
|
714
|
+
const labelChanged = updates.label !== void 0 && updates.label !== existing.label;
|
|
715
|
+
let newSlug;
|
|
716
|
+
if (typeChanged || labelChanged) {
|
|
717
|
+
const nextType = updates.entityType ?? existing.entityType;
|
|
718
|
+
const nextLabel = updates.label ?? existing.label;
|
|
719
|
+
newSlug = await generateUniqueSlug(
|
|
720
|
+
nextType,
|
|
721
|
+
nextLabel,
|
|
722
|
+
async (candidateSlug) => {
|
|
723
|
+
if (candidateSlug === existing.slug) return false;
|
|
724
|
+
const other = await this.storage.getEntityBySlug(this.repositoryId, candidateSlug);
|
|
725
|
+
return other !== null;
|
|
726
|
+
}
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
const needsReembed = updates.reembed === true || updates.label !== void 0 || updates.summary !== void 0 || updates.properties !== void 0;
|
|
730
|
+
const nextSummary = updates.summary === void 0 ? existing.summary : updates.summary ?? void 0;
|
|
731
|
+
const nextEntityType = updates.entityType ?? existing.entityType;
|
|
732
|
+
const entityEmbedding = needsReembed ? await this.generateEmbedding(updates.label ?? existing.label, nextSummary, mergedProperties, nextEntityType) : void 0;
|
|
423
733
|
const updated = await this.storage.updateEntity(this.repositoryId, entityId, {
|
|
734
|
+
entityType: typeChanged ? updates.entityType : void 0,
|
|
424
735
|
label: updates.label,
|
|
736
|
+
slug: newSlug,
|
|
425
737
|
summary: updates.summary,
|
|
426
738
|
properties: updates.properties ? mergedProperties : void 0,
|
|
427
739
|
data: updates.data,
|
|
@@ -492,18 +804,33 @@ var EntityManager = class {
|
|
|
492
804
|
offset: result.offset
|
|
493
805
|
};
|
|
494
806
|
}
|
|
495
|
-
/** Delete an entity */
|
|
807
|
+
/** Delete an entity — throws EntityNotFoundError if it does not exist */
|
|
496
808
|
async delete(entityId) {
|
|
497
809
|
const existing = await this.storage.getEntity(this.repositoryId, entityId);
|
|
498
810
|
if (!existing) {
|
|
499
811
|
throw new EntityNotFoundError(entityId);
|
|
500
812
|
}
|
|
501
|
-
const hookResult = await this.eventBus.emitHook("entity:deleting", {
|
|
813
|
+
const hookResult = await this.eventBus.emitHook("entity:deleting", { ids: [entityId] });
|
|
502
814
|
if (hookResult.cancelled) {
|
|
503
815
|
throw new OperationCancelledError("Entity deletion", hookResult.reason ?? "cancelled by hook");
|
|
504
816
|
}
|
|
505
817
|
await this.storage.deleteEntity(this.repositoryId, entityId);
|
|
506
|
-
await this.eventBus.emit("entity:deleted", {
|
|
818
|
+
await this.eventBus.emit("entity:deleted", { ids: [entityId] });
|
|
819
|
+
}
|
|
820
|
+
/** Delete multiple entities in a single batch operation */
|
|
821
|
+
async deleteMany(ids) {
|
|
822
|
+
const hookResult = await this.eventBus.emitHook("entity:deleting", { ids });
|
|
823
|
+
if (hookResult.cancelled) {
|
|
824
|
+
throw new OperationCancelledError("Entity deletion", hookResult.reason ?? "cancelled by hook");
|
|
825
|
+
}
|
|
826
|
+
const { deleted, notFound } = await this.storage.deleteEntities(this.repositoryId, ids);
|
|
827
|
+
if (deleted.length > 0) {
|
|
828
|
+
await this.eventBus.emit("entity:deleted", { ids: deleted });
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
deleted,
|
|
832
|
+
failed: notFound.map((id) => ({ id, error: `Entity '${id}' not found` }))
|
|
833
|
+
};
|
|
507
834
|
}
|
|
508
835
|
/**
|
|
509
836
|
* Re-embed a specific set of entities using the current EmbeddingProvider.
|
|
@@ -516,7 +843,12 @@ var EntityManager = class {
|
|
|
516
843
|
const maxRetries = options?.maxRetries ?? 3;
|
|
517
844
|
const storedMap = await this.storage.getEntities(this.repositoryId, entityIds);
|
|
518
845
|
const entries = Array.from(storedMap.entries());
|
|
519
|
-
const
|
|
846
|
+
const vocab = await this.vocabularyEngine.getVocabulary();
|
|
847
|
+
const texts = entries.map(([, e]) => {
|
|
848
|
+
const typeDef = getEntityTypeDef(e.entityType, vocab);
|
|
849
|
+
const embeddableValues = typeDef ? typeDef.properties.filter((p) => p.embeddable === true && typeof e.properties[p.name] === "string").map((p) => e.properties[p.name]) : [];
|
|
850
|
+
return [e.label, e.summary ?? "", ...embeddableValues].filter(Boolean).join(" ");
|
|
851
|
+
});
|
|
520
852
|
let embeddings;
|
|
521
853
|
let attempt = 0;
|
|
522
854
|
while (true) {
|
|
@@ -618,10 +950,13 @@ var EntityManager = class {
|
|
|
618
950
|
dimensions: this.embedding.dimensions()
|
|
619
951
|
};
|
|
620
952
|
}
|
|
621
|
-
/** Generate an embedding vector from label + summary if a provider is available */
|
|
622
|
-
async generateEmbedding(label, summary) {
|
|
953
|
+
/** Generate an embedding vector from label + summary + embeddable string properties if a provider is available */
|
|
954
|
+
async generateEmbedding(label, summary, properties, entityType) {
|
|
623
955
|
if (!this.embedding) return void 0;
|
|
624
|
-
const
|
|
956
|
+
const vocab = await this.vocabularyEngine.getVocabulary();
|
|
957
|
+
const typeDef = getEntityTypeDef(entityType, vocab);
|
|
958
|
+
const embeddableValues = typeDef ? typeDef.properties.filter((p) => p.embeddable === true && typeof properties[p.name] === "string").map((p) => properties[p.name]) : [];
|
|
959
|
+
const text = [label, summary ?? "", ...embeddableValues].filter(Boolean).join(" ");
|
|
625
960
|
return this.embedding.embed(text);
|
|
626
961
|
}
|
|
627
962
|
};
|
|
@@ -658,57 +993,6 @@ function storedToBrief(stored) {
|
|
|
658
993
|
};
|
|
659
994
|
}
|
|
660
995
|
|
|
661
|
-
// src/vocabulary/similarity.ts
|
|
662
|
-
function jaroSimilarity(a, b) {
|
|
663
|
-
if (a === b) return 1;
|
|
664
|
-
if (a.length === 0 || b.length === 0) return 0;
|
|
665
|
-
const matchWindow = Math.max(Math.floor(Math.max(a.length, b.length) / 2) - 1, 0);
|
|
666
|
-
const aMatches = new Array(a.length).fill(false);
|
|
667
|
-
const bMatches = new Array(b.length).fill(false);
|
|
668
|
-
let matches = 0;
|
|
669
|
-
let transpositions = 0;
|
|
670
|
-
for (let i = 0; i < a.length; i++) {
|
|
671
|
-
const start = Math.max(0, i - matchWindow);
|
|
672
|
-
const end = Math.min(i + matchWindow + 1, b.length);
|
|
673
|
-
for (let j = start; j < end; j++) {
|
|
674
|
-
if (bMatches[j] || a[i] !== b[j]) continue;
|
|
675
|
-
aMatches[i] = true;
|
|
676
|
-
bMatches[j] = true;
|
|
677
|
-
matches++;
|
|
678
|
-
break;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
if (matches === 0) return 0;
|
|
682
|
-
let k = 0;
|
|
683
|
-
for (let i = 0; i < a.length; i++) {
|
|
684
|
-
if (!aMatches[i]) continue;
|
|
685
|
-
while (!bMatches[k]) k++;
|
|
686
|
-
if (a[i] !== b[k]) transpositions++;
|
|
687
|
-
k++;
|
|
688
|
-
}
|
|
689
|
-
return (matches / a.length + matches / b.length + (matches - transpositions / 2) / matches) / 3;
|
|
690
|
-
}
|
|
691
|
-
function jaroWinklerSimilarity(a, b, scalingFactor = 0.1) {
|
|
692
|
-
const jaro = jaroSimilarity(a, b);
|
|
693
|
-
if (jaro === 0) return 0;
|
|
694
|
-
const prefixLength = Math.min(4, Math.min(a.length, b.length));
|
|
695
|
-
let commonPrefix = 0;
|
|
696
|
-
for (let i = 0; i < prefixLength; i++) {
|
|
697
|
-
if (a[i] === b[i]) {
|
|
698
|
-
commonPrefix++;
|
|
699
|
-
} else {
|
|
700
|
-
break;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
return jaro + commonPrefix * Math.min(scalingFactor, 0.25) * (1 - jaro);
|
|
704
|
-
}
|
|
705
|
-
function normalizeTypeName(name) {
|
|
706
|
-
return name.toLowerCase().trim().replace(/[-\s.]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
707
|
-
}
|
|
708
|
-
function toScreamingSnakeCase(name) {
|
|
709
|
-
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();
|
|
710
|
-
}
|
|
711
|
-
|
|
712
996
|
// src/relationships/RelationshipManager.ts
|
|
713
997
|
var RelationshipManager = class {
|
|
714
998
|
constructor(repositoryId, vocabularyEngine, provenanceTracker, eventBus, storage) {
|
|
@@ -727,6 +1011,9 @@ var RelationshipManager = class {
|
|
|
727
1011
|
async create(inputs) {
|
|
728
1012
|
const results = [];
|
|
729
1013
|
for (const input of inputs) {
|
|
1014
|
+
if (input.sourceEntityId === input.targetEntityId) {
|
|
1015
|
+
throw new SelfReferentialRelationshipError(input.sourceEntityId, input.relationshipType);
|
|
1016
|
+
}
|
|
730
1017
|
const sourceEntity = await this.storage.getEntity(this.repositoryId, input.sourceEntityId);
|
|
731
1018
|
if (!sourceEntity) {
|
|
732
1019
|
throw new EntityNotFoundError(input.sourceEntityId);
|
|
@@ -777,21 +1064,20 @@ var RelationshipManager = class {
|
|
|
777
1064
|
}
|
|
778
1065
|
return results;
|
|
779
1066
|
}
|
|
780
|
-
/** Remove a
|
|
781
|
-
async
|
|
782
|
-
const
|
|
783
|
-
if (!existing) {
|
|
784
|
-
throw new RelationshipNotFoundError(relationshipId);
|
|
785
|
-
}
|
|
786
|
-
const hookResult = await this.eventBus.emitHook("relationship:removing", { id: relationshipId });
|
|
1067
|
+
/** Remove one or more relationships in a single batch storage operation */
|
|
1068
|
+
async removeMany(ids) {
|
|
1069
|
+
const hookResult = await this.eventBus.emitHook("relationship:removing", { ids });
|
|
787
1070
|
if (hookResult.cancelled) {
|
|
788
|
-
throw new OperationCancelledError(
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1071
|
+
throw new OperationCancelledError("Relationship removal", hookResult.reason ?? "cancelled by hook");
|
|
1072
|
+
}
|
|
1073
|
+
const { deleted, notFound } = await this.storage.deleteRelationships(this.repositoryId, ids);
|
|
1074
|
+
if (deleted.length > 0) {
|
|
1075
|
+
await this.eventBus.emit("relationship:removed", { ids: deleted });
|
|
792
1076
|
}
|
|
793
|
-
|
|
794
|
-
|
|
1077
|
+
return {
|
|
1078
|
+
removed: deleted,
|
|
1079
|
+
failed: notFound.map((id) => ({ id, error: `Relationship '${id}' not found` }))
|
|
1080
|
+
};
|
|
795
1081
|
}
|
|
796
1082
|
/** Get relationships for an entity with filtering */
|
|
797
1083
|
async getForEntity(entityId, options) {
|
|
@@ -1531,6 +1817,7 @@ var SearchOrchestrator = class {
|
|
|
1531
1817
|
search;
|
|
1532
1818
|
embedding;
|
|
1533
1819
|
eventBus;
|
|
1820
|
+
vocabularyEngine;
|
|
1534
1821
|
defaultSimilarityThreshold;
|
|
1535
1822
|
conceptSearchScanLimit;
|
|
1536
1823
|
constructor(config) {
|
|
@@ -1539,9 +1826,14 @@ var SearchOrchestrator = class {
|
|
|
1539
1826
|
this.search = config.search;
|
|
1540
1827
|
this.embedding = config.embedding;
|
|
1541
1828
|
this.eventBus = config.eventBus;
|
|
1829
|
+
this.vocabularyEngine = config.vocabularyEngine;
|
|
1542
1830
|
this.defaultSimilarityThreshold = config.defaultSimilarityThreshold ?? 0.5;
|
|
1543
1831
|
this.conceptSearchScanLimit = config.conceptSearchScanLimit ?? 1e3;
|
|
1544
1832
|
}
|
|
1833
|
+
/** Swap the embedding provider, e.g. after a repository re-embed changes the model or dimensionality */
|
|
1834
|
+
setEmbeddingProvider(embedding) {
|
|
1835
|
+
this.embedding = embedding;
|
|
1836
|
+
}
|
|
1545
1837
|
/**
|
|
1546
1838
|
* Find entities by label, type, and property filters.
|
|
1547
1839
|
* When a SearchProvider is available, its results are merged with
|
|
@@ -1609,7 +1901,7 @@ var SearchOrchestrator = class {
|
|
|
1609
1901
|
if (entity.embedding && entity.embedding.length > 0) {
|
|
1610
1902
|
score = this.cosineSimilarity(queryEmbedding, entity.embedding);
|
|
1611
1903
|
} else {
|
|
1612
|
-
const entityText =
|
|
1904
|
+
const entityText = await this.buildEmbeddingText(entity.entityType, entity.label, entity.summary, entity.properties);
|
|
1613
1905
|
const entityEmbedding = await this.embedding.embed(entityText);
|
|
1614
1906
|
score = this.cosineSimilarity(queryEmbedding, entityEmbedding);
|
|
1615
1907
|
}
|
|
@@ -1668,6 +1960,22 @@ var SearchOrchestrator = class {
|
|
|
1668
1960
|
offset
|
|
1669
1961
|
};
|
|
1670
1962
|
}
|
|
1963
|
+
/** Build the embedding text for an entity — label + summary + embeddable string properties */
|
|
1964
|
+
async buildEmbeddingText(entityType, label, summary, properties) {
|
|
1965
|
+
const embeddableValues = [];
|
|
1966
|
+
if (this.vocabularyEngine) {
|
|
1967
|
+
const vocab = await this.vocabularyEngine.getVocabulary();
|
|
1968
|
+
const typeDef = getEntityTypeDef(entityType, vocab);
|
|
1969
|
+
if (typeDef) {
|
|
1970
|
+
for (const p of typeDef.properties) {
|
|
1971
|
+
if (p.embeddable === true && typeof properties[p.name] === "string") {
|
|
1972
|
+
embeddableValues.push(properties[p.name]);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
return [label, summary ?? "", ...embeddableValues].filter(Boolean).join(" ");
|
|
1978
|
+
}
|
|
1671
1979
|
/** Cosine similarity between two vectors */
|
|
1672
1980
|
cosineSimilarity(a, b) {
|
|
1673
1981
|
if (this.embedding?.similarity) {
|
|
@@ -1694,6 +2002,177 @@ var SearchOrchestrator = class {
|
|
|
1694
2002
|
}
|
|
1695
2003
|
};
|
|
1696
2004
|
|
|
2005
|
+
// src/validation/RepositoryValidator.ts
|
|
2006
|
+
var DEFAULT_TAKE = 200;
|
|
2007
|
+
var RepositoryValidator = class {
|
|
2008
|
+
constructor(repositoryId, storage, vocabularyEngine) {
|
|
2009
|
+
this.repositoryId = repositoryId;
|
|
2010
|
+
this.storage = storage;
|
|
2011
|
+
this.vocabularyEngine = vocabularyEngine;
|
|
2012
|
+
}
|
|
2013
|
+
repositoryId;
|
|
2014
|
+
storage;
|
|
2015
|
+
vocabularyEngine;
|
|
2016
|
+
async validateEntities(options) {
|
|
2017
|
+
const offset = options?.offset ?? 0;
|
|
2018
|
+
const take = options?.take ?? DEFAULT_TAKE;
|
|
2019
|
+
const delayMs = options?.delayBetweenChunksMs ?? 0;
|
|
2020
|
+
const vocabulary = await this.vocabularyEngine.getVocabulary();
|
|
2021
|
+
const issues = [];
|
|
2022
|
+
let scanned = 0;
|
|
2023
|
+
let skipped = 0;
|
|
2024
|
+
let stoppedEarly = false;
|
|
2025
|
+
for await (const chunk of this.storage.exportAll(this.repositoryId)) {
|
|
2026
|
+
if (chunk.type !== "entities") {
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
const data = chunk.data;
|
|
2030
|
+
for (const entity of data) {
|
|
2031
|
+
scanned++;
|
|
2032
|
+
const result = validateEntity(
|
|
2033
|
+
{
|
|
2034
|
+
entityType: entity.entityType,
|
|
2035
|
+
label: entity.label,
|
|
2036
|
+
properties: entity.properties
|
|
2037
|
+
},
|
|
2038
|
+
vocabulary
|
|
2039
|
+
);
|
|
2040
|
+
if (!result.valid) {
|
|
2041
|
+
if (skipped < offset) {
|
|
2042
|
+
skipped++;
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
issues.push({
|
|
2046
|
+
entityId: entity.id,
|
|
2047
|
+
slug: entity.slug,
|
|
2048
|
+
entityType: entity.entityType,
|
|
2049
|
+
label: entity.label,
|
|
2050
|
+
errors: result.errors
|
|
2051
|
+
});
|
|
2052
|
+
if (issues.length >= take) {
|
|
2053
|
+
stoppedEarly = true;
|
|
2054
|
+
break;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
if (stoppedEarly) break;
|
|
2059
|
+
if (delayMs > 0 && !chunk.isLast) {
|
|
2060
|
+
await new Promise((resolve) => {
|
|
2061
|
+
setTimeout(resolve, delayMs);
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
return {
|
|
2066
|
+
issues,
|
|
2067
|
+
scanned,
|
|
2068
|
+
nextOffset: offset + issues.length,
|
|
2069
|
+
done: !stoppedEarly
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
async validateRelationships(options) {
|
|
2073
|
+
const offset = options?.offset ?? 0;
|
|
2074
|
+
const take = options?.take ?? DEFAULT_TAKE;
|
|
2075
|
+
const delayMs = options?.delayBetweenChunksMs ?? 0;
|
|
2076
|
+
const vocabulary = await this.vocabularyEngine.getVocabulary();
|
|
2077
|
+
const entityMap = /* @__PURE__ */ new Map();
|
|
2078
|
+
const issues = [];
|
|
2079
|
+
let scanned = 0;
|
|
2080
|
+
let skipped = 0;
|
|
2081
|
+
let stoppedEarly = false;
|
|
2082
|
+
for await (const chunk of this.storage.exportAll(this.repositoryId)) {
|
|
2083
|
+
if (chunk.type === "entities") {
|
|
2084
|
+
for (const entity of chunk.data) {
|
|
2085
|
+
entityMap.set(entity.id, {
|
|
2086
|
+
entityType: entity.entityType,
|
|
2087
|
+
label: entity.label,
|
|
2088
|
+
slug: entity.slug
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
if (delayMs > 0 && !chunk.isLast) {
|
|
2092
|
+
await new Promise((resolve) => {
|
|
2093
|
+
setTimeout(resolve, delayMs);
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
const data = chunk.data;
|
|
2099
|
+
for (const rel of data) {
|
|
2100
|
+
scanned++;
|
|
2101
|
+
const src = entityMap.get(rel.sourceEntityId);
|
|
2102
|
+
const tgt = entityMap.get(rel.targetEntityId);
|
|
2103
|
+
const errors = [];
|
|
2104
|
+
if (!src) {
|
|
2105
|
+
errors.push({
|
|
2106
|
+
field: "sourceEntityId",
|
|
2107
|
+
message: `Source entity "${rel.sourceEntityId}" does not exist in this repository`
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
if (!tgt) {
|
|
2111
|
+
errors.push({
|
|
2112
|
+
field: "targetEntityId",
|
|
2113
|
+
message: `Target entity "${rel.targetEntityId}" does not exist in this repository`
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
if (rel.sourceEntityId === rel.targetEntityId) {
|
|
2117
|
+
errors.push({
|
|
2118
|
+
field: "targetEntityId",
|
|
2119
|
+
message: `Self-referential relationship: source and target are the same entity "${rel.sourceEntityId}"`,
|
|
2120
|
+
suggestion: "Remove the relationship, or repoint it to a different entity."
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
if (src && tgt) {
|
|
2124
|
+
const result = validateRelationship(
|
|
2125
|
+
{
|
|
2126
|
+
relationshipType: rel.relationshipType,
|
|
2127
|
+
sourceEntityId: rel.sourceEntityId,
|
|
2128
|
+
targetEntityId: rel.targetEntityId,
|
|
2129
|
+
properties: rel.properties
|
|
2130
|
+
},
|
|
2131
|
+
vocabulary,
|
|
2132
|
+
src.entityType,
|
|
2133
|
+
tgt.entityType
|
|
2134
|
+
);
|
|
2135
|
+
errors.push(...result.errors);
|
|
2136
|
+
}
|
|
2137
|
+
if (errors.length > 0) {
|
|
2138
|
+
if (skipped < offset) {
|
|
2139
|
+
skipped++;
|
|
2140
|
+
continue;
|
|
2141
|
+
}
|
|
2142
|
+
issues.push({
|
|
2143
|
+
relationshipId: rel.id,
|
|
2144
|
+
relationshipType: rel.relationshipType,
|
|
2145
|
+
sourceEntityId: rel.sourceEntityId,
|
|
2146
|
+
targetEntityId: rel.targetEntityId,
|
|
2147
|
+
sourceLabel: src?.label,
|
|
2148
|
+
targetLabel: tgt?.label,
|
|
2149
|
+
sourceEntityType: src?.entityType,
|
|
2150
|
+
targetEntityType: tgt?.entityType,
|
|
2151
|
+
errors
|
|
2152
|
+
});
|
|
2153
|
+
if (issues.length >= take) {
|
|
2154
|
+
stoppedEarly = true;
|
|
2155
|
+
break;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
if (stoppedEarly) break;
|
|
2160
|
+
if (delayMs > 0 && !chunk.isLast) {
|
|
2161
|
+
await new Promise((resolve) => {
|
|
2162
|
+
setTimeout(resolve, delayMs);
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
return {
|
|
2167
|
+
issues,
|
|
2168
|
+
scanned,
|
|
2169
|
+
nextOffset: offset + issues.length,
|
|
2170
|
+
done: !stoppedEarly,
|
|
2171
|
+
entitiesInMap: entityMap.size
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
};
|
|
2175
|
+
|
|
1697
2176
|
// src/core/MemoryRepository.ts
|
|
1698
2177
|
var MemoryRepository = class {
|
|
1699
2178
|
repositoryId;
|
|
@@ -1706,6 +2185,8 @@ var MemoryRepository = class {
|
|
|
1706
2185
|
relationshipManager;
|
|
1707
2186
|
graphTraversal;
|
|
1708
2187
|
searchOrchestrator;
|
|
2188
|
+
embeddingFactory;
|
|
2189
|
+
embedding;
|
|
1709
2190
|
constructor(config) {
|
|
1710
2191
|
this.repositoryId = config.repositoryId;
|
|
1711
2192
|
this.storage = config.storage;
|
|
@@ -1713,6 +2194,8 @@ var MemoryRepository = class {
|
|
|
1713
2194
|
this.vocabularyEngine = config.vocabularyEngine;
|
|
1714
2195
|
this.provenanceTracker = config.provenanceTracker;
|
|
1715
2196
|
this.eventBus = config.eventBus;
|
|
2197
|
+
this.embedding = config.embedding;
|
|
2198
|
+
this.embeddingFactory = config.embeddingFactory;
|
|
1716
2199
|
this.entityManager = new EntityManager(
|
|
1717
2200
|
config.repositoryId,
|
|
1718
2201
|
config.vocabularyEngine,
|
|
@@ -1740,6 +2223,7 @@ var MemoryRepository = class {
|
|
|
1740
2223
|
search: config.search,
|
|
1741
2224
|
embedding: config.embedding,
|
|
1742
2225
|
eventBus: config.eventBus,
|
|
2226
|
+
vocabularyEngine: config.vocabularyEngine,
|
|
1743
2227
|
defaultSimilarityThreshold: config.vocabularyEngine.getGovernanceConfig().defaultSimilarityThreshold
|
|
1744
2228
|
});
|
|
1745
2229
|
}
|
|
@@ -1798,11 +2282,14 @@ var MemoryRepository = class {
|
|
|
1798
2282
|
async findEntities(query) {
|
|
1799
2283
|
return this.searchOrchestrator.findEntities(query);
|
|
1800
2284
|
}
|
|
1801
|
-
async
|
|
1802
|
-
await this.entityManager.
|
|
1803
|
-
if (this.search) {
|
|
1804
|
-
|
|
2285
|
+
async deleteEntities(ids) {
|
|
2286
|
+
const result = await this.entityManager.deleteMany(ids);
|
|
2287
|
+
if (this.search && result.deleted.length > 0) {
|
|
2288
|
+
for (const id of result.deleted) {
|
|
2289
|
+
await this.search.removeEntity(this.repositoryId, id);
|
|
2290
|
+
}
|
|
1805
2291
|
}
|
|
2292
|
+
return result;
|
|
1806
2293
|
}
|
|
1807
2294
|
// ─── Re-embedding ──────────────────────────────────────────────────
|
|
1808
2295
|
/** Re-embed specific entities using the current EmbeddingProvider */
|
|
@@ -1810,11 +2297,45 @@ var MemoryRepository = class {
|
|
|
1810
2297
|
return this.entityManager.reembedEntities(entityIds);
|
|
1811
2298
|
}
|
|
1812
2299
|
/**
|
|
1813
|
-
* Re-embed all entities in the repository
|
|
2300
|
+
* Re-embed all entities in the repository. If `model` or `dimensions` is provided,
|
|
2301
|
+
* the repository's embedding configuration is updated to the new values before
|
|
2302
|
+
* re-embedding, so all subsequent entity writes use the new model/dimensionality.
|
|
2303
|
+
*
|
|
1814
2304
|
* Processes in batches with optional rate limiting. Emits reembed:* events for progress tracking.
|
|
1815
2305
|
* Updates repository metadata with the new model ID and dimensions on completion.
|
|
1816
2306
|
*/
|
|
1817
2307
|
async reembedAll(options) {
|
|
2308
|
+
if (options?.model !== void 0 || options?.dimensions !== void 0) {
|
|
2309
|
+
if (!this.embeddingFactory) {
|
|
2310
|
+
throw new Error(
|
|
2311
|
+
"Cannot change embedding model or dimensions: DeepMemory was constructed without an embeddingFactory. Provide embeddingFactory in DeepMemoryConfig to enable per-repository embedding reconfiguration."
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
const currentModel = this.embedding?.modelId();
|
|
2315
|
+
let currentDimensions;
|
|
2316
|
+
try {
|
|
2317
|
+
currentDimensions = this.embedding?.dimensions();
|
|
2318
|
+
} catch {
|
|
2319
|
+
currentDimensions = void 0;
|
|
2320
|
+
}
|
|
2321
|
+
const nextModel = options.model ?? currentModel;
|
|
2322
|
+
const nextDimensions = options.dimensions ?? currentDimensions;
|
|
2323
|
+
if (!nextModel || nextDimensions === void 0) {
|
|
2324
|
+
throw new Error(
|
|
2325
|
+
`Cannot rebuild embedding provider: model and dimensions must both be known. Got model="${nextModel ?? "undefined"}", dimensions=${nextDimensions ?? "undefined"}.`
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
const newProvider = this.embeddingFactory({ model: nextModel, dimensions: nextDimensions });
|
|
2329
|
+
this.embedding = newProvider;
|
|
2330
|
+
this.entityManager.setEmbeddingProvider(newProvider);
|
|
2331
|
+
this.searchOrchestrator.setEmbeddingProvider(newProvider);
|
|
2332
|
+
await this.storage.updateRepository(this.repositoryId, {
|
|
2333
|
+
metadata: {
|
|
2334
|
+
embeddingModelId: nextModel,
|
|
2335
|
+
embeddingDimensions: nextDimensions
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
1818
2339
|
const stats = await this.getStats();
|
|
1819
2340
|
await this.eventBus.emit("reembed:started", {
|
|
1820
2341
|
repositoryId: this.repositoryId,
|
|
@@ -1860,8 +2381,8 @@ var MemoryRepository = class {
|
|
|
1860
2381
|
async createRelationships(inputs) {
|
|
1861
2382
|
return this.relationshipManager.create(inputs);
|
|
1862
2383
|
}
|
|
1863
|
-
async
|
|
1864
|
-
return this.relationshipManager.
|
|
2384
|
+
async removeRelationships(ids) {
|
|
2385
|
+
return this.relationshipManager.removeMany(ids);
|
|
1865
2386
|
}
|
|
1866
2387
|
async getRelationships(entityId, options) {
|
|
1867
2388
|
return this.relationshipManager.getForEntity(entityId, options);
|
|
@@ -2047,6 +2568,34 @@ var MemoryRepository = class {
|
|
|
2047
2568
|
async getStats() {
|
|
2048
2569
|
return this.storage.getRepositoryStats(this.repositoryId);
|
|
2049
2570
|
}
|
|
2571
|
+
// ─── Validation ────────────────────────────────────────────────────
|
|
2572
|
+
/**
|
|
2573
|
+
* Audit a window of entities against the current vocabulary. Returns a single
|
|
2574
|
+
* page; callers loop until `done` is true. Paging is over scanned entities,
|
|
2575
|
+
* not issues. Does not mutate anything.
|
|
2576
|
+
*/
|
|
2577
|
+
async validateEntities(options) {
|
|
2578
|
+
const validator = new RepositoryValidator(
|
|
2579
|
+
this.repositoryId,
|
|
2580
|
+
this.storage,
|
|
2581
|
+
this.vocabularyEngine
|
|
2582
|
+
);
|
|
2583
|
+
return validator.validateEntities(options);
|
|
2584
|
+
}
|
|
2585
|
+
/**
|
|
2586
|
+
* Audit a window of relationships against the current vocabulary. Returns a
|
|
2587
|
+
* single page; callers loop until `done` is true. The full entity set is
|
|
2588
|
+
* loaded once per call to resolve orphan and type-mismatch checks, then
|
|
2589
|
+
* offset/take is applied to the relationship stream. Does not mutate anything.
|
|
2590
|
+
*/
|
|
2591
|
+
async validateRelationships(options) {
|
|
2592
|
+
const validator = new RepositoryValidator(
|
|
2593
|
+
this.repositoryId,
|
|
2594
|
+
this.storage,
|
|
2595
|
+
this.vocabularyEngine
|
|
2596
|
+
);
|
|
2597
|
+
return validator.validateRelationships(options);
|
|
2598
|
+
}
|
|
2050
2599
|
// ─── Bulk Operations ──────────────────────────────────────────────
|
|
2051
2600
|
/** Delete all entities and relationships in this repository, preserving the repository and vocabulary */
|
|
2052
2601
|
async deleteAllContents() {
|
|
@@ -2620,195 +3169,6 @@ function generateChangeId() {
|
|
|
2620
3169
|
return `change_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2621
3170
|
}
|
|
2622
3171
|
|
|
2623
|
-
// src/vocabulary/VocabularyValidator.ts
|
|
2624
|
-
function ok() {
|
|
2625
|
-
return { valid: true, errors: [] };
|
|
2626
|
-
}
|
|
2627
|
-
function fail(...errors) {
|
|
2628
|
-
return { valid: false, errors };
|
|
2629
|
-
}
|
|
2630
|
-
function merge(...results) {
|
|
2631
|
-
const errors = results.flatMap((r) => r.errors);
|
|
2632
|
-
return { valid: errors.length === 0, errors };
|
|
2633
|
-
}
|
|
2634
|
-
function findClosestType(typeName, types) {
|
|
2635
|
-
if (types.length === 0) return void 0;
|
|
2636
|
-
const lower = typeName.toLowerCase();
|
|
2637
|
-
const match = types.find(
|
|
2638
|
-
(t) => t.type.toLowerCase().includes(lower) || lower.includes(t.type.toLowerCase())
|
|
2639
|
-
);
|
|
2640
|
-
return match?.type;
|
|
2641
|
-
}
|
|
2642
|
-
function validatePropertyValue(name, value, schema) {
|
|
2643
|
-
if (value === void 0 || value === null) {
|
|
2644
|
-
if (schema.required) {
|
|
2645
|
-
return fail({
|
|
2646
|
-
field: `properties.${name}`,
|
|
2647
|
-
message: `Required property "${name}" is missing`
|
|
2648
|
-
});
|
|
2649
|
-
}
|
|
2650
|
-
return ok();
|
|
2651
|
-
}
|
|
2652
|
-
switch (schema.type) {
|
|
2653
|
-
case "string":
|
|
2654
|
-
if (typeof value !== "string") {
|
|
2655
|
-
return fail({
|
|
2656
|
-
field: `properties.${name}`,
|
|
2657
|
-
message: `Property "${name}" must be a string, got ${typeof value}`
|
|
2658
|
-
});
|
|
2659
|
-
}
|
|
2660
|
-
break;
|
|
2661
|
-
case "number":
|
|
2662
|
-
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
2663
|
-
return fail({
|
|
2664
|
-
field: `properties.${name}`,
|
|
2665
|
-
message: `Property "${name}" must be a number, got ${typeof value}`
|
|
2666
|
-
});
|
|
2667
|
-
}
|
|
2668
|
-
break;
|
|
2669
|
-
case "boolean":
|
|
2670
|
-
if (typeof value !== "boolean") {
|
|
2671
|
-
return fail({
|
|
2672
|
-
field: `properties.${name}`,
|
|
2673
|
-
message: `Property "${name}" must be a boolean, got ${typeof value}`
|
|
2674
|
-
});
|
|
2675
|
-
}
|
|
2676
|
-
break;
|
|
2677
|
-
case "date":
|
|
2678
|
-
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
2679
|
-
return fail({
|
|
2680
|
-
field: `properties.${name}`,
|
|
2681
|
-
message: `Property "${name}" must be a valid ISO date string`
|
|
2682
|
-
});
|
|
2683
|
-
}
|
|
2684
|
-
break;
|
|
2685
|
-
case "enum":
|
|
2686
|
-
if (typeof value !== "string") {
|
|
2687
|
-
return fail({
|
|
2688
|
-
field: `properties.${name}`,
|
|
2689
|
-
message: `Property "${name}" must be a string (enum value), got ${typeof value}`
|
|
2690
|
-
});
|
|
2691
|
-
}
|
|
2692
|
-
if (schema.enumValues && !schema.enumValues.includes(value)) {
|
|
2693
|
-
return fail({
|
|
2694
|
-
field: `properties.${name}`,
|
|
2695
|
-
message: `Property "${name}" must be one of: ${schema.enumValues.join(", ")}`,
|
|
2696
|
-
suggestion: `Valid values: ${schema.enumValues.join(", ")}`
|
|
2697
|
-
});
|
|
2698
|
-
}
|
|
2699
|
-
break;
|
|
2700
|
-
}
|
|
2701
|
-
return ok();
|
|
2702
|
-
}
|
|
2703
|
-
function validateProperties(properties, schemas, context) {
|
|
2704
|
-
const results = [];
|
|
2705
|
-
const providedProperties = properties ?? {};
|
|
2706
|
-
for (const schema of schemas) {
|
|
2707
|
-
const value = providedProperties[schema.name];
|
|
2708
|
-
results.push(validatePropertyValue(schema.name, value, schema));
|
|
2709
|
-
}
|
|
2710
|
-
const knownNames = new Set(schemas.map((s) => s.name));
|
|
2711
|
-
for (const key of Object.keys(providedProperties)) {
|
|
2712
|
-
if (!knownNames.has(key)) {
|
|
2713
|
-
results.push(
|
|
2714
|
-
fail({
|
|
2715
|
-
field: `properties.${key}`,
|
|
2716
|
-
message: `Unknown property "${key}" on ${context}`,
|
|
2717
|
-
suggestion: `Known properties: ${schemas.map((s) => s.name).join(", ") || "none"}`
|
|
2718
|
-
})
|
|
2719
|
-
);
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
return merge(...results);
|
|
2723
|
-
}
|
|
2724
|
-
function validateEntity(input, vocabulary) {
|
|
2725
|
-
const entityTypeDef = vocabulary.entityTypes.find((et) => et.type === input.entityType);
|
|
2726
|
-
if (!entityTypeDef) {
|
|
2727
|
-
const closest = findClosestType(input.entityType, vocabulary.entityTypes);
|
|
2728
|
-
return fail({
|
|
2729
|
-
field: "entityType",
|
|
2730
|
-
message: `Entity type "${input.entityType}" does not exist in the vocabulary`,
|
|
2731
|
-
suggestion: closest ? `Did you mean "${closest}"?` : `Available types: ${vocabulary.entityTypes.map((et) => et.type).join(", ") || "none"}`
|
|
2732
|
-
});
|
|
2733
|
-
}
|
|
2734
|
-
if (!input.label || input.label.trim().length === 0) {
|
|
2735
|
-
return fail({
|
|
2736
|
-
field: "label",
|
|
2737
|
-
message: "Entity label is required and cannot be empty"
|
|
2738
|
-
});
|
|
2739
|
-
}
|
|
2740
|
-
return validateProperties(input.properties, entityTypeDef.properties, input.entityType);
|
|
2741
|
-
}
|
|
2742
|
-
function validateEntityUpdate(input, entityTypeDef) {
|
|
2743
|
-
if (input.label !== void 0 && input.label.trim().length === 0) {
|
|
2744
|
-
return fail({
|
|
2745
|
-
field: "label",
|
|
2746
|
-
message: "Entity label cannot be empty"
|
|
2747
|
-
});
|
|
2748
|
-
}
|
|
2749
|
-
if (input.properties) {
|
|
2750
|
-
const results = [];
|
|
2751
|
-
for (const [key, value] of Object.entries(input.properties)) {
|
|
2752
|
-
const schema = entityTypeDef.properties.find((p) => p.name === key);
|
|
2753
|
-
if (!schema) {
|
|
2754
|
-
results.push(
|
|
2755
|
-
fail({
|
|
2756
|
-
field: `properties.${key}`,
|
|
2757
|
-
message: `Unknown property "${key}" on ${entityTypeDef.type}`,
|
|
2758
|
-
suggestion: `Known properties: ${entityTypeDef.properties.map((p) => p.name).join(", ") || "none"}`
|
|
2759
|
-
})
|
|
2760
|
-
);
|
|
2761
|
-
} else {
|
|
2762
|
-
results.push(validatePropertyValue(key, value, schema));
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
return merge(...results);
|
|
2766
|
-
}
|
|
2767
|
-
return ok();
|
|
2768
|
-
}
|
|
2769
|
-
function validateRelationship(input, vocabulary, sourceEntityType, targetEntityType) {
|
|
2770
|
-
const normalizedType = toScreamingSnakeCase(input.relationshipType);
|
|
2771
|
-
const relTypeDef = vocabulary.relationshipTypes.find(
|
|
2772
|
-
(rt) => rt.type === normalizedType
|
|
2773
|
-
);
|
|
2774
|
-
if (!relTypeDef) {
|
|
2775
|
-
const closest = findClosestType(input.relationshipType, vocabulary.relationshipTypes);
|
|
2776
|
-
return fail({
|
|
2777
|
-
field: "relationshipType",
|
|
2778
|
-
message: `Relationship type "${input.relationshipType}" does not exist in the vocabulary`,
|
|
2779
|
-
suggestion: closest ? `Did you mean "${closest}"?` : `Available types: ${vocabulary.relationshipTypes.map((rt) => rt.type).join(", ") || "none"}`
|
|
2780
|
-
});
|
|
2781
|
-
}
|
|
2782
|
-
const results = [];
|
|
2783
|
-
if (relTypeDef.allowedSourceTypes.length > 0 && !relTypeDef.allowedSourceTypes.includes(sourceEntityType)) {
|
|
2784
|
-
results.push(
|
|
2785
|
-
fail({
|
|
2786
|
-
field: "sourceEntityId",
|
|
2787
|
-
message: `Entity type "${sourceEntityType}" is not allowed as source for relationship type "${input.relationshipType}"`,
|
|
2788
|
-
suggestion: `Allowed source types: ${relTypeDef.allowedSourceTypes.join(", ")}`
|
|
2789
|
-
})
|
|
2790
|
-
);
|
|
2791
|
-
}
|
|
2792
|
-
if (relTypeDef.allowedTargetTypes.length > 0 && !relTypeDef.allowedTargetTypes.includes(targetEntityType)) {
|
|
2793
|
-
results.push(
|
|
2794
|
-
fail({
|
|
2795
|
-
field: "targetEntityId",
|
|
2796
|
-
message: `Entity type "${targetEntityType}" is not allowed as target for relationship type "${input.relationshipType}"`,
|
|
2797
|
-
suggestion: `Allowed target types: ${relTypeDef.allowedTargetTypes.join(", ")}`
|
|
2798
|
-
})
|
|
2799
|
-
);
|
|
2800
|
-
}
|
|
2801
|
-
if (relTypeDef.properties) {
|
|
2802
|
-
results.push(
|
|
2803
|
-
validateProperties(input.properties, relTypeDef.properties, input.relationshipType)
|
|
2804
|
-
);
|
|
2805
|
-
}
|
|
2806
|
-
return merge(...results);
|
|
2807
|
-
}
|
|
2808
|
-
function getEntityTypeDef(entityType, vocabulary) {
|
|
2809
|
-
return vocabulary.entityTypes.find((et) => et.type === entityType) ?? null;
|
|
2810
|
-
}
|
|
2811
|
-
|
|
2812
3172
|
// src/core/VocabularyEngine.ts
|
|
2813
3173
|
var VocabularyEngine = class {
|
|
2814
3174
|
repositoryId;
|
|
@@ -2870,7 +3230,7 @@ var VocabularyEngine = class {
|
|
|
2870
3230
|
]
|
|
2871
3231
|
};
|
|
2872
3232
|
}
|
|
2873
|
-
return validateEntityUpdate(input, typeDef);
|
|
3233
|
+
return validateEntityUpdate(input, typeDef, vocabulary);
|
|
2874
3234
|
}
|
|
2875
3235
|
/** Validate a relationship creation input against the vocabulary */
|
|
2876
3236
|
async validateRelationship(input, sourceEntityType, targetEntityType) {
|
|
@@ -2907,6 +3267,10 @@ var VocabularyEngine = class {
|
|
|
2907
3267
|
}
|
|
2908
3268
|
}
|
|
2909
3269
|
}
|
|
3270
|
+
const propertySchemaErrors = this.validateProposalPropertySchemas(proposal);
|
|
3271
|
+
if (propertySchemaErrors) {
|
|
3272
|
+
return propertySchemaErrors;
|
|
3273
|
+
}
|
|
2910
3274
|
const { result, updatedVocabulary } = processProposal(
|
|
2911
3275
|
vocabulary,
|
|
2912
3276
|
proposal,
|
|
@@ -2944,6 +3308,37 @@ var VocabularyEngine = class {
|
|
|
2944
3308
|
}
|
|
2945
3309
|
return { deletedEntities: 0, deletedRelationships: 0 };
|
|
2946
3310
|
}
|
|
3311
|
+
/** Validate all property schemas in a proposal — returns a rejected result on the first invalid schema, or undefined if all pass */
|
|
3312
|
+
validateProposalPropertySchemas(proposal) {
|
|
3313
|
+
const schemas = [];
|
|
3314
|
+
let typeName = "";
|
|
3315
|
+
if (proposal.proposalType === "entity_type" && proposal.entityType) {
|
|
3316
|
+
schemas.push(...proposal.entityType.properties ?? []);
|
|
3317
|
+
typeName = proposal.entityType.type;
|
|
3318
|
+
} else if (proposal.proposalType === "relationship_type" && proposal.relationshipType) {
|
|
3319
|
+
schemas.push(...proposal.relationshipType.properties ?? []);
|
|
3320
|
+
typeName = proposal.relationshipType.type;
|
|
3321
|
+
} else if (proposal.proposalType === "edit_entity_type" && proposal.editEntityType) {
|
|
3322
|
+
schemas.push(...proposal.editEntityType.addProperties ?? []);
|
|
3323
|
+
schemas.push(...proposal.editEntityType.updateProperties ?? []);
|
|
3324
|
+
typeName = proposal.editEntityType.type;
|
|
3325
|
+
} else if (proposal.proposalType === "edit_relationship_type" && proposal.editRelationshipType) {
|
|
3326
|
+
schemas.push(...proposal.editRelationshipType.addProperties ?? []);
|
|
3327
|
+
schemas.push(...proposal.editRelationshipType.updateProperties ?? []);
|
|
3328
|
+
typeName = proposal.editRelationshipType.type;
|
|
3329
|
+
}
|
|
3330
|
+
for (const schema of schemas) {
|
|
3331
|
+
const result = validatePropertySchema(schema);
|
|
3332
|
+
if (!result.valid) {
|
|
3333
|
+
return {
|
|
3334
|
+
status: "rejected",
|
|
3335
|
+
type: typeName,
|
|
3336
|
+
reason: result.errors.map((e) => e.message).join("; ")
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
return void 0;
|
|
3341
|
+
}
|
|
2947
3342
|
getExistingTypesForProposal(proposal, vocabulary) {
|
|
2948
3343
|
if (proposal.proposalType === "entity_type") {
|
|
2949
3344
|
return vocabulary.entityTypes.map((et) => ({
|
|
@@ -3094,7 +3489,7 @@ var EventBus = class {
|
|
|
3094
3489
|
};
|
|
3095
3490
|
|
|
3096
3491
|
// src/portability/RepositoryExporter.ts
|
|
3097
|
-
var LIBRARY_VERSION = true ? "0.
|
|
3492
|
+
var LIBRARY_VERSION = true ? "0.8.0" : "0.1.0";
|
|
3098
3493
|
var RepositoryExporter = class {
|
|
3099
3494
|
storage;
|
|
3100
3495
|
provenance;
|
|
@@ -3744,7 +4139,9 @@ function isValidUuid(value) {
|
|
|
3744
4139
|
var DeepMemory = class {
|
|
3745
4140
|
storage;
|
|
3746
4141
|
search;
|
|
3747
|
-
|
|
4142
|
+
embeddingFactory;
|
|
4143
|
+
defaultEmbeddingModel;
|
|
4144
|
+
defaultEmbeddingDimensions;
|
|
3748
4145
|
/** Reserved for future distributed locking support */
|
|
3749
4146
|
lock;
|
|
3750
4147
|
graphTraversalProvider;
|
|
@@ -3754,12 +4151,22 @@ var DeepMemory = class {
|
|
|
3754
4151
|
constructor(config) {
|
|
3755
4152
|
this.storage = config.storage;
|
|
3756
4153
|
this.search = config.search;
|
|
3757
|
-
this.
|
|
4154
|
+
this.embeddingFactory = config.embeddingFactory;
|
|
4155
|
+
this.defaultEmbeddingModel = config.defaultEmbeddingModel;
|
|
4156
|
+
this.defaultEmbeddingDimensions = config.defaultEmbeddingDimensions;
|
|
3758
4157
|
this.lock = config.lock;
|
|
3759
4158
|
this.graphTraversalProvider = config.graphTraversal;
|
|
3760
4159
|
this.provenance = new ProvenanceTracker(config.provenance);
|
|
3761
4160
|
this.globalEventBus = new EventBus(config.provenance);
|
|
3762
4161
|
}
|
|
4162
|
+
/**
|
|
4163
|
+
* Build an embedding provider for a repository given its stored model + dimensions.
|
|
4164
|
+
* Returns undefined when no factory is configured or when model/dimensions are missing.
|
|
4165
|
+
*/
|
|
4166
|
+
buildEmbeddingForRepository(model, dimensions) {
|
|
4167
|
+
if (!this.embeddingFactory || !model || dimensions === void 0) return void 0;
|
|
4168
|
+
return this.embeddingFactory({ model, dimensions });
|
|
4169
|
+
}
|
|
3763
4170
|
/** Initialize the storage provider and optional providers (call once before use) */
|
|
3764
4171
|
async ensureInitialized() {
|
|
3765
4172
|
if (!this.initialized) {
|
|
@@ -3811,16 +4218,11 @@ var DeepMemory = class {
|
|
|
3811
4218
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3812
4219
|
const governanceConfig = config.governance ?? { mode: "open" };
|
|
3813
4220
|
const metadata = { ...config.metadata };
|
|
3814
|
-
if (this.
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
try {
|
|
3820
|
-
metadata.embeddingDimensions = this.embedding.dimensions();
|
|
3821
|
-
} catch {
|
|
3822
|
-
}
|
|
3823
|
-
}
|
|
4221
|
+
if (!metadata.embeddingModelId && this.defaultEmbeddingModel) {
|
|
4222
|
+
metadata.embeddingModelId = this.defaultEmbeddingModel;
|
|
4223
|
+
}
|
|
4224
|
+
if (metadata.embeddingDimensions === void 0 && this.defaultEmbeddingDimensions !== void 0) {
|
|
4225
|
+
metadata.embeddingDimensions = this.defaultEmbeddingDimensions;
|
|
3824
4226
|
}
|
|
3825
4227
|
await this.storage.createRepository({
|
|
3826
4228
|
repositoryId,
|
|
@@ -3836,18 +4238,23 @@ var DeepMemory = class {
|
|
|
3836
4238
|
});
|
|
3837
4239
|
const vocabulary = config.vocabulary ? buildVocabulary(config.vocabulary, context.actorId) : createEmptyVocabulary(context.actorId);
|
|
3838
4240
|
await this.storage.saveVocabulary(repositoryId, vocabulary);
|
|
4241
|
+
const embedding = this.buildEmbeddingForRepository(
|
|
4242
|
+
metadata.embeddingModelId,
|
|
4243
|
+
metadata.embeddingDimensions
|
|
4244
|
+
);
|
|
3839
4245
|
const eventBus = new EventBus(context, repositoryId);
|
|
3840
4246
|
const vocabularyEngine = new VocabularyEngine({
|
|
3841
4247
|
repositoryId,
|
|
3842
4248
|
storageProvider: this.storage,
|
|
3843
4249
|
governanceConfig,
|
|
3844
|
-
embeddingProvider:
|
|
4250
|
+
embeddingProvider: embedding
|
|
3845
4251
|
});
|
|
3846
4252
|
const repo = new MemoryRepository({
|
|
3847
4253
|
repositoryId,
|
|
3848
4254
|
storage: this.storage,
|
|
3849
4255
|
search: this.search,
|
|
3850
|
-
embedding
|
|
4256
|
+
embedding,
|
|
4257
|
+
embeddingFactory: this.embeddingFactory,
|
|
3851
4258
|
graphTraversal: this.graphTraversalProvider,
|
|
3852
4259
|
vocabularyEngine,
|
|
3853
4260
|
provenanceTracker: this.provenance,
|
|
@@ -3869,17 +4276,22 @@ var DeepMemory = class {
|
|
|
3869
4276
|
}
|
|
3870
4277
|
const context = this.provenance.getContext();
|
|
3871
4278
|
const eventBus = new EventBus(context, repositoryId);
|
|
4279
|
+
const embedding = this.buildEmbeddingForRepository(
|
|
4280
|
+
storedRepo.metadata?.embeddingModelId ?? this.defaultEmbeddingModel,
|
|
4281
|
+
storedRepo.metadata?.embeddingDimensions ?? this.defaultEmbeddingDimensions
|
|
4282
|
+
);
|
|
3872
4283
|
const vocabularyEngine = new VocabularyEngine({
|
|
3873
4284
|
repositoryId,
|
|
3874
4285
|
storageProvider: this.storage,
|
|
3875
4286
|
governanceConfig: storedRepo.governanceConfig,
|
|
3876
|
-
embeddingProvider:
|
|
4287
|
+
embeddingProvider: embedding
|
|
3877
4288
|
});
|
|
3878
4289
|
const repo = new MemoryRepository({
|
|
3879
4290
|
repositoryId,
|
|
3880
4291
|
storage: this.storage,
|
|
3881
4292
|
search: this.search,
|
|
3882
|
-
embedding
|
|
4293
|
+
embedding,
|
|
4294
|
+
embeddingFactory: this.embeddingFactory,
|
|
3883
4295
|
graphTraversal: this.graphTraversalProvider,
|
|
3884
4296
|
vocabularyEngine,
|
|
3885
4297
|
provenanceTracker: this.provenance,
|
|
@@ -4601,11 +5013,12 @@ var InMemoryStorageProvider = class {
|
|
|
4601
5013
|
}
|
|
4602
5014
|
const updated = {
|
|
4603
5015
|
...existing,
|
|
5016
|
+
entityType: updates.entityType ?? existing.entityType,
|
|
4604
5017
|
label: updates.label ?? existing.label,
|
|
4605
|
-
summary: updates.summary
|
|
5018
|
+
summary: updates.summary === void 0 ? existing.summary : updates.summary ?? void 0,
|
|
4606
5019
|
properties: updates.properties ?? existing.properties,
|
|
4607
|
-
data: updates.data
|
|
4608
|
-
dataFormat: updates.dataFormat
|
|
5020
|
+
data: updates.data === void 0 ? existing.data : updates.data ?? void 0,
|
|
5021
|
+
dataFormat: updates.dataFormat === void 0 ? existing.dataFormat : updates.dataFormat ?? void 0,
|
|
4609
5022
|
provenance: updates.provenance,
|
|
4610
5023
|
embedding: updates.embedding ?? existing.embedding
|
|
4611
5024
|
};
|
|
@@ -4732,6 +5145,29 @@ var InMemoryStorageProvider = class {
|
|
|
4732
5145
|
offset
|
|
4733
5146
|
};
|
|
4734
5147
|
}
|
|
5148
|
+
async deleteEntities(repositoryId, ids) {
|
|
5149
|
+
const store = this.getStore(repositoryId);
|
|
5150
|
+
const deleted = [];
|
|
5151
|
+
const notFound = [];
|
|
5152
|
+
const deletedSet = /* @__PURE__ */ new Set();
|
|
5153
|
+
for (const id of ids) {
|
|
5154
|
+
const existing = store.entities.get(id);
|
|
5155
|
+
if (!existing) {
|
|
5156
|
+
notFound.push(id);
|
|
5157
|
+
continue;
|
|
5158
|
+
}
|
|
5159
|
+
store.slugIndex.delete(existing.slug);
|
|
5160
|
+
store.entities.delete(id);
|
|
5161
|
+
deleted.push(id);
|
|
5162
|
+
deletedSet.add(id);
|
|
5163
|
+
}
|
|
5164
|
+
for (const [relId, rel] of store.relationships) {
|
|
5165
|
+
if (deletedSet.has(rel.sourceEntityId) || deletedSet.has(rel.targetEntityId)) {
|
|
5166
|
+
store.relationships.delete(relId);
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
return { deleted, notFound };
|
|
5170
|
+
}
|
|
4735
5171
|
async deleteRelationship(repositoryId, relationshipId) {
|
|
4736
5172
|
const store = this.getStore(repositoryId);
|
|
4737
5173
|
if (!store.relationships.has(relationshipId)) {
|
|
@@ -4739,6 +5175,20 @@ var InMemoryStorageProvider = class {
|
|
|
4739
5175
|
}
|
|
4740
5176
|
store.relationships.delete(relationshipId);
|
|
4741
5177
|
}
|
|
5178
|
+
async deleteRelationships(repositoryId, ids) {
|
|
5179
|
+
const store = this.getStore(repositoryId);
|
|
5180
|
+
const deleted = [];
|
|
5181
|
+
const notFound = [];
|
|
5182
|
+
for (const id of ids) {
|
|
5183
|
+
if (store.relationships.has(id)) {
|
|
5184
|
+
store.relationships.delete(id);
|
|
5185
|
+
deleted.push(id);
|
|
5186
|
+
} else {
|
|
5187
|
+
notFound.push(id);
|
|
5188
|
+
}
|
|
5189
|
+
}
|
|
5190
|
+
return { deleted, notFound };
|
|
5191
|
+
}
|
|
4742
5192
|
async deleteRelationshipsByType(repositoryId, relationshipType) {
|
|
4743
5193
|
const store = this.getStore(repositoryId);
|
|
4744
5194
|
let deletedRelationships = 0;
|
|
@@ -5150,6 +5600,8 @@ var NoOpEmbeddingProvider = class {
|
|
|
5150
5600
|
RelationshipConstraintError,
|
|
5151
5601
|
RelationshipNotFoundError,
|
|
5152
5602
|
RepositoryNotFoundError,
|
|
5603
|
+
RepositoryValidator,
|
|
5604
|
+
SelfReferentialRelationshipError,
|
|
5153
5605
|
TraversalTimeoutError,
|
|
5154
5606
|
TraversalValidationError,
|
|
5155
5607
|
TraversalVocabularyError,
|