learngraph 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -21
- package/README.md +165 -3
- package/dist/cjs/llm/adapters/anthropic.js +124 -0
- package/dist/cjs/llm/adapters/anthropic.js.map +1 -0
- package/dist/cjs/llm/adapters/base.js +100 -0
- package/dist/cjs/llm/adapters/base.js.map +1 -0
- package/dist/cjs/llm/adapters/index.js +22 -0
- package/dist/cjs/llm/adapters/index.js.map +1 -0
- package/dist/cjs/llm/adapters/ollama.js +149 -0
- package/dist/cjs/llm/adapters/ollama.js.map +1 -0
- package/dist/cjs/llm/adapters/openai.js +126 -0
- package/dist/cjs/llm/adapters/openai.js.map +1 -0
- package/dist/cjs/llm/index.js +34 -5
- package/dist/cjs/llm/index.js.map +1 -1
- package/dist/cjs/llm/orchestrator.js +219 -0
- package/dist/cjs/llm/orchestrator.js.map +1 -0
- package/dist/cjs/llm/prompts.js +367 -0
- package/dist/cjs/llm/prompts.js.map +1 -0
- package/dist/cjs/parsers/base.js +189 -0
- package/dist/cjs/parsers/base.js.map +1 -0
- package/dist/cjs/parsers/demo.js +159 -0
- package/dist/cjs/parsers/demo.js.map +1 -0
- package/dist/cjs/parsers/extractor.js +191 -0
- package/dist/cjs/parsers/extractor.js.map +1 -0
- package/dist/cjs/parsers/index.js +43 -4
- package/dist/cjs/parsers/index.js.map +1 -1
- package/dist/cjs/parsers/json.js +157 -0
- package/dist/cjs/parsers/json.js.map +1 -0
- package/dist/cjs/parsers/markdown.js +168 -0
- package/dist/cjs/parsers/markdown.js.map +1 -0
- package/dist/cjs/parsers/samples.js +139 -0
- package/dist/cjs/parsers/samples.js.map +1 -0
- package/dist/cjs/storage/base.js +231 -0
- package/dist/cjs/storage/base.js.map +1 -0
- package/dist/cjs/storage/errors.js +128 -0
- package/dist/cjs/storage/errors.js.map +1 -0
- package/dist/cjs/storage/index.js +92 -5
- package/dist/cjs/storage/index.js.map +1 -1
- package/dist/cjs/storage/levelgraph.js +855 -0
- package/dist/cjs/storage/levelgraph.js.map +1 -0
- package/dist/cjs/storage/memory.js +447 -0
- package/dist/cjs/storage/memory.js.map +1 -0
- package/dist/cjs/storage/neo4j.js +866 -0
- package/dist/cjs/storage/neo4j.js.map +1 -0
- package/dist/cjs/storage/seeds.js +565 -0
- package/dist/cjs/storage/seeds.js.map +1 -0
- package/dist/cjs/types/llm.js +8 -0
- package/dist/cjs/types/llm.js.map +1 -0
- package/dist/cjs/types/parser.js +8 -0
- package/dist/cjs/types/parser.js.map +1 -0
- package/dist/esm/llm/adapters/anthropic.js +119 -0
- package/dist/esm/llm/adapters/anthropic.js.map +1 -0
- package/dist/esm/llm/adapters/base.js +95 -0
- package/dist/esm/llm/adapters/base.js.map +1 -0
- package/dist/esm/llm/adapters/index.js +10 -0
- package/dist/esm/llm/adapters/index.js.map +1 -0
- package/dist/esm/llm/adapters/ollama.js +144 -0
- package/dist/esm/llm/adapters/ollama.js.map +1 -0
- package/dist/esm/llm/adapters/openai.js +121 -0
- package/dist/esm/llm/adapters/openai.js.map +1 -0
- package/dist/esm/llm/index.js +12 -6
- package/dist/esm/llm/index.js.map +1 -1
- package/dist/esm/llm/orchestrator.js +214 -0
- package/dist/esm/llm/orchestrator.js.map +1 -0
- package/dist/esm/llm/prompts.js +360 -0
- package/dist/esm/llm/prompts.js.map +1 -0
- package/dist/esm/parsers/base.js +179 -0
- package/dist/esm/parsers/base.js.map +1 -0
- package/dist/esm/parsers/demo.js +154 -0
- package/dist/esm/parsers/demo.js.map +1 -0
- package/dist/esm/parsers/extractor.js +187 -0
- package/dist/esm/parsers/extractor.js.map +1 -0
- package/dist/esm/parsers/index.js +24 -5
- package/dist/esm/parsers/index.js.map +1 -1
- package/dist/esm/parsers/json.js +153 -0
- package/dist/esm/parsers/json.js.map +1 -0
- package/dist/esm/parsers/markdown.js +164 -0
- package/dist/esm/parsers/markdown.js.map +1 -0
- package/dist/esm/parsers/samples.js +136 -0
- package/dist/esm/parsers/samples.js.map +1 -0
- package/dist/esm/storage/base.js +221 -0
- package/dist/esm/storage/base.js.map +1 -0
- package/dist/esm/storage/errors.js +116 -0
- package/dist/esm/storage/errors.js.map +1 -0
- package/dist/esm/storage/index.js +71 -6
- package/dist/esm/storage/index.js.map +1 -1
- package/dist/esm/storage/levelgraph.js +818 -0
- package/dist/esm/storage/levelgraph.js.map +1 -0
- package/dist/esm/storage/memory.js +443 -0
- package/dist/esm/storage/memory.js.map +1 -0
- package/dist/esm/storage/neo4j.js +829 -0
- package/dist/esm/storage/neo4j.js.map +1 -0
- package/dist/esm/storage/seeds.js +561 -0
- package/dist/esm/storage/seeds.js.map +1 -0
- package/dist/esm/types/llm.js +7 -0
- package/dist/esm/types/llm.js.map +1 -0
- package/dist/esm/types/parser.js +7 -0
- package/dist/esm/types/parser.js.map +1 -0
- package/dist/types/llm/adapters/anthropic.d.ts +21 -0
- package/dist/types/llm/adapters/anthropic.d.ts.map +1 -0
- package/dist/types/llm/adapters/base.d.ts +46 -0
- package/dist/types/llm/adapters/base.d.ts.map +1 -0
- package/dist/types/llm/adapters/index.d.ts +11 -0
- package/dist/types/llm/adapters/index.d.ts.map +1 -0
- package/dist/types/llm/adapters/ollama.d.ts +30 -0
- package/dist/types/llm/adapters/ollama.d.ts.map +1 -0
- package/dist/types/llm/adapters/openai.d.ts +22 -0
- package/dist/types/llm/adapters/openai.d.ts.map +1 -0
- package/dist/types/llm/index.d.ts +5 -0
- package/dist/types/llm/index.d.ts.map +1 -1
- package/dist/types/llm/orchestrator.d.ts +35 -0
- package/dist/types/llm/orchestrator.d.ts.map +1 -0
- package/dist/types/llm/prompts.d.ts +269 -0
- package/dist/types/llm/prompts.d.ts.map +1 -0
- package/dist/types/parsers/base.d.ts +39 -0
- package/dist/types/parsers/base.d.ts.map +1 -0
- package/dist/types/parsers/demo.d.ts +87 -0
- package/dist/types/parsers/demo.d.ts.map +1 -0
- package/dist/types/parsers/extractor.d.ts +43 -0
- package/dist/types/parsers/extractor.d.ts.map +1 -0
- package/dist/types/parsers/index.d.ts +10 -0
- package/dist/types/parsers/index.d.ts.map +1 -1
- package/dist/types/parsers/json.d.ts +71 -0
- package/dist/types/parsers/json.d.ts.map +1 -0
- package/dist/types/parsers/markdown.d.ts +43 -0
- package/dist/types/parsers/markdown.d.ts.map +1 -0
- package/dist/types/parsers/samples.d.ts +27 -0
- package/dist/types/parsers/samples.d.ts.map +1 -0
- package/dist/types/storage/base.d.ts +39 -0
- package/dist/types/storage/base.d.ts.map +1 -0
- package/dist/types/storage/errors.d.ts +74 -0
- package/dist/types/storage/errors.d.ts.map +1 -0
- package/dist/types/storage/index.d.ts +50 -2
- package/dist/types/storage/index.d.ts.map +1 -1
- package/dist/types/storage/levelgraph.d.ts +92 -0
- package/dist/types/storage/levelgraph.d.ts.map +1 -0
- package/dist/types/storage/memory.d.ts +70 -0
- package/dist/types/storage/memory.d.ts.map +1 -0
- package/dist/types/storage/neo4j.d.ts +88 -0
- package/dist/types/storage/neo4j.d.ts.map +1 -0
- package/dist/types/storage/seeds.d.ts +27 -0
- package/dist/types/storage/seeds.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +2 -0
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/types/llm.d.ts +298 -0
- package/dist/types/types/llm.d.ts.map +1 -0
- package/dist/types/types/parser.d.ts +208 -0
- package/dist/types/types/parser.d.ts.map +1 -0
- package/package.json +4 -2
- package/scripts/postinstall.js +68 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LevelGraph storage adapter
|
|
4
|
+
*
|
|
5
|
+
* Browser-compatible graph storage using LevelGraph (triple store)
|
|
6
|
+
* built on LevelDB/IndexedDB.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.LevelGraphStorage = void 0;
|
|
45
|
+
const index_js_1 = require("../types/index.js");
|
|
46
|
+
const errors_js_1 = require("./errors.js");
|
|
47
|
+
const base_js_1 = require("./base.js");
|
|
48
|
+
/**
|
|
49
|
+
* Skill property predicates for triple mapping
|
|
50
|
+
*/
|
|
51
|
+
const SKILL_PREDICATES = {
|
|
52
|
+
TYPE: 'rdf:type',
|
|
53
|
+
NAME: 'lg:name',
|
|
54
|
+
DESCRIPTION: 'lg:description',
|
|
55
|
+
BLOOM_LEVEL: 'lg:bloomLevel',
|
|
56
|
+
DIFFICULTY: 'lg:difficulty',
|
|
57
|
+
IS_THRESHOLD: 'lg:isThresholdConcept',
|
|
58
|
+
MASTERY_THRESHOLD: 'lg:masteryThreshold',
|
|
59
|
+
ESTIMATED_MINUTES: 'lg:estimatedMinutes',
|
|
60
|
+
TAGS: 'lg:tags',
|
|
61
|
+
STANDARD_ALIGNMENT: 'lg:standardAlignment',
|
|
62
|
+
DOMAIN: 'lg:domain',
|
|
63
|
+
GRADE_LEVEL: 'lg:gradeLevel',
|
|
64
|
+
METADATA: 'lg:metadata',
|
|
65
|
+
CREATED_AT: 'lg:createdAt',
|
|
66
|
+
UPDATED_AT: 'lg:updatedAt',
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Edge property predicates for triple mapping
|
|
70
|
+
*/
|
|
71
|
+
const EDGE_PREDICATES = {
|
|
72
|
+
TYPE: 'rdf:type',
|
|
73
|
+
PREREQUISITE_OF: 'lg:prerequisiteOf',
|
|
74
|
+
EDGE_STRENGTH: 'lg:strength',
|
|
75
|
+
EDGE_TYPE: 'lg:edgeType',
|
|
76
|
+
REASONING: 'lg:reasoning',
|
|
77
|
+
METADATA: 'lg:edgeMetadata',
|
|
78
|
+
CREATED_AT: 'lg:edgeCreatedAt',
|
|
79
|
+
SOURCE: 'lg:source',
|
|
80
|
+
TARGET: 'lg:target',
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* LevelGraph storage adapter.
|
|
84
|
+
*
|
|
85
|
+
* Stores skill graphs using RDF-style triples, compatible with
|
|
86
|
+
* LevelDB (Node.js) and IndexedDB (browser).
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* import { LevelGraphStorage } from 'learngraph/storage';
|
|
91
|
+
* import levelgraph from 'levelgraph';
|
|
92
|
+
* import level from 'level';
|
|
93
|
+
*
|
|
94
|
+
* // Node.js usage
|
|
95
|
+
* const storage = new LevelGraphStorage();
|
|
96
|
+
* await storage.connect({
|
|
97
|
+
* backend: 'levelgraph',
|
|
98
|
+
* path: './my-graph-db',
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* // Browser usage (with level-js)
|
|
102
|
+
* await storage.connect({
|
|
103
|
+
* backend: 'levelgraph',
|
|
104
|
+
* dbName: 'my-graph-db',
|
|
105
|
+
* });
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
class LevelGraphStorage {
|
|
109
|
+
db = null;
|
|
110
|
+
_connected = false;
|
|
111
|
+
config = null;
|
|
112
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
113
|
+
// Triple Conversion Utilities
|
|
114
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
115
|
+
/**
|
|
116
|
+
* Convert a SkillNode to triples
|
|
117
|
+
*/
|
|
118
|
+
skillToTriples(skill) {
|
|
119
|
+
const id = skill.id;
|
|
120
|
+
const triples = [
|
|
121
|
+
{ subject: id, predicate: SKILL_PREDICATES.TYPE, object: 'Skill' },
|
|
122
|
+
{ subject: id, predicate: SKILL_PREDICATES.NAME, object: skill.name },
|
|
123
|
+
{ subject: id, predicate: SKILL_PREDICATES.DESCRIPTION, object: skill.description },
|
|
124
|
+
{ subject: id, predicate: SKILL_PREDICATES.BLOOM_LEVEL, object: skill.bloomLevel },
|
|
125
|
+
{ subject: id, predicate: SKILL_PREDICATES.DIFFICULTY, object: String(skill.difficulty) },
|
|
126
|
+
{ subject: id, predicate: SKILL_PREDICATES.IS_THRESHOLD, object: String(skill.isThresholdConcept) },
|
|
127
|
+
{ subject: id, predicate: SKILL_PREDICATES.MASTERY_THRESHOLD, object: String(skill.masteryThreshold) },
|
|
128
|
+
{ subject: id, predicate: SKILL_PREDICATES.ESTIMATED_MINUTES, object: String(skill.estimatedMinutes) },
|
|
129
|
+
{ subject: id, predicate: SKILL_PREDICATES.TAGS, object: JSON.stringify(skill.tags) },
|
|
130
|
+
{ subject: id, predicate: SKILL_PREDICATES.METADATA, object: JSON.stringify(skill.metadata) },
|
|
131
|
+
{ subject: id, predicate: SKILL_PREDICATES.CREATED_AT, object: skill.createdAt },
|
|
132
|
+
{ subject: id, predicate: SKILL_PREDICATES.UPDATED_AT, object: skill.updatedAt },
|
|
133
|
+
];
|
|
134
|
+
if (skill.standardAlignment) {
|
|
135
|
+
triples.push({
|
|
136
|
+
subject: id,
|
|
137
|
+
predicate: SKILL_PREDICATES.STANDARD_ALIGNMENT,
|
|
138
|
+
object: JSON.stringify(skill.standardAlignment),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (skill.domain) {
|
|
142
|
+
triples.push({
|
|
143
|
+
subject: id,
|
|
144
|
+
predicate: SKILL_PREDICATES.DOMAIN,
|
|
145
|
+
object: skill.domain,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (skill.gradeLevel) {
|
|
149
|
+
triples.push({
|
|
150
|
+
subject: id,
|
|
151
|
+
predicate: SKILL_PREDICATES.GRADE_LEVEL,
|
|
152
|
+
object: skill.gradeLevel,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return triples;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Convert triples back to a SkillNode
|
|
159
|
+
*/
|
|
160
|
+
triplesToSkill(triples) {
|
|
161
|
+
if (triples.length === 0)
|
|
162
|
+
return null;
|
|
163
|
+
const props = {};
|
|
164
|
+
const id = triples[0]?.subject;
|
|
165
|
+
if (!id)
|
|
166
|
+
return null;
|
|
167
|
+
for (const triple of triples) {
|
|
168
|
+
props[triple.predicate] = triple.object;
|
|
169
|
+
}
|
|
170
|
+
const tagsStr = props[SKILL_PREDICATES.TAGS];
|
|
171
|
+
const metadataStr = props[SKILL_PREDICATES.METADATA];
|
|
172
|
+
const node = {
|
|
173
|
+
id: (0, index_js_1.createSkillId)(id),
|
|
174
|
+
name: props[SKILL_PREDICATES.NAME] ?? '',
|
|
175
|
+
description: props[SKILL_PREDICATES.DESCRIPTION] ?? '',
|
|
176
|
+
bloomLevel: (props[SKILL_PREDICATES.BLOOM_LEVEL] ?? 'remember'),
|
|
177
|
+
difficulty: parseFloat(props[SKILL_PREDICATES.DIFFICULTY] ?? '0.5'),
|
|
178
|
+
isThresholdConcept: props[SKILL_PREDICATES.IS_THRESHOLD] === 'true',
|
|
179
|
+
masteryThreshold: parseFloat(props[SKILL_PREDICATES.MASTERY_THRESHOLD] ?? '0.8'),
|
|
180
|
+
estimatedMinutes: parseInt(props[SKILL_PREDICATES.ESTIMATED_MINUTES] ?? '30', 10),
|
|
181
|
+
tags: tagsStr ? JSON.parse(tagsStr) : [],
|
|
182
|
+
metadata: metadataStr ? JSON.parse(metadataStr) : {},
|
|
183
|
+
createdAt: props[SKILL_PREDICATES.CREATED_AT] ?? (0, base_js_1.nowISO)(),
|
|
184
|
+
updatedAt: props[SKILL_PREDICATES.UPDATED_AT] ?? (0, base_js_1.nowISO)(),
|
|
185
|
+
};
|
|
186
|
+
// Only add optional fields if they have values
|
|
187
|
+
const standardAlignmentStr = props[SKILL_PREDICATES.STANDARD_ALIGNMENT];
|
|
188
|
+
const domainStr = props[SKILL_PREDICATES.DOMAIN];
|
|
189
|
+
const gradeLevelStr = props[SKILL_PREDICATES.GRADE_LEVEL];
|
|
190
|
+
if (standardAlignmentStr) {
|
|
191
|
+
node.standardAlignment = JSON.parse(standardAlignmentStr);
|
|
192
|
+
}
|
|
193
|
+
if (domainStr) {
|
|
194
|
+
node.domain = domainStr;
|
|
195
|
+
}
|
|
196
|
+
if (gradeLevelStr) {
|
|
197
|
+
node.gradeLevel = gradeLevelStr;
|
|
198
|
+
}
|
|
199
|
+
return node;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Convert a PrerequisiteEdge to triples
|
|
203
|
+
*/
|
|
204
|
+
edgeToTriples(edge) {
|
|
205
|
+
const id = edge.id;
|
|
206
|
+
const triples = [
|
|
207
|
+
{ subject: id, predicate: EDGE_PREDICATES.TYPE, object: 'Edge' },
|
|
208
|
+
{ subject: id, predicate: EDGE_PREDICATES.SOURCE, object: edge.sourceId },
|
|
209
|
+
{ subject: id, predicate: EDGE_PREDICATES.TARGET, object: edge.targetId },
|
|
210
|
+
{ subject: id, predicate: EDGE_PREDICATES.EDGE_STRENGTH, object: String(edge.strength) },
|
|
211
|
+
{ subject: id, predicate: EDGE_PREDICATES.EDGE_TYPE, object: edge.type },
|
|
212
|
+
{ subject: id, predicate: EDGE_PREDICATES.METADATA, object: JSON.stringify(edge.metadata) },
|
|
213
|
+
{ subject: id, predicate: EDGE_PREDICATES.CREATED_AT, object: edge.createdAt },
|
|
214
|
+
// Store the relationship triple
|
|
215
|
+
{
|
|
216
|
+
subject: edge.sourceId,
|
|
217
|
+
predicate: EDGE_PREDICATES.PREREQUISITE_OF,
|
|
218
|
+
object: edge.targetId,
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
if (edge.reasoning) {
|
|
222
|
+
triples.push({
|
|
223
|
+
subject: id,
|
|
224
|
+
predicate: EDGE_PREDICATES.REASONING,
|
|
225
|
+
object: edge.reasoning,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return triples;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Convert triples back to a PrerequisiteEdge
|
|
232
|
+
*/
|
|
233
|
+
triplesToEdge(triples) {
|
|
234
|
+
if (triples.length === 0)
|
|
235
|
+
return null;
|
|
236
|
+
const props = {};
|
|
237
|
+
const id = triples[0]?.subject;
|
|
238
|
+
if (!id)
|
|
239
|
+
return null;
|
|
240
|
+
for (const triple of triples) {
|
|
241
|
+
props[triple.predicate] = triple.object;
|
|
242
|
+
}
|
|
243
|
+
const metadataStr = props[EDGE_PREDICATES.METADATA];
|
|
244
|
+
const reasoningStr = props[EDGE_PREDICATES.REASONING];
|
|
245
|
+
const edge = {
|
|
246
|
+
id: (0, index_js_1.createEdgeId)(id),
|
|
247
|
+
sourceId: (0, index_js_1.createSkillId)(props[EDGE_PREDICATES.SOURCE] ?? ''),
|
|
248
|
+
targetId: (0, index_js_1.createSkillId)(props[EDGE_PREDICATES.TARGET] ?? ''),
|
|
249
|
+
strength: parseFloat(props[EDGE_PREDICATES.EDGE_STRENGTH] ?? '0.5'),
|
|
250
|
+
type: (props[EDGE_PREDICATES.EDGE_TYPE] ?? 'soft'),
|
|
251
|
+
metadata: metadataStr ? JSON.parse(metadataStr) : {},
|
|
252
|
+
createdAt: props[EDGE_PREDICATES.CREATED_AT] ?? (0, base_js_1.nowISO)(),
|
|
253
|
+
};
|
|
254
|
+
// Only add optional fields if they have values
|
|
255
|
+
if (reasoningStr) {
|
|
256
|
+
edge.reasoning = reasoningStr;
|
|
257
|
+
}
|
|
258
|
+
return edge;
|
|
259
|
+
}
|
|
260
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
261
|
+
// Promisified LevelGraph Operations
|
|
262
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
263
|
+
put(triples) {
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
if (!this.db)
|
|
266
|
+
return reject(new Error('Not connected'));
|
|
267
|
+
this.db.put(triples, (err) => {
|
|
268
|
+
if (err)
|
|
269
|
+
reject(err);
|
|
270
|
+
else
|
|
271
|
+
resolve();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
del(triples) {
|
|
276
|
+
return new Promise((resolve, reject) => {
|
|
277
|
+
if (!this.db)
|
|
278
|
+
return reject(new Error('Not connected'));
|
|
279
|
+
this.db.del(triples, (err) => {
|
|
280
|
+
if (err)
|
|
281
|
+
reject(err);
|
|
282
|
+
else
|
|
283
|
+
resolve();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
get(pattern) {
|
|
288
|
+
return new Promise((resolve, reject) => {
|
|
289
|
+
if (!this.db)
|
|
290
|
+
return reject(new Error('Not connected'));
|
|
291
|
+
this.db.get(pattern, (err, triples) => {
|
|
292
|
+
if (err)
|
|
293
|
+
reject(err);
|
|
294
|
+
else
|
|
295
|
+
resolve(triples);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
300
|
+
// Connection Management
|
|
301
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
302
|
+
async connect(config) {
|
|
303
|
+
if (config.backend !== 'levelgraph') {
|
|
304
|
+
throw new Error('LevelGraphStorage only supports "levelgraph" backend');
|
|
305
|
+
}
|
|
306
|
+
this.config = config;
|
|
307
|
+
try {
|
|
308
|
+
// Dynamic import of levelgraph and level
|
|
309
|
+
const levelgraph = await Promise.resolve().then(() => __importStar(require('levelgraph')));
|
|
310
|
+
const level = await Promise.resolve().then(() => __importStar(require('level')));
|
|
311
|
+
const dbPath = this.config.path || this.config.dbName || './learngraph-db';
|
|
312
|
+
const leveldb = new level.Level(dbPath);
|
|
313
|
+
// Cast the result to our LevelGraphDB interface
|
|
314
|
+
this.db = levelgraph.default(leveldb);
|
|
315
|
+
this._connected = true;
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
throw new errors_js_1.ConnectionError(`Failed to connect to LevelGraph: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async disconnect() {
|
|
322
|
+
this.db = null;
|
|
323
|
+
this._connected = false;
|
|
324
|
+
}
|
|
325
|
+
isConnected() {
|
|
326
|
+
return this._connected;
|
|
327
|
+
}
|
|
328
|
+
async getStatus() {
|
|
329
|
+
const startTime = Date.now();
|
|
330
|
+
if (!this._connected || !this.db) {
|
|
331
|
+
return {
|
|
332
|
+
connected: false,
|
|
333
|
+
backend: 'levelgraph',
|
|
334
|
+
lastChecked: new Date(),
|
|
335
|
+
error: 'Not connected',
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
// Simple health check - try to get any triple
|
|
340
|
+
await this.get({ predicate: SKILL_PREDICATES.TYPE });
|
|
341
|
+
return {
|
|
342
|
+
connected: true,
|
|
343
|
+
backend: 'levelgraph',
|
|
344
|
+
latencyMs: Date.now() - startTime,
|
|
345
|
+
lastChecked: new Date(),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
return {
|
|
350
|
+
connected: false,
|
|
351
|
+
backend: 'levelgraph',
|
|
352
|
+
lastChecked: new Date(),
|
|
353
|
+
error: error instanceof Error ? error.message : String(error),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
358
|
+
// Skill Node CRUD
|
|
359
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
360
|
+
async createSkill(input) {
|
|
361
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
362
|
+
(0, base_js_1.validateSkillInput)(input);
|
|
363
|
+
const skill = (0, base_js_1.inputToSkillNode)(input);
|
|
364
|
+
// Check for duplicate
|
|
365
|
+
const existing = await this.get({
|
|
366
|
+
subject: skill.id,
|
|
367
|
+
predicate: SKILL_PREDICATES.TYPE,
|
|
368
|
+
object: 'Skill',
|
|
369
|
+
});
|
|
370
|
+
if (existing.length > 0) {
|
|
371
|
+
throw new errors_js_1.DuplicateError('skill', skill.id);
|
|
372
|
+
}
|
|
373
|
+
const triples = this.skillToTriples(skill);
|
|
374
|
+
await this.put(triples);
|
|
375
|
+
return skill;
|
|
376
|
+
}
|
|
377
|
+
async getSkill(id) {
|
|
378
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
379
|
+
const triples = await this.get({ subject: id });
|
|
380
|
+
if (triples.length === 0)
|
|
381
|
+
return null;
|
|
382
|
+
// Check if it's actually a Skill
|
|
383
|
+
const typeTriple = triples.find((t) => t.predicate === SKILL_PREDICATES.TYPE && t.object === 'Skill');
|
|
384
|
+
if (!typeTriple)
|
|
385
|
+
return null;
|
|
386
|
+
return this.triplesToSkill(triples);
|
|
387
|
+
}
|
|
388
|
+
async getSkills(ids) {
|
|
389
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
390
|
+
const skills = [];
|
|
391
|
+
for (const id of ids) {
|
|
392
|
+
const skill = await this.getSkill(id);
|
|
393
|
+
if (skill)
|
|
394
|
+
skills.push(skill);
|
|
395
|
+
}
|
|
396
|
+
return skills;
|
|
397
|
+
}
|
|
398
|
+
async updateSkill(id, updates) {
|
|
399
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
400
|
+
const existing = await this.getSkill(id);
|
|
401
|
+
if (!existing) {
|
|
402
|
+
throw new errors_js_1.NotFoundError('skill', id);
|
|
403
|
+
}
|
|
404
|
+
// Delete old triples
|
|
405
|
+
const oldTriples = await this.get({ subject: id });
|
|
406
|
+
await this.del(oldTriples.filter((t) => t.predicate.startsWith('lg:')));
|
|
407
|
+
// Create updated skill
|
|
408
|
+
const updated = {
|
|
409
|
+
...existing,
|
|
410
|
+
...updates,
|
|
411
|
+
id: existing.id,
|
|
412
|
+
createdAt: existing.createdAt,
|
|
413
|
+
updatedAt: (0, base_js_1.nowISO)(),
|
|
414
|
+
};
|
|
415
|
+
// Insert new triples
|
|
416
|
+
const newTriples = this.skillToTriples(updated);
|
|
417
|
+
await this.put(newTriples);
|
|
418
|
+
return updated;
|
|
419
|
+
}
|
|
420
|
+
async deleteSkill(id) {
|
|
421
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
422
|
+
// Delete skill triples
|
|
423
|
+
const skillTriples = await this.get({ subject: id });
|
|
424
|
+
if (skillTriples.length > 0) {
|
|
425
|
+
await this.del(skillTriples);
|
|
426
|
+
}
|
|
427
|
+
// Delete edges referencing this skill
|
|
428
|
+
const sourceEdges = await this.get({
|
|
429
|
+
predicate: EDGE_PREDICATES.SOURCE,
|
|
430
|
+
object: id,
|
|
431
|
+
});
|
|
432
|
+
const targetEdges = await this.get({
|
|
433
|
+
predicate: EDGE_PREDICATES.TARGET,
|
|
434
|
+
object: id,
|
|
435
|
+
});
|
|
436
|
+
for (const edge of [...sourceEdges, ...targetEdges]) {
|
|
437
|
+
const edgeTriples = await this.get({ subject: edge.subject });
|
|
438
|
+
await this.del(edgeTriples);
|
|
439
|
+
}
|
|
440
|
+
// Delete prerequisiteOf relationships
|
|
441
|
+
const prereqTriples = await this.get({
|
|
442
|
+
subject: id,
|
|
443
|
+
predicate: EDGE_PREDICATES.PREREQUISITE_OF,
|
|
444
|
+
});
|
|
445
|
+
if (prereqTriples.length > 0) {
|
|
446
|
+
await this.del(prereqTriples);
|
|
447
|
+
}
|
|
448
|
+
const depTriples = await this.get({
|
|
449
|
+
predicate: EDGE_PREDICATES.PREREQUISITE_OF,
|
|
450
|
+
object: id,
|
|
451
|
+
});
|
|
452
|
+
if (depTriples.length > 0) {
|
|
453
|
+
await this.del(depTriples);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async findSkills(query) {
|
|
457
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
458
|
+
// Get all skills
|
|
459
|
+
const typeTriples = await this.get({
|
|
460
|
+
predicate: SKILL_PREDICATES.TYPE,
|
|
461
|
+
object: 'Skill',
|
|
462
|
+
});
|
|
463
|
+
const skills = [];
|
|
464
|
+
for (const typeTriple of typeTriples) {
|
|
465
|
+
const skill = await this.getSkill((0, index_js_1.createSkillId)(typeTriple.subject));
|
|
466
|
+
if (skill)
|
|
467
|
+
skills.push(skill);
|
|
468
|
+
}
|
|
469
|
+
// Apply filters in memory (LevelGraph doesn't support complex queries)
|
|
470
|
+
let results = skills;
|
|
471
|
+
if (query.filters) {
|
|
472
|
+
for (const filter of query.filters) {
|
|
473
|
+
results = results.filter((skill) => {
|
|
474
|
+
const value = skill[filter.field];
|
|
475
|
+
switch (filter.operator) {
|
|
476
|
+
case 'eq':
|
|
477
|
+
return value === filter.value;
|
|
478
|
+
case 'neq':
|
|
479
|
+
return value !== filter.value;
|
|
480
|
+
case 'gt':
|
|
481
|
+
return typeof value === 'number' && value > filter.value;
|
|
482
|
+
case 'gte':
|
|
483
|
+
return typeof value === 'number' && value >= filter.value;
|
|
484
|
+
case 'lt':
|
|
485
|
+
return typeof value === 'number' && value < filter.value;
|
|
486
|
+
case 'lte':
|
|
487
|
+
return typeof value === 'number' && value <= filter.value;
|
|
488
|
+
case 'in':
|
|
489
|
+
return Array.isArray(filter.value) && filter.value.includes(value);
|
|
490
|
+
case 'contains':
|
|
491
|
+
if (Array.isArray(value)) {
|
|
492
|
+
return value.includes(filter.value);
|
|
493
|
+
}
|
|
494
|
+
if (typeof value === 'string') {
|
|
495
|
+
return value.includes(filter.value);
|
|
496
|
+
}
|
|
497
|
+
return false;
|
|
498
|
+
default:
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// Apply sorting
|
|
505
|
+
if (query.sorting && query.sorting.length > 0) {
|
|
506
|
+
results.sort((a, b) => {
|
|
507
|
+
for (const sortItem of query.sorting) {
|
|
508
|
+
const aVal = a[sortItem.field];
|
|
509
|
+
const bVal = b[sortItem.field];
|
|
510
|
+
const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
|
|
511
|
+
if (cmp !== 0) {
|
|
512
|
+
return sortItem.direction === 'asc' ? cmp : -cmp;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return 0;
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
// Apply pagination
|
|
519
|
+
if (query.pagination) {
|
|
520
|
+
const { offset = 0, limit = 100 } = query.pagination;
|
|
521
|
+
results = results.slice(offset, offset + limit);
|
|
522
|
+
}
|
|
523
|
+
return results;
|
|
524
|
+
}
|
|
525
|
+
async countSkills(query) {
|
|
526
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
527
|
+
if (!query) {
|
|
528
|
+
const typeTriples = await this.get({
|
|
529
|
+
predicate: SKILL_PREDICATES.TYPE,
|
|
530
|
+
object: 'Skill',
|
|
531
|
+
});
|
|
532
|
+
return typeTriples.length;
|
|
533
|
+
}
|
|
534
|
+
const { pagination, ...queryWithoutPagination } = query;
|
|
535
|
+
const results = await this.findSkills(queryWithoutPagination);
|
|
536
|
+
return results.length;
|
|
537
|
+
}
|
|
538
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
539
|
+
// Prerequisite Edge CRUD
|
|
540
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
541
|
+
async createPrerequisite(input) {
|
|
542
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
543
|
+
(0, base_js_1.validateEdgeInput)(input);
|
|
544
|
+
// Verify source and target exist
|
|
545
|
+
const source = await this.getSkill(input.sourceId);
|
|
546
|
+
if (!source) {
|
|
547
|
+
throw new errors_js_1.ReferenceError(input.id || 'new', input.sourceId, 'source');
|
|
548
|
+
}
|
|
549
|
+
const target = await this.getSkill(input.targetId);
|
|
550
|
+
if (!target) {
|
|
551
|
+
throw new errors_js_1.ReferenceError(input.id || 'new', input.targetId, 'target');
|
|
552
|
+
}
|
|
553
|
+
const edge = (0, base_js_1.inputToEdge)(input);
|
|
554
|
+
// Check for duplicate
|
|
555
|
+
const existing = await this.get({
|
|
556
|
+
subject: edge.id,
|
|
557
|
+
predicate: EDGE_PREDICATES.TYPE,
|
|
558
|
+
object: 'Edge',
|
|
559
|
+
});
|
|
560
|
+
if (existing.length > 0) {
|
|
561
|
+
throw new errors_js_1.DuplicateError('edge', edge.id);
|
|
562
|
+
}
|
|
563
|
+
const triples = this.edgeToTriples(edge);
|
|
564
|
+
await this.put(triples);
|
|
565
|
+
return edge;
|
|
566
|
+
}
|
|
567
|
+
async getPrerequisite(id) {
|
|
568
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
569
|
+
const triples = await this.get({ subject: id });
|
|
570
|
+
if (triples.length === 0)
|
|
571
|
+
return null;
|
|
572
|
+
const typeTriple = triples.find((t) => t.predicate === EDGE_PREDICATES.TYPE && t.object === 'Edge');
|
|
573
|
+
if (!typeTriple)
|
|
574
|
+
return null;
|
|
575
|
+
return this.triplesToEdge(triples);
|
|
576
|
+
}
|
|
577
|
+
async deletePrerequisite(id) {
|
|
578
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
579
|
+
const edge = await this.getPrerequisite(id);
|
|
580
|
+
if (!edge)
|
|
581
|
+
return;
|
|
582
|
+
// Delete edge triples
|
|
583
|
+
const edgeTriples = await this.get({ subject: id });
|
|
584
|
+
if (edgeTriples.length > 0) {
|
|
585
|
+
await this.del(edgeTriples);
|
|
586
|
+
}
|
|
587
|
+
// Delete the relationship triple
|
|
588
|
+
await this.del([
|
|
589
|
+
{
|
|
590
|
+
subject: edge.sourceId,
|
|
591
|
+
predicate: EDGE_PREDICATES.PREREQUISITE_OF,
|
|
592
|
+
object: edge.targetId,
|
|
593
|
+
},
|
|
594
|
+
]);
|
|
595
|
+
}
|
|
596
|
+
async findPrerequisites(criteria) {
|
|
597
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
598
|
+
// Get all edges
|
|
599
|
+
const typeTriples = await this.get({
|
|
600
|
+
predicate: EDGE_PREDICATES.TYPE,
|
|
601
|
+
object: 'Edge',
|
|
602
|
+
});
|
|
603
|
+
const edges = [];
|
|
604
|
+
for (const typeTriple of typeTriples) {
|
|
605
|
+
const edge = await this.getPrerequisite((0, index_js_1.createEdgeId)(typeTriple.subject));
|
|
606
|
+
if (edge)
|
|
607
|
+
edges.push(edge);
|
|
608
|
+
}
|
|
609
|
+
// Apply filters
|
|
610
|
+
let results = edges;
|
|
611
|
+
if (criteria.sourceId) {
|
|
612
|
+
results = results.filter((e) => e.sourceId === criteria.sourceId);
|
|
613
|
+
}
|
|
614
|
+
if (criteria.targetId) {
|
|
615
|
+
results = results.filter((e) => e.targetId === criteria.targetId);
|
|
616
|
+
}
|
|
617
|
+
if (criteria.type) {
|
|
618
|
+
results = results.filter((e) => e.type === criteria.type);
|
|
619
|
+
}
|
|
620
|
+
if (criteria.minStrength !== undefined) {
|
|
621
|
+
results = results.filter((e) => e.strength >= criteria.minStrength);
|
|
622
|
+
}
|
|
623
|
+
return results;
|
|
624
|
+
}
|
|
625
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
626
|
+
// Graph Traversal
|
|
627
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
628
|
+
async getPrerequisitesOf(skillId) {
|
|
629
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
630
|
+
const prereqTriples = await this.get({
|
|
631
|
+
predicate: EDGE_PREDICATES.PREREQUISITE_OF,
|
|
632
|
+
object: skillId,
|
|
633
|
+
});
|
|
634
|
+
const skills = [];
|
|
635
|
+
for (const triple of prereqTriples) {
|
|
636
|
+
const skill = await this.getSkill((0, index_js_1.createSkillId)(triple.subject));
|
|
637
|
+
if (skill)
|
|
638
|
+
skills.push(skill);
|
|
639
|
+
}
|
|
640
|
+
return skills;
|
|
641
|
+
}
|
|
642
|
+
async getDependentsOf(skillId) {
|
|
643
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
644
|
+
const depTriples = await this.get({
|
|
645
|
+
subject: skillId,
|
|
646
|
+
predicate: EDGE_PREDICATES.PREREQUISITE_OF,
|
|
647
|
+
});
|
|
648
|
+
const skills = [];
|
|
649
|
+
for (const triple of depTriples) {
|
|
650
|
+
const skill = await this.getSkill((0, index_js_1.createSkillId)(triple.object));
|
|
651
|
+
if (skill)
|
|
652
|
+
skills.push(skill);
|
|
653
|
+
}
|
|
654
|
+
return skills;
|
|
655
|
+
}
|
|
656
|
+
async getSubgraph(rootId, depth) {
|
|
657
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
658
|
+
const visitedNodes = new Set();
|
|
659
|
+
const subgraphNodes = [];
|
|
660
|
+
const subgraphEdges = [];
|
|
661
|
+
const queue = [
|
|
662
|
+
{ id: rootId, level: 0 },
|
|
663
|
+
];
|
|
664
|
+
while (queue.length > 0) {
|
|
665
|
+
const { id, level } = queue.shift();
|
|
666
|
+
if (visitedNodes.has(id) || level > depth) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
visitedNodes.add(id);
|
|
670
|
+
const node = await this.getSkill(id);
|
|
671
|
+
if (node) {
|
|
672
|
+
subgraphNodes.push(node);
|
|
673
|
+
// Get prerequisites
|
|
674
|
+
const prereqTriples = await this.get({
|
|
675
|
+
predicate: EDGE_PREDICATES.PREREQUISITE_OF,
|
|
676
|
+
object: id,
|
|
677
|
+
});
|
|
678
|
+
for (const triple of prereqTriples) {
|
|
679
|
+
queue.push({ id: (0, index_js_1.createSkillId)(triple.subject), level: level + 1 });
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
// Get all edges between visited nodes
|
|
684
|
+
const allEdges = await this.findPrerequisites({});
|
|
685
|
+
for (const edge of allEdges) {
|
|
686
|
+
if (visitedNodes.has(edge.sourceId) &&
|
|
687
|
+
visitedNodes.has(edge.targetId)) {
|
|
688
|
+
subgraphEdges.push(edge);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
rootId,
|
|
693
|
+
depth,
|
|
694
|
+
nodes: subgraphNodes,
|
|
695
|
+
edges: subgraphEdges,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
async getRootSkills() {
|
|
699
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
700
|
+
const allSkills = await this.findSkills({});
|
|
701
|
+
const hasPrereq = new Set();
|
|
702
|
+
const prereqTriples = await this.get({
|
|
703
|
+
predicate: EDGE_PREDICATES.PREREQUISITE_OF,
|
|
704
|
+
});
|
|
705
|
+
for (const triple of prereqTriples) {
|
|
706
|
+
hasPrereq.add(triple.object);
|
|
707
|
+
}
|
|
708
|
+
return allSkills.filter((skill) => !hasPrereq.has(skill.id));
|
|
709
|
+
}
|
|
710
|
+
async getLeafSkills() {
|
|
711
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
712
|
+
const allSkills = await this.findSkills({});
|
|
713
|
+
const hasDependents = new Set();
|
|
714
|
+
const prereqTriples = await this.get({
|
|
715
|
+
predicate: EDGE_PREDICATES.PREREQUISITE_OF,
|
|
716
|
+
});
|
|
717
|
+
for (const triple of prereqTriples) {
|
|
718
|
+
hasDependents.add(triple.subject);
|
|
719
|
+
}
|
|
720
|
+
return allSkills.filter((skill) => !hasDependents.has(skill.id));
|
|
721
|
+
}
|
|
722
|
+
async getPath(fromId, toId) {
|
|
723
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
724
|
+
const visited = new Set();
|
|
725
|
+
const parent = new Map();
|
|
726
|
+
const queue = [fromId];
|
|
727
|
+
visited.add(fromId);
|
|
728
|
+
while (queue.length > 0) {
|
|
729
|
+
const current = queue.shift();
|
|
730
|
+
if (current === toId) {
|
|
731
|
+
const path = [];
|
|
732
|
+
let node = toId;
|
|
733
|
+
while (node) {
|
|
734
|
+
const skillNode = await this.getSkill((0, index_js_1.createSkillId)(node));
|
|
735
|
+
if (skillNode)
|
|
736
|
+
path.unshift(skillNode);
|
|
737
|
+
node = parent.get(node);
|
|
738
|
+
}
|
|
739
|
+
return path;
|
|
740
|
+
}
|
|
741
|
+
const dependents = await this.getDependentsOf(current);
|
|
742
|
+
for (const dep of dependents) {
|
|
743
|
+
const depId = dep.id;
|
|
744
|
+
if (!visited.has(depId)) {
|
|
745
|
+
visited.add(depId);
|
|
746
|
+
parent.set(depId, current);
|
|
747
|
+
queue.push(dep.id);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
754
|
+
// Bulk Operations
|
|
755
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
756
|
+
async importGraph(nodes, edges, options) {
|
|
757
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
758
|
+
const startTime = Date.now();
|
|
759
|
+
const result = {
|
|
760
|
+
nodesCreated: 0,
|
|
761
|
+
edgesCreated: 0,
|
|
762
|
+
nodesSkipped: 0,
|
|
763
|
+
edgesSkipped: 0,
|
|
764
|
+
errors: [],
|
|
765
|
+
durationMs: 0,
|
|
766
|
+
};
|
|
767
|
+
if (options?.clearExisting) {
|
|
768
|
+
await this.clearAll();
|
|
769
|
+
}
|
|
770
|
+
// Import nodes
|
|
771
|
+
for (const nodeInput of nodes) {
|
|
772
|
+
try {
|
|
773
|
+
const skill = (0, base_js_1.inputToSkillNode)(nodeInput);
|
|
774
|
+
const existing = await this.getSkill(skill.id);
|
|
775
|
+
if (existing) {
|
|
776
|
+
result.nodesSkipped++;
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
const triples = this.skillToTriples(skill);
|
|
780
|
+
await this.put(triples);
|
|
781
|
+
result.nodesCreated++;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
catch (error) {
|
|
785
|
+
result.errors.push({
|
|
786
|
+
type: 'node',
|
|
787
|
+
id: nodeInput.id || 'unknown',
|
|
788
|
+
error: error instanceof Error ? error.message : String(error),
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
// Import edges
|
|
793
|
+
for (const edgeInput of edges) {
|
|
794
|
+
try {
|
|
795
|
+
const edge = (0, base_js_1.inputToEdge)(edgeInput);
|
|
796
|
+
const source = await this.getSkill(edge.sourceId);
|
|
797
|
+
if (!source) {
|
|
798
|
+
throw new Error(`Source skill not found: ${edge.sourceId}`);
|
|
799
|
+
}
|
|
800
|
+
const target = await this.getSkill(edge.targetId);
|
|
801
|
+
if (!target) {
|
|
802
|
+
throw new Error(`Target skill not found: ${edge.targetId}`);
|
|
803
|
+
}
|
|
804
|
+
const existing = await this.getPrerequisite(edge.id);
|
|
805
|
+
if (existing) {
|
|
806
|
+
result.edgesSkipped++;
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
const triples = this.edgeToTriples(edge);
|
|
810
|
+
await this.put(triples);
|
|
811
|
+
result.edgesCreated++;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
catch (error) {
|
|
815
|
+
result.errors.push({
|
|
816
|
+
type: 'edge',
|
|
817
|
+
id: edgeInput.id || 'unknown',
|
|
818
|
+
error: error instanceof Error ? error.message : String(error),
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
result.durationMs = Date.now() - startTime;
|
|
823
|
+
return result;
|
|
824
|
+
}
|
|
825
|
+
async exportGraph() {
|
|
826
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
827
|
+
const nodes = await this.findSkills({});
|
|
828
|
+
const edges = await this.findPrerequisites({});
|
|
829
|
+
return {
|
|
830
|
+
version: index_js_1.GRAPH_VERSION,
|
|
831
|
+
exportedAt: (0, base_js_1.nowISO)(),
|
|
832
|
+
nodes,
|
|
833
|
+
edges,
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
async clearAll() {
|
|
837
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
838
|
+
// Get all triples and delete them
|
|
839
|
+
const allTriples = await this.get({});
|
|
840
|
+
if (allTriples.length > 0) {
|
|
841
|
+
await this.del(allTriples);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
845
|
+
// Analytics
|
|
846
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
847
|
+
async getStats() {
|
|
848
|
+
(0, base_js_1.requireConnection)(this._connected);
|
|
849
|
+
const nodes = await this.findSkills({});
|
|
850
|
+
const edges = await this.findPrerequisites({});
|
|
851
|
+
return (0, base_js_1.calculateStats)(nodes, edges);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
exports.LevelGraphStorage = LevelGraphStorage;
|
|
855
|
+
//# sourceMappingURL=levelgraph.js.map
|