@lorion-org/composition-graph 1.0.0-beta.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 +21 -0
- package/README.md +203 -0
- package/dist/index.cjs +442 -0
- package/dist/index.d.cts +197 -0
- package/dist/index.d.ts +197 -0
- package/dist/index.js +402 -0
- package/examples/deployment-composition.js +43 -0
- package/examples/deployment-composition.ts +52 -0
- package/package.json +59 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
// src/descriptorMap.ts
|
|
2
|
+
function buildDescriptorMap(descriptors) {
|
|
3
|
+
const descriptorMap = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const descriptor of descriptors) {
|
|
5
|
+
descriptorMap.set(descriptor.id, descriptor);
|
|
6
|
+
}
|
|
7
|
+
return descriptorMap;
|
|
8
|
+
}
|
|
9
|
+
function parseDescriptorIds(input) {
|
|
10
|
+
if (!input) return [];
|
|
11
|
+
const items = Array.isArray(input) ? input : input.split(/[,\s]+/).map((item) => item.trim());
|
|
12
|
+
return Array.from(new Set(items.map((item) => item.trim()).filter(Boolean))).sort();
|
|
13
|
+
}
|
|
14
|
+
function assertKnownDescriptorIds(descriptorMap, ids, label) {
|
|
15
|
+
const missing = [...new Set(ids)].filter((id) => id && !descriptorMap.has(id)).sort();
|
|
16
|
+
if (!missing.length) return;
|
|
17
|
+
throw new Error(`Unknown ${label}: ${missing.join(", ")}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/descriptorGraph.ts
|
|
21
|
+
var createEmptyAdjacency = (descriptorMap) => {
|
|
22
|
+
return new Map(
|
|
23
|
+
Array.from(descriptorMap.keys()).sort().map((id) => [id, []])
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
var sortEdges = (edges) => {
|
|
27
|
+
return [...edges].sort(
|
|
28
|
+
(left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to) || left.relation.localeCompare(right.relation)
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
var defaultRelationDescriptors = [
|
|
32
|
+
{
|
|
33
|
+
id: "dependencies",
|
|
34
|
+
field: "dependencies"
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
function isVersionConstraintMap(value) {
|
|
38
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
function getRelationTargets(descriptor, relationDescriptor) {
|
|
41
|
+
const field = relationDescriptor.field ?? relationDescriptor.id;
|
|
42
|
+
const relationValue = descriptor[field];
|
|
43
|
+
if (!isVersionConstraintMap(relationValue)) return [];
|
|
44
|
+
return Object.keys(relationValue).sort();
|
|
45
|
+
}
|
|
46
|
+
function createOriginType(path) {
|
|
47
|
+
return path.length ? "resolved" : "selected";
|
|
48
|
+
}
|
|
49
|
+
function compareOrigins(left, right) {
|
|
50
|
+
const leftPath = JSON.stringify(left.path);
|
|
51
|
+
const rightPath = JSON.stringify(right.path);
|
|
52
|
+
return left.originType.localeCompare(right.originType) || leftPath.localeCompare(rightPath);
|
|
53
|
+
}
|
|
54
|
+
function buildDescriptorGraph(input) {
|
|
55
|
+
const relationDescriptors = input.relationDescriptors ?? defaultRelationDescriptors;
|
|
56
|
+
const descriptorRegistry = new Map(
|
|
57
|
+
[...relationDescriptors].sort((left, right) => left.id.localeCompare(right.id)).map((descriptor) => [descriptor.id, descriptor])
|
|
58
|
+
);
|
|
59
|
+
const edges = [];
|
|
60
|
+
const outgoing = createEmptyAdjacency(input.descriptorMap);
|
|
61
|
+
const incoming = createEmptyAdjacency(input.descriptorMap);
|
|
62
|
+
for (const descriptor of Array.from(input.descriptorMap.values()).sort(
|
|
63
|
+
(left, right) => left.id.localeCompare(right.id)
|
|
64
|
+
)) {
|
|
65
|
+
for (const relationDescriptor of descriptorRegistry.values()) {
|
|
66
|
+
for (const target of getRelationTargets(descriptor, relationDescriptor)) {
|
|
67
|
+
if (!input.descriptorMap.has(target)) continue;
|
|
68
|
+
const edge = {
|
|
69
|
+
from: descriptor.id,
|
|
70
|
+
to: target,
|
|
71
|
+
relation: relationDescriptor.id,
|
|
72
|
+
source: "descriptor"
|
|
73
|
+
};
|
|
74
|
+
edges.push(edge);
|
|
75
|
+
outgoing.get(descriptor.id)?.push(edge);
|
|
76
|
+
incoming.get(target)?.push(edge);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const [id, nodeEdges] of outgoing) outgoing.set(id, sortEdges(nodeEdges));
|
|
81
|
+
for (const [id, nodeEdges] of incoming) incoming.set(id, sortEdges(nodeEdges));
|
|
82
|
+
return {
|
|
83
|
+
descriptorMap: input.descriptorMap,
|
|
84
|
+
edges: sortEdges(edges),
|
|
85
|
+
outgoing,
|
|
86
|
+
incoming,
|
|
87
|
+
relationDescriptors: descriptorRegistry
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function getTransitiveTargets(input) {
|
|
91
|
+
const allowedRelations = new Set(input.relationIds);
|
|
92
|
+
const visited = /* @__PURE__ */ new Set();
|
|
93
|
+
const stack = [...input.start].sort().reverse();
|
|
94
|
+
while (stack.length) {
|
|
95
|
+
const current = stack.pop();
|
|
96
|
+
if (!current || visited.has(current)) continue;
|
|
97
|
+
visited.add(current);
|
|
98
|
+
for (const edge of input.graph.outgoing.get(current) ?? []) {
|
|
99
|
+
if (!allowedRelations.has(edge.relation) || visited.has(edge.to)) continue;
|
|
100
|
+
stack.push(edge.to);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return Array.from(visited).filter((id) => input.graph.descriptorMap.has(id)).sort();
|
|
104
|
+
}
|
|
105
|
+
function getIncomingRelationMap(input) {
|
|
106
|
+
const entries = Array.from(input.graph.incoming.entries()).map(([target, edges]) => {
|
|
107
|
+
const sources = edges.filter((edge) => edge.relation === input.relationId).map((edge) => edge.from).sort();
|
|
108
|
+
return [target, sources];
|
|
109
|
+
}).filter(([, sources]) => sources.length > 0).sort(([left], [right]) => left.localeCompare(right));
|
|
110
|
+
return new Map(entries);
|
|
111
|
+
}
|
|
112
|
+
function getDependents(input) {
|
|
113
|
+
if (!input.graph.descriptorMap.has(input.target)) return [];
|
|
114
|
+
const allowedRelations = new Set(input.relationIds);
|
|
115
|
+
const visited = /* @__PURE__ */ new Set();
|
|
116
|
+
const queue = [input.target];
|
|
117
|
+
while (queue.length) {
|
|
118
|
+
const current = queue.shift();
|
|
119
|
+
if (!current || visited.has(current)) continue;
|
|
120
|
+
visited.add(current);
|
|
121
|
+
if (input.transitive === false && current !== input.target) continue;
|
|
122
|
+
for (const edge of input.graph.incoming.get(current) ?? []) {
|
|
123
|
+
if (!allowedRelations.has(edge.relation) || visited.has(edge.from)) continue;
|
|
124
|
+
queue.push(edge.from);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
visited.delete(input.target);
|
|
128
|
+
return Array.from(visited).sort();
|
|
129
|
+
}
|
|
130
|
+
function explainPath(input) {
|
|
131
|
+
if (!input.graph.descriptorMap.has(input.from) || !input.graph.descriptorMap.has(input.to))
|
|
132
|
+
return [];
|
|
133
|
+
if (input.from === input.to) return [];
|
|
134
|
+
const allowedRelations = new Set(input.relationIds);
|
|
135
|
+
const visited = /* @__PURE__ */ new Set([input.from]);
|
|
136
|
+
const queue = [
|
|
137
|
+
{ node: input.from, path: [] }
|
|
138
|
+
];
|
|
139
|
+
while (queue.length) {
|
|
140
|
+
const current = queue.shift();
|
|
141
|
+
if (!current) continue;
|
|
142
|
+
for (const edge of input.graph.outgoing.get(current.node) ?? []) {
|
|
143
|
+
if (!allowedRelations.has(edge.relation) || visited.has(edge.to)) continue;
|
|
144
|
+
const nextPath = [
|
|
145
|
+
...current.path,
|
|
146
|
+
{
|
|
147
|
+
from: edge.from,
|
|
148
|
+
to: edge.to,
|
|
149
|
+
relation: edge.relation
|
|
150
|
+
}
|
|
151
|
+
];
|
|
152
|
+
if (edge.to === input.to) return nextPath;
|
|
153
|
+
visited.add(edge.to);
|
|
154
|
+
queue.push({
|
|
155
|
+
node: edge.to,
|
|
156
|
+
path: nextPath
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
function explainPathsBatch(input) {
|
|
163
|
+
return [...input.pairs].sort((left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to)).map(({ from, to }) => ({
|
|
164
|
+
from,
|
|
165
|
+
to,
|
|
166
|
+
path: explainPath({
|
|
167
|
+
graph: input.graph,
|
|
168
|
+
from,
|
|
169
|
+
to,
|
|
170
|
+
relationIds: input.relationIds
|
|
171
|
+
})
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
function getCompositionProvenance(input) {
|
|
175
|
+
const selected = [...new Set(input.selected)].filter((descriptorId) => input.graph.descriptorMap.has(descriptorId)).sort();
|
|
176
|
+
const baseDescriptors = [...new Set(input.baseDescriptors ?? [])].filter((descriptorId) => input.graph.descriptorMap.has(descriptorId)).sort();
|
|
177
|
+
const descriptorIds = [...new Set(input.descriptorIds)].filter((descriptorId) => input.graph.descriptorMap.has(descriptorId)).sort();
|
|
178
|
+
return descriptorIds.map((descriptorId) => {
|
|
179
|
+
const origins = [];
|
|
180
|
+
if (selected.includes(descriptorId)) {
|
|
181
|
+
origins.push({
|
|
182
|
+
originType: "selected",
|
|
183
|
+
path: []
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (baseDescriptors.includes(descriptorId)) {
|
|
187
|
+
origins.push({
|
|
188
|
+
originType: "base",
|
|
189
|
+
path: []
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
for (const source of [...selected, ...baseDescriptors]) {
|
|
193
|
+
if (source === descriptorId) continue;
|
|
194
|
+
const path = explainPath({
|
|
195
|
+
graph: input.graph,
|
|
196
|
+
from: source,
|
|
197
|
+
to: descriptorId,
|
|
198
|
+
relationIds: input.relationIds
|
|
199
|
+
});
|
|
200
|
+
if (!path.length) continue;
|
|
201
|
+
origins.push({
|
|
202
|
+
originType: createOriginType(path),
|
|
203
|
+
path
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
descriptorId,
|
|
208
|
+
origins: origins.sort(compareOrigins)
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/compositionSelection.ts
|
|
214
|
+
var defaultCompositionPolicy = {
|
|
215
|
+
resolutionRelationIds: ["dependencies"],
|
|
216
|
+
provenanceRelationIds: ["dependencies"],
|
|
217
|
+
inspectionRelationIds: ["dependencies"]
|
|
218
|
+
};
|
|
219
|
+
function resolvePolicy(policy) {
|
|
220
|
+
return {
|
|
221
|
+
...defaultCompositionPolicy,
|
|
222
|
+
...policy,
|
|
223
|
+
resolutionRelationIds: policy?.resolutionRelationIds ?? defaultCompositionPolicy.resolutionRelationIds,
|
|
224
|
+
provenanceRelationIds: policy?.provenanceRelationIds ?? defaultCompositionPolicy.provenanceRelationIds,
|
|
225
|
+
inspectionRelationIds: policy?.inspectionRelationIds ?? defaultCompositionPolicy.inspectionRelationIds
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function createCompositionSelection(input) {
|
|
229
|
+
const catalog = input.catalog;
|
|
230
|
+
const descriptorMap = catalog.getDescriptorMap();
|
|
231
|
+
const selected = [...new Set(input.selected ?? [])].filter(Boolean).sort();
|
|
232
|
+
const baseDescriptors = [...new Set(input.baseDescriptors ?? [])].filter(Boolean).sort();
|
|
233
|
+
assertKnownDescriptorIds(descriptorMap, selected, "selected descriptors");
|
|
234
|
+
assertKnownDescriptorIds(descriptorMap, baseDescriptors, "base descriptors");
|
|
235
|
+
const policy = resolvePolicy(input.policy);
|
|
236
|
+
const resolved = catalog.getTransitiveTargets({
|
|
237
|
+
start: [...selected, ...baseDescriptors],
|
|
238
|
+
relationIds: policy.resolutionRelationIds
|
|
239
|
+
});
|
|
240
|
+
let resolvedDescriptorsCache;
|
|
241
|
+
let provenanceCache;
|
|
242
|
+
const getResolvedDescriptors = () => {
|
|
243
|
+
if (!resolvedDescriptorsCache) {
|
|
244
|
+
resolvedDescriptorsCache = resolved.map((descriptorId) => descriptorMap.get(descriptorId)).filter((descriptor) => Boolean(descriptor));
|
|
245
|
+
}
|
|
246
|
+
return resolvedDescriptorsCache;
|
|
247
|
+
};
|
|
248
|
+
const getProvenance = () => {
|
|
249
|
+
if (!provenanceCache) {
|
|
250
|
+
provenanceCache = getCompositionProvenance({
|
|
251
|
+
graph: catalog.getGraph(),
|
|
252
|
+
descriptorIds: resolved,
|
|
253
|
+
selected,
|
|
254
|
+
baseDescriptors,
|
|
255
|
+
relationIds: policy.provenanceRelationIds
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return provenanceCache;
|
|
259
|
+
};
|
|
260
|
+
return {
|
|
261
|
+
getCatalog: () => catalog,
|
|
262
|
+
getGraph: () => catalog.getGraph(),
|
|
263
|
+
getSelected: () => [...selected],
|
|
264
|
+
getBaseDescriptors: () => [...baseDescriptors],
|
|
265
|
+
getResolved: () => [...resolved],
|
|
266
|
+
getResolvedDescriptors,
|
|
267
|
+
getProvenance,
|
|
268
|
+
getDependentsFor: (target, dependentsInput = {}) => {
|
|
269
|
+
return catalog.getDependents({
|
|
270
|
+
target,
|
|
271
|
+
relationIds: dependentsInput.relationIds ?? policy.resolutionRelationIds,
|
|
272
|
+
...dependentsInput.transitive !== void 0 ? { transitive: dependentsInput.transitive } : {}
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
explain: (explainInput) => {
|
|
276
|
+
return catalog.explain({
|
|
277
|
+
from: explainInput.from,
|
|
278
|
+
to: explainInput.to,
|
|
279
|
+
relationIds: explainInput.relationIds ?? policy.inspectionRelationIds
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
explainPathsBatch: (batchInput) => {
|
|
283
|
+
return catalog.explainPathsBatch({
|
|
284
|
+
pairs: batchInput.pairs,
|
|
285
|
+
relationIds: batchInput.relationIds ?? policy.inspectionRelationIds
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/descriptorCatalog.ts
|
|
292
|
+
function createRelationRecord(relationIds, values) {
|
|
293
|
+
return Object.fromEntries(
|
|
294
|
+
relationIds.map((relationId) => {
|
|
295
|
+
const entry = values.find(([relation]) => relation === relationId);
|
|
296
|
+
return [relationId, entry?.[1] ?? []];
|
|
297
|
+
})
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
function resolveRelationDescriptors(relationDescriptors) {
|
|
301
|
+
const registry = /* @__PURE__ */ new Map();
|
|
302
|
+
for (const relationDescriptor of defaultRelationDescriptors) {
|
|
303
|
+
registry.set(relationDescriptor.id, relationDescriptor);
|
|
304
|
+
}
|
|
305
|
+
for (const relationDescriptor of relationDescriptors ?? []) {
|
|
306
|
+
registry.set(relationDescriptor.id, relationDescriptor);
|
|
307
|
+
}
|
|
308
|
+
return Array.from(registry.values()).sort((left, right) => left.id.localeCompare(right.id));
|
|
309
|
+
}
|
|
310
|
+
function buildDescriptorProfile(descriptor, relationIds, graph) {
|
|
311
|
+
const outgoing = createRelationRecord(
|
|
312
|
+
relationIds,
|
|
313
|
+
relationIds.map((relationId) => {
|
|
314
|
+
const targets = (graph.outgoing.get(descriptor.id) ?? []).filter((edge) => edge.relation === relationId).map((edge) => edge.to).sort();
|
|
315
|
+
return [relationId, targets];
|
|
316
|
+
})
|
|
317
|
+
);
|
|
318
|
+
const incoming = createRelationRecord(
|
|
319
|
+
relationIds,
|
|
320
|
+
relationIds.map((relationId) => {
|
|
321
|
+
const sources = (graph.incoming.get(descriptor.id) ?? []).filter((edge) => edge.relation === relationId).map((edge) => edge.from).sort();
|
|
322
|
+
return [relationId, sources];
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
return {
|
|
326
|
+
id: descriptor.id,
|
|
327
|
+
disabled: descriptor.disabled === true,
|
|
328
|
+
capabilities: [...descriptor.capabilities ?? []].sort(),
|
|
329
|
+
...descriptor.location ? { location: descriptor.location } : {},
|
|
330
|
+
...typeof descriptor.providesFor === "string" ? { providesFor: descriptor.providesFor } : {},
|
|
331
|
+
outgoing,
|
|
332
|
+
incoming
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function createDescriptorCatalog(input) {
|
|
336
|
+
const descriptorMap = input.descriptorMap ?? buildDescriptorMap(input.descriptors ?? []);
|
|
337
|
+
const relationDescriptors = resolveRelationDescriptors(input.relationDescriptors);
|
|
338
|
+
const graph = buildDescriptorGraph({
|
|
339
|
+
descriptorMap,
|
|
340
|
+
relationDescriptors
|
|
341
|
+
});
|
|
342
|
+
const catalog = {
|
|
343
|
+
getDescriptorMap: () => descriptorMap,
|
|
344
|
+
getAllDescriptors: () => Array.from(descriptorMap.values()),
|
|
345
|
+
getDescriptor: (id) => descriptorMap.get(id),
|
|
346
|
+
getGraph: () => graph,
|
|
347
|
+
getRelationDescriptors: () => Array.from(graph.relationDescriptors.values()),
|
|
348
|
+
getProfiles: (profileInput = {}) => {
|
|
349
|
+
const ids = [...new Set(profileInput.ids ?? [])].filter(Boolean).sort();
|
|
350
|
+
const descriptors = ids.length ? ids.map((id) => descriptorMap.get(id)).filter((descriptor) => Boolean(descriptor)) : Array.from(descriptorMap.values()).filter(
|
|
351
|
+
(descriptor) => profileInput.includeDisabled === true || descriptor.disabled !== true
|
|
352
|
+
).sort((left, right) => left.id.localeCompare(right.id));
|
|
353
|
+
return descriptors.map(
|
|
354
|
+
(descriptor) => buildDescriptorProfile(descriptor, Array.from(graph.relationDescriptors.keys()), graph)
|
|
355
|
+
);
|
|
356
|
+
},
|
|
357
|
+
getIncomingRelationMap: (relationId) => getIncomingRelationMap({ graph, relationId }),
|
|
358
|
+
getTransitiveTargets: (transitiveInput) => getTransitiveTargets({
|
|
359
|
+
graph,
|
|
360
|
+
start: transitiveInput.start,
|
|
361
|
+
relationIds: transitiveInput.relationIds
|
|
362
|
+
}),
|
|
363
|
+
getDependents: (dependentsInput) => getDependents({
|
|
364
|
+
graph,
|
|
365
|
+
target: dependentsInput.target,
|
|
366
|
+
relationIds: dependentsInput.relationIds,
|
|
367
|
+
...dependentsInput.transitive !== void 0 ? { transitive: dependentsInput.transitive } : {}
|
|
368
|
+
}),
|
|
369
|
+
explain: (explainInput) => explainPath({
|
|
370
|
+
graph,
|
|
371
|
+
from: explainInput.from,
|
|
372
|
+
to: explainInput.to,
|
|
373
|
+
relationIds: explainInput.relationIds
|
|
374
|
+
}),
|
|
375
|
+
explainPathsBatch: (batchInput) => explainPathsBatch({
|
|
376
|
+
graph,
|
|
377
|
+
pairs: batchInput.pairs,
|
|
378
|
+
relationIds: batchInput.relationIds
|
|
379
|
+
}),
|
|
380
|
+
resolveSelection: (selectionInput) => createCompositionSelection({
|
|
381
|
+
catalog,
|
|
382
|
+
...selectionInput
|
|
383
|
+
})
|
|
384
|
+
};
|
|
385
|
+
return catalog;
|
|
386
|
+
}
|
|
387
|
+
export {
|
|
388
|
+
assertKnownDescriptorIds,
|
|
389
|
+
buildDescriptorGraph,
|
|
390
|
+
buildDescriptorMap,
|
|
391
|
+
createCompositionSelection,
|
|
392
|
+
createDescriptorCatalog,
|
|
393
|
+
defaultCompositionPolicy,
|
|
394
|
+
defaultRelationDescriptors,
|
|
395
|
+
explainPath,
|
|
396
|
+
explainPathsBatch,
|
|
397
|
+
getCompositionProvenance,
|
|
398
|
+
getDependents,
|
|
399
|
+
getIncomingRelationMap,
|
|
400
|
+
getTransitiveTargets,
|
|
401
|
+
parseDescriptorIds
|
|
402
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* eslint-env node */
|
|
2
|
+
import { createDescriptorCatalog } from '../dist/index.js';
|
|
3
|
+
|
|
4
|
+
const catalog = createDescriptorCatalog({
|
|
5
|
+
relationDescriptors: [
|
|
6
|
+
{
|
|
7
|
+
id: 'integrations',
|
|
8
|
+
field: 'integrations',
|
|
9
|
+
},
|
|
10
|
+
],
|
|
11
|
+
descriptors: [
|
|
12
|
+
{
|
|
13
|
+
id: 'billing',
|
|
14
|
+
version: '1.0.0',
|
|
15
|
+
dependencies: { storage: '*' },
|
|
16
|
+
integrations: { analytics: '*' },
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'storage',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'analytics',
|
|
24
|
+
version: '1.0.0',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'ui-shell',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
dependencies: { router: '*' },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'router',
|
|
33
|
+
version: '1.0.0',
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const selection = catalog.resolveSelection({
|
|
39
|
+
selected: ['billing'],
|
|
40
|
+
baseDescriptors: ['ui-shell'],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
process.stdout.write(`${selection.getResolved().join(', ')}\n`);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createDescriptorCatalog } from '@lorion-org/composition-graph';
|
|
2
|
+
|
|
3
|
+
const catalog = createDescriptorCatalog({
|
|
4
|
+
relationDescriptors: [
|
|
5
|
+
{
|
|
6
|
+
id: 'integrations',
|
|
7
|
+
field: 'integrations',
|
|
8
|
+
},
|
|
9
|
+
],
|
|
10
|
+
descriptors: [
|
|
11
|
+
{
|
|
12
|
+
id: 'billing',
|
|
13
|
+
version: '1.0.0',
|
|
14
|
+
dependencies: { storage: '*' },
|
|
15
|
+
integrations: { analytics: '*' },
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'storage',
|
|
19
|
+
version: '1.0.0',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'analytics',
|
|
23
|
+
version: '1.0.0',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'ui-shell',
|
|
27
|
+
version: '1.0.0',
|
|
28
|
+
dependencies: { router: '*' },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'router',
|
|
32
|
+
version: '1.0.0',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const selection = catalog.resolveSelection({
|
|
38
|
+
selected: ['billing'],
|
|
39
|
+
baseDescriptors: ['ui-shell'],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
console.log(selection.getResolved());
|
|
43
|
+
// ['analytics', 'billing', 'router', 'storage', 'ui-shell']
|
|
44
|
+
|
|
45
|
+
console.log(selection.getProvenance());
|
|
46
|
+
// [
|
|
47
|
+
// { descriptorId: 'analytics', origins: ['resolved'] },
|
|
48
|
+
// { descriptorId: 'billing', origins: ['selected'] },
|
|
49
|
+
// { descriptorId: 'router', origins: ['resolved'] },
|
|
50
|
+
// { descriptorId: 'storage', origins: ['resolved'] },
|
|
51
|
+
// { descriptorId: 'ui-shell', origins: ['base'] }
|
|
52
|
+
// ]
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lorion-org/composition-graph",
|
|
3
|
+
"version": "1.0.0-beta.0",
|
|
4
|
+
"description": "Framework-free descriptor catalogs, relation graphs, and composition selection logic.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/lorion-org/lorion.git",
|
|
9
|
+
"directory": "packages/composition-graph"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/lorion-org/lorion/tree/main/packages/composition-graph#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/lorion-org/lorion/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"require": {
|
|
30
|
+
"types": "./dist/index.d.cts",
|
|
31
|
+
"default": "./dist/index.cjs"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"examples",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"keywords": [
|
|
41
|
+
"composition",
|
|
42
|
+
"graph",
|
|
43
|
+
"descriptor",
|
|
44
|
+
"catalog"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
51
|
+
"clean": "rimraf coverage dist tsconfig.tsbuildinfo",
|
|
52
|
+
"example:composition": "node ./examples/deployment-composition.js",
|
|
53
|
+
"lint": "eslint src --ext .ts",
|
|
54
|
+
"test": "vitest run --root ../.. --config vitest.config.mts packages/composition-graph/src/index.spec.ts",
|
|
55
|
+
"coverage": "node -e \"require('node:fs').mkdirSync('../../coverage/.tmp', { recursive: true })\" && vitest run --coverage --coverage.include=packages/composition-graph/src/**/*.ts --root ../.. --config vitest.config.mts packages/composition-graph/src/index.spec.ts",
|
|
56
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
57
|
+
"package:check": "pnpm build && pnpm pack --dry-run && publint"
|
|
58
|
+
}
|
|
59
|
+
}
|