@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the 'Software'), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # @lorion-org/composition-graph
2
+
3
+ Framework-free descriptor catalogs, relation graphs, and composition selection logic.
4
+
5
+ This package models flat named descriptors, their declared relations, optional base descriptors, and deterministic composition flows.
6
+
7
+ ## Install
8
+
9
+ ```shell
10
+ pnpm add @lorion-org/composition-graph
11
+ ```
12
+
13
+ ## What it is
14
+
15
+ - a typed descriptor model for flat descriptors
16
+ - a graph builder for declared relations
17
+ - a catalog for querying profiles and relation paths
18
+ - a selection layer for resolving selected and base descriptors into a final set
19
+
20
+ ## What it is not
21
+
22
+ - not a package manager
23
+ - not a filesystem discovery tool
24
+ - not a framework adapter
25
+ - not a runtime-config loader
26
+
27
+ ## Basic example
28
+
29
+ ```ts
30
+ import { createDescriptorCatalog } from '@lorion-org/composition-graph';
31
+
32
+ const catalog = createDescriptorCatalog({
33
+ relationDescriptors: [
34
+ {
35
+ id: 'integrations',
36
+ field: 'integrations',
37
+ },
38
+ ],
39
+ descriptors: [
40
+ {
41
+ id: 'billing',
42
+ version: '1.0.0',
43
+ dependencies: { storage: '*' },
44
+ integrations: { analytics: '*' },
45
+ },
46
+ {
47
+ id: 'storage',
48
+ version: '1.0.0',
49
+ },
50
+ {
51
+ id: 'analytics',
52
+ version: '1.0.0',
53
+ },
54
+ {
55
+ id: 'web-shell',
56
+ version: '1.0.0',
57
+ dependencies: { router: '*' },
58
+ },
59
+ {
60
+ id: 'router',
61
+ version: '1.0.0',
62
+ },
63
+ ],
64
+ });
65
+
66
+ const selection = catalog.resolveSelection({
67
+ selected: ['billing'],
68
+ baseDescriptors: ['web-shell'],
69
+ });
70
+
71
+ console.log(selection.getResolved());
72
+ console.log(selection.getProvenance());
73
+ ```
74
+
75
+ ## Example: explicit base descriptors
76
+
77
+ Use `baseDescriptors` for descriptors that always participate in the
78
+ resolution for a given runtime, deployment, or application shell.
79
+
80
+ ```ts
81
+ import { createDescriptorCatalog } from '@lorion-org/composition-graph';
82
+
83
+ const catalog = createDescriptorCatalog({
84
+ descriptors: [
85
+ {
86
+ id: 'billing',
87
+ version: '1.0.0',
88
+ dependencies: { storage: '*' },
89
+ },
90
+ {
91
+ id: 'storage',
92
+ version: '1.0.0',
93
+ },
94
+ {
95
+ id: 'web-shell',
96
+ version: '1.0.0',
97
+ dependencies: { router: '*' },
98
+ },
99
+ {
100
+ id: 'router',
101
+ version: '1.0.0',
102
+ },
103
+ ],
104
+ });
105
+
106
+ const selection = catalog.resolveSelection({
107
+ selected: ['billing'],
108
+ baseDescriptors: ['web-shell'],
109
+ });
110
+
111
+ selection.getResolved();
112
+ // => ['billing', 'router', 'storage', 'web-shell']
113
+ ```
114
+
115
+ ## Example: explain why something is present
116
+
117
+ The catalog and selection APIs expose path explanation helpers for diagnostics,
118
+ admin UIs, and composition debugging.
119
+
120
+ ```ts
121
+ import { createDescriptorCatalog } from '@lorion-org/composition-graph';
122
+
123
+ const catalog = createDescriptorCatalog({
124
+ descriptors: [
125
+ {
126
+ id: 'billing',
127
+ version: '1.0.0',
128
+ dependencies: { storage: '*' },
129
+ },
130
+ {
131
+ id: 'storage',
132
+ version: '1.0.0',
133
+ dependencies: { queue: '*' },
134
+ },
135
+ {
136
+ id: 'queue',
137
+ version: '1.0.0',
138
+ },
139
+ ],
140
+ });
141
+
142
+ catalog.explain({
143
+ from: 'billing',
144
+ to: 'queue',
145
+ relationIds: ['dependencies'],
146
+ });
147
+ // => [
148
+ // { from: 'billing', to: 'storage', relation: 'dependencies' },
149
+ // { from: 'storage', to: 'queue', relation: 'dependencies' },
150
+ // ]
151
+ ```
152
+
153
+ ## Example: combine with descriptor discovery
154
+
155
+ `@lorion-org/composition-graph` expects flat descriptors. If your authoring format
156
+ allows nested descriptor documents, flatten them before building the catalog.
157
+
158
+ ```ts
159
+ import { createDescriptorCatalog } from '@lorion-org/composition-graph';
160
+ import { discoverDescriptors } from '@lorion-org/descriptor-discovery';
161
+
162
+ const discovered = discoverDescriptors({
163
+ roots: ['./descriptors'],
164
+ descriptorFileName: 'descriptor.json',
165
+ idField: 'name',
166
+ nestedField: 'bundles',
167
+ });
168
+
169
+ const catalog = createDescriptorCatalog({
170
+ descriptors: discovered.map((entry) => entry.descriptor),
171
+ });
172
+ ```
173
+
174
+ ## Relations
175
+
176
+ - `dependencies` is the only built-in relation
177
+ - every additional relation must be registered via `relationDescriptors`
178
+ - unconfigured descriptor fields are ignored by the graph
179
+ - string parsing for user-facing selection input belongs in a higher adapter layer
180
+ - nested descriptor authoring belongs in a discovery or normalization layer, not in this package
181
+
182
+ `relationDescriptors` are intentionally small:
183
+
184
+ - `id` identifies the relation in graph queries and policies
185
+ - `field` optionally maps the relation to a descriptor field name
186
+
187
+ The core graph does not prescribe directional interpretation, hinting, or
188
+ weighting. Those concerns belong in higher layers.
189
+
190
+ Dependency-specific projections are also intentionally outside the core. If a
191
+ consumer needs a "what pulled this in?" view, derive that from
192
+ `getProvenance()` in its own adapter or UI layer.
193
+
194
+ ## Local commands
195
+
196
+ ```shell
197
+ cd packages/composition-graph
198
+ pnpm build
199
+ pnpm test
200
+ pnpm coverage
201
+ pnpm typecheck
202
+ pnpm package:check
203
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,442 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ assertKnownDescriptorIds: () => assertKnownDescriptorIds,
24
+ buildDescriptorGraph: () => buildDescriptorGraph,
25
+ buildDescriptorMap: () => buildDescriptorMap,
26
+ createCompositionSelection: () => createCompositionSelection,
27
+ createDescriptorCatalog: () => createDescriptorCatalog,
28
+ defaultCompositionPolicy: () => defaultCompositionPolicy,
29
+ defaultRelationDescriptors: () => defaultRelationDescriptors,
30
+ explainPath: () => explainPath,
31
+ explainPathsBatch: () => explainPathsBatch,
32
+ getCompositionProvenance: () => getCompositionProvenance,
33
+ getDependents: () => getDependents,
34
+ getIncomingRelationMap: () => getIncomingRelationMap,
35
+ getTransitiveTargets: () => getTransitiveTargets,
36
+ parseDescriptorIds: () => parseDescriptorIds
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/descriptorMap.ts
41
+ function buildDescriptorMap(descriptors) {
42
+ const descriptorMap = /* @__PURE__ */ new Map();
43
+ for (const descriptor of descriptors) {
44
+ descriptorMap.set(descriptor.id, descriptor);
45
+ }
46
+ return descriptorMap;
47
+ }
48
+ function parseDescriptorIds(input) {
49
+ if (!input) return [];
50
+ const items = Array.isArray(input) ? input : input.split(/[,\s]+/).map((item) => item.trim());
51
+ return Array.from(new Set(items.map((item) => item.trim()).filter(Boolean))).sort();
52
+ }
53
+ function assertKnownDescriptorIds(descriptorMap, ids, label) {
54
+ const missing = [...new Set(ids)].filter((id) => id && !descriptorMap.has(id)).sort();
55
+ if (!missing.length) return;
56
+ throw new Error(`Unknown ${label}: ${missing.join(", ")}`);
57
+ }
58
+
59
+ // src/descriptorGraph.ts
60
+ var createEmptyAdjacency = (descriptorMap) => {
61
+ return new Map(
62
+ Array.from(descriptorMap.keys()).sort().map((id) => [id, []])
63
+ );
64
+ };
65
+ var sortEdges = (edges) => {
66
+ return [...edges].sort(
67
+ (left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to) || left.relation.localeCompare(right.relation)
68
+ );
69
+ };
70
+ var defaultRelationDescriptors = [
71
+ {
72
+ id: "dependencies",
73
+ field: "dependencies"
74
+ }
75
+ ];
76
+ function isVersionConstraintMap(value) {
77
+ return typeof value === "object" && value !== null && !Array.isArray(value);
78
+ }
79
+ function getRelationTargets(descriptor, relationDescriptor) {
80
+ const field = relationDescriptor.field ?? relationDescriptor.id;
81
+ const relationValue = descriptor[field];
82
+ if (!isVersionConstraintMap(relationValue)) return [];
83
+ return Object.keys(relationValue).sort();
84
+ }
85
+ function createOriginType(path) {
86
+ return path.length ? "resolved" : "selected";
87
+ }
88
+ function compareOrigins(left, right) {
89
+ const leftPath = JSON.stringify(left.path);
90
+ const rightPath = JSON.stringify(right.path);
91
+ return left.originType.localeCompare(right.originType) || leftPath.localeCompare(rightPath);
92
+ }
93
+ function buildDescriptorGraph(input) {
94
+ const relationDescriptors = input.relationDescriptors ?? defaultRelationDescriptors;
95
+ const descriptorRegistry = new Map(
96
+ [...relationDescriptors].sort((left, right) => left.id.localeCompare(right.id)).map((descriptor) => [descriptor.id, descriptor])
97
+ );
98
+ const edges = [];
99
+ const outgoing = createEmptyAdjacency(input.descriptorMap);
100
+ const incoming = createEmptyAdjacency(input.descriptorMap);
101
+ for (const descriptor of Array.from(input.descriptorMap.values()).sort(
102
+ (left, right) => left.id.localeCompare(right.id)
103
+ )) {
104
+ for (const relationDescriptor of descriptorRegistry.values()) {
105
+ for (const target of getRelationTargets(descriptor, relationDescriptor)) {
106
+ if (!input.descriptorMap.has(target)) continue;
107
+ const edge = {
108
+ from: descriptor.id,
109
+ to: target,
110
+ relation: relationDescriptor.id,
111
+ source: "descriptor"
112
+ };
113
+ edges.push(edge);
114
+ outgoing.get(descriptor.id)?.push(edge);
115
+ incoming.get(target)?.push(edge);
116
+ }
117
+ }
118
+ }
119
+ for (const [id, nodeEdges] of outgoing) outgoing.set(id, sortEdges(nodeEdges));
120
+ for (const [id, nodeEdges] of incoming) incoming.set(id, sortEdges(nodeEdges));
121
+ return {
122
+ descriptorMap: input.descriptorMap,
123
+ edges: sortEdges(edges),
124
+ outgoing,
125
+ incoming,
126
+ relationDescriptors: descriptorRegistry
127
+ };
128
+ }
129
+ function getTransitiveTargets(input) {
130
+ const allowedRelations = new Set(input.relationIds);
131
+ const visited = /* @__PURE__ */ new Set();
132
+ const stack = [...input.start].sort().reverse();
133
+ while (stack.length) {
134
+ const current = stack.pop();
135
+ if (!current || visited.has(current)) continue;
136
+ visited.add(current);
137
+ for (const edge of input.graph.outgoing.get(current) ?? []) {
138
+ if (!allowedRelations.has(edge.relation) || visited.has(edge.to)) continue;
139
+ stack.push(edge.to);
140
+ }
141
+ }
142
+ return Array.from(visited).filter((id) => input.graph.descriptorMap.has(id)).sort();
143
+ }
144
+ function getIncomingRelationMap(input) {
145
+ const entries = Array.from(input.graph.incoming.entries()).map(([target, edges]) => {
146
+ const sources = edges.filter((edge) => edge.relation === input.relationId).map((edge) => edge.from).sort();
147
+ return [target, sources];
148
+ }).filter(([, sources]) => sources.length > 0).sort(([left], [right]) => left.localeCompare(right));
149
+ return new Map(entries);
150
+ }
151
+ function getDependents(input) {
152
+ if (!input.graph.descriptorMap.has(input.target)) return [];
153
+ const allowedRelations = new Set(input.relationIds);
154
+ const visited = /* @__PURE__ */ new Set();
155
+ const queue = [input.target];
156
+ while (queue.length) {
157
+ const current = queue.shift();
158
+ if (!current || visited.has(current)) continue;
159
+ visited.add(current);
160
+ if (input.transitive === false && current !== input.target) continue;
161
+ for (const edge of input.graph.incoming.get(current) ?? []) {
162
+ if (!allowedRelations.has(edge.relation) || visited.has(edge.from)) continue;
163
+ queue.push(edge.from);
164
+ }
165
+ }
166
+ visited.delete(input.target);
167
+ return Array.from(visited).sort();
168
+ }
169
+ function explainPath(input) {
170
+ if (!input.graph.descriptorMap.has(input.from) || !input.graph.descriptorMap.has(input.to))
171
+ return [];
172
+ if (input.from === input.to) return [];
173
+ const allowedRelations = new Set(input.relationIds);
174
+ const visited = /* @__PURE__ */ new Set([input.from]);
175
+ const queue = [
176
+ { node: input.from, path: [] }
177
+ ];
178
+ while (queue.length) {
179
+ const current = queue.shift();
180
+ if (!current) continue;
181
+ for (const edge of input.graph.outgoing.get(current.node) ?? []) {
182
+ if (!allowedRelations.has(edge.relation) || visited.has(edge.to)) continue;
183
+ const nextPath = [
184
+ ...current.path,
185
+ {
186
+ from: edge.from,
187
+ to: edge.to,
188
+ relation: edge.relation
189
+ }
190
+ ];
191
+ if (edge.to === input.to) return nextPath;
192
+ visited.add(edge.to);
193
+ queue.push({
194
+ node: edge.to,
195
+ path: nextPath
196
+ });
197
+ }
198
+ }
199
+ return [];
200
+ }
201
+ function explainPathsBatch(input) {
202
+ return [...input.pairs].sort((left, right) => left.from.localeCompare(right.from) || left.to.localeCompare(right.to)).map(({ from, to }) => ({
203
+ from,
204
+ to,
205
+ path: explainPath({
206
+ graph: input.graph,
207
+ from,
208
+ to,
209
+ relationIds: input.relationIds
210
+ })
211
+ }));
212
+ }
213
+ function getCompositionProvenance(input) {
214
+ const selected = [...new Set(input.selected)].filter((descriptorId) => input.graph.descriptorMap.has(descriptorId)).sort();
215
+ const baseDescriptors = [...new Set(input.baseDescriptors ?? [])].filter((descriptorId) => input.graph.descriptorMap.has(descriptorId)).sort();
216
+ const descriptorIds = [...new Set(input.descriptorIds)].filter((descriptorId) => input.graph.descriptorMap.has(descriptorId)).sort();
217
+ return descriptorIds.map((descriptorId) => {
218
+ const origins = [];
219
+ if (selected.includes(descriptorId)) {
220
+ origins.push({
221
+ originType: "selected",
222
+ path: []
223
+ });
224
+ }
225
+ if (baseDescriptors.includes(descriptorId)) {
226
+ origins.push({
227
+ originType: "base",
228
+ path: []
229
+ });
230
+ }
231
+ for (const source of [...selected, ...baseDescriptors]) {
232
+ if (source === descriptorId) continue;
233
+ const path = explainPath({
234
+ graph: input.graph,
235
+ from: source,
236
+ to: descriptorId,
237
+ relationIds: input.relationIds
238
+ });
239
+ if (!path.length) continue;
240
+ origins.push({
241
+ originType: createOriginType(path),
242
+ path
243
+ });
244
+ }
245
+ return {
246
+ descriptorId,
247
+ origins: origins.sort(compareOrigins)
248
+ };
249
+ });
250
+ }
251
+
252
+ // src/compositionSelection.ts
253
+ var defaultCompositionPolicy = {
254
+ resolutionRelationIds: ["dependencies"],
255
+ provenanceRelationIds: ["dependencies"],
256
+ inspectionRelationIds: ["dependencies"]
257
+ };
258
+ function resolvePolicy(policy) {
259
+ return {
260
+ ...defaultCompositionPolicy,
261
+ ...policy,
262
+ resolutionRelationIds: policy?.resolutionRelationIds ?? defaultCompositionPolicy.resolutionRelationIds,
263
+ provenanceRelationIds: policy?.provenanceRelationIds ?? defaultCompositionPolicy.provenanceRelationIds,
264
+ inspectionRelationIds: policy?.inspectionRelationIds ?? defaultCompositionPolicy.inspectionRelationIds
265
+ };
266
+ }
267
+ function createCompositionSelection(input) {
268
+ const catalog = input.catalog;
269
+ const descriptorMap = catalog.getDescriptorMap();
270
+ const selected = [...new Set(input.selected ?? [])].filter(Boolean).sort();
271
+ const baseDescriptors = [...new Set(input.baseDescriptors ?? [])].filter(Boolean).sort();
272
+ assertKnownDescriptorIds(descriptorMap, selected, "selected descriptors");
273
+ assertKnownDescriptorIds(descriptorMap, baseDescriptors, "base descriptors");
274
+ const policy = resolvePolicy(input.policy);
275
+ const resolved = catalog.getTransitiveTargets({
276
+ start: [...selected, ...baseDescriptors],
277
+ relationIds: policy.resolutionRelationIds
278
+ });
279
+ let resolvedDescriptorsCache;
280
+ let provenanceCache;
281
+ const getResolvedDescriptors = () => {
282
+ if (!resolvedDescriptorsCache) {
283
+ resolvedDescriptorsCache = resolved.map((descriptorId) => descriptorMap.get(descriptorId)).filter((descriptor) => Boolean(descriptor));
284
+ }
285
+ return resolvedDescriptorsCache;
286
+ };
287
+ const getProvenance = () => {
288
+ if (!provenanceCache) {
289
+ provenanceCache = getCompositionProvenance({
290
+ graph: catalog.getGraph(),
291
+ descriptorIds: resolved,
292
+ selected,
293
+ baseDescriptors,
294
+ relationIds: policy.provenanceRelationIds
295
+ });
296
+ }
297
+ return provenanceCache;
298
+ };
299
+ return {
300
+ getCatalog: () => catalog,
301
+ getGraph: () => catalog.getGraph(),
302
+ getSelected: () => [...selected],
303
+ getBaseDescriptors: () => [...baseDescriptors],
304
+ getResolved: () => [...resolved],
305
+ getResolvedDescriptors,
306
+ getProvenance,
307
+ getDependentsFor: (target, dependentsInput = {}) => {
308
+ return catalog.getDependents({
309
+ target,
310
+ relationIds: dependentsInput.relationIds ?? policy.resolutionRelationIds,
311
+ ...dependentsInput.transitive !== void 0 ? { transitive: dependentsInput.transitive } : {}
312
+ });
313
+ },
314
+ explain: (explainInput) => {
315
+ return catalog.explain({
316
+ from: explainInput.from,
317
+ to: explainInput.to,
318
+ relationIds: explainInput.relationIds ?? policy.inspectionRelationIds
319
+ });
320
+ },
321
+ explainPathsBatch: (batchInput) => {
322
+ return catalog.explainPathsBatch({
323
+ pairs: batchInput.pairs,
324
+ relationIds: batchInput.relationIds ?? policy.inspectionRelationIds
325
+ });
326
+ }
327
+ };
328
+ }
329
+
330
+ // src/descriptorCatalog.ts
331
+ function createRelationRecord(relationIds, values) {
332
+ return Object.fromEntries(
333
+ relationIds.map((relationId) => {
334
+ const entry = values.find(([relation]) => relation === relationId);
335
+ return [relationId, entry?.[1] ?? []];
336
+ })
337
+ );
338
+ }
339
+ function resolveRelationDescriptors(relationDescriptors) {
340
+ const registry = /* @__PURE__ */ new Map();
341
+ for (const relationDescriptor of defaultRelationDescriptors) {
342
+ registry.set(relationDescriptor.id, relationDescriptor);
343
+ }
344
+ for (const relationDescriptor of relationDescriptors ?? []) {
345
+ registry.set(relationDescriptor.id, relationDescriptor);
346
+ }
347
+ return Array.from(registry.values()).sort((left, right) => left.id.localeCompare(right.id));
348
+ }
349
+ function buildDescriptorProfile(descriptor, relationIds, graph) {
350
+ const outgoing = createRelationRecord(
351
+ relationIds,
352
+ relationIds.map((relationId) => {
353
+ const targets = (graph.outgoing.get(descriptor.id) ?? []).filter((edge) => edge.relation === relationId).map((edge) => edge.to).sort();
354
+ return [relationId, targets];
355
+ })
356
+ );
357
+ const incoming = createRelationRecord(
358
+ relationIds,
359
+ relationIds.map((relationId) => {
360
+ const sources = (graph.incoming.get(descriptor.id) ?? []).filter((edge) => edge.relation === relationId).map((edge) => edge.from).sort();
361
+ return [relationId, sources];
362
+ })
363
+ );
364
+ return {
365
+ id: descriptor.id,
366
+ disabled: descriptor.disabled === true,
367
+ capabilities: [...descriptor.capabilities ?? []].sort(),
368
+ ...descriptor.location ? { location: descriptor.location } : {},
369
+ ...typeof descriptor.providesFor === "string" ? { providesFor: descriptor.providesFor } : {},
370
+ outgoing,
371
+ incoming
372
+ };
373
+ }
374
+ function createDescriptorCatalog(input) {
375
+ const descriptorMap = input.descriptorMap ?? buildDescriptorMap(input.descriptors ?? []);
376
+ const relationDescriptors = resolveRelationDescriptors(input.relationDescriptors);
377
+ const graph = buildDescriptorGraph({
378
+ descriptorMap,
379
+ relationDescriptors
380
+ });
381
+ const catalog = {
382
+ getDescriptorMap: () => descriptorMap,
383
+ getAllDescriptors: () => Array.from(descriptorMap.values()),
384
+ getDescriptor: (id) => descriptorMap.get(id),
385
+ getGraph: () => graph,
386
+ getRelationDescriptors: () => Array.from(graph.relationDescriptors.values()),
387
+ getProfiles: (profileInput = {}) => {
388
+ const ids = [...new Set(profileInput.ids ?? [])].filter(Boolean).sort();
389
+ const descriptors = ids.length ? ids.map((id) => descriptorMap.get(id)).filter((descriptor) => Boolean(descriptor)) : Array.from(descriptorMap.values()).filter(
390
+ (descriptor) => profileInput.includeDisabled === true || descriptor.disabled !== true
391
+ ).sort((left, right) => left.id.localeCompare(right.id));
392
+ return descriptors.map(
393
+ (descriptor) => buildDescriptorProfile(descriptor, Array.from(graph.relationDescriptors.keys()), graph)
394
+ );
395
+ },
396
+ getIncomingRelationMap: (relationId) => getIncomingRelationMap({ graph, relationId }),
397
+ getTransitiveTargets: (transitiveInput) => getTransitiveTargets({
398
+ graph,
399
+ start: transitiveInput.start,
400
+ relationIds: transitiveInput.relationIds
401
+ }),
402
+ getDependents: (dependentsInput) => getDependents({
403
+ graph,
404
+ target: dependentsInput.target,
405
+ relationIds: dependentsInput.relationIds,
406
+ ...dependentsInput.transitive !== void 0 ? { transitive: dependentsInput.transitive } : {}
407
+ }),
408
+ explain: (explainInput) => explainPath({
409
+ graph,
410
+ from: explainInput.from,
411
+ to: explainInput.to,
412
+ relationIds: explainInput.relationIds
413
+ }),
414
+ explainPathsBatch: (batchInput) => explainPathsBatch({
415
+ graph,
416
+ pairs: batchInput.pairs,
417
+ relationIds: batchInput.relationIds
418
+ }),
419
+ resolveSelection: (selectionInput) => createCompositionSelection({
420
+ catalog,
421
+ ...selectionInput
422
+ })
423
+ };
424
+ return catalog;
425
+ }
426
+ // Annotate the CommonJS export names for ESM import in node:
427
+ 0 && (module.exports = {
428
+ assertKnownDescriptorIds,
429
+ buildDescriptorGraph,
430
+ buildDescriptorMap,
431
+ createCompositionSelection,
432
+ createDescriptorCatalog,
433
+ defaultCompositionPolicy,
434
+ defaultRelationDescriptors,
435
+ explainPath,
436
+ explainPathsBatch,
437
+ getCompositionProvenance,
438
+ getDependents,
439
+ getIncomingRelationMap,
440
+ getTransitiveTargets,
441
+ parseDescriptorIds
442
+ });