@lorion-org/composition-graph 1.0.0-beta.0 → 1.0.0-beta.2
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/README.md +101 -39
- package/dist/index.cjs +71 -12
- package/dist/index.d.cts +16 -4
- package/dist/index.d.ts +16 -4
- package/dist/index.js +69 -11
- package/examples/deployment-composition.js +22 -15
- package/examples/deployment-composition.ts +29 -21
- package/package.json +8 -2
- package/src/compositionSelection.ts +109 -0
- package/src/descriptorCatalog.ts +163 -0
- package/src/descriptorGraph.ts +327 -0
- package/src/descriptorMap.ts +127 -0
- package/src/index.ts +38 -0
- package/src/types.ts +133 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Descriptor, DescriptorId, DescriptorMap } from './types';
|
|
2
|
+
|
|
3
|
+
export type DescriptorSelectionSeedInput = {
|
|
4
|
+
argv?: string[];
|
|
5
|
+
cliKeys?: string[];
|
|
6
|
+
defaultValue?: DescriptorId[] | string;
|
|
7
|
+
env?: Record<string, string | undefined>;
|
|
8
|
+
envKeys?: string[];
|
|
9
|
+
key?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function buildDescriptorMap(descriptors: Iterable<Descriptor>): DescriptorMap {
|
|
13
|
+
const descriptorMap: DescriptorMap = new Map();
|
|
14
|
+
|
|
15
|
+
for (const descriptor of descriptors) {
|
|
16
|
+
descriptorMap.set(descriptor.id, descriptor);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return descriptorMap;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function parseDescriptorIds(input?: unknown): DescriptorId[] {
|
|
23
|
+
if (typeof input === 'string') {
|
|
24
|
+
return Array.from(
|
|
25
|
+
new Set(
|
|
26
|
+
input
|
|
27
|
+
.split(/[,\s]+/)
|
|
28
|
+
.map((item) => item.trim())
|
|
29
|
+
.filter(Boolean),
|
|
30
|
+
),
|
|
31
|
+
).sort();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!Array.isArray(input)) return [];
|
|
35
|
+
|
|
36
|
+
const items = input.flatMap((item) => (typeof item === 'string' ? parseDescriptorIds(item) : []));
|
|
37
|
+
|
|
38
|
+
return Array.from(new Set(items.map((item) => item.trim()).filter(Boolean))).sort();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readCliValue(argv: string[], key: string): string | undefined {
|
|
42
|
+
const arg = argv.find((entry) => entry === key || entry.startsWith(`${key}=`));
|
|
43
|
+
|
|
44
|
+
if (!arg) return undefined;
|
|
45
|
+
if (arg === key) {
|
|
46
|
+
const value = argv[argv.indexOf(arg) + 1];
|
|
47
|
+
return value?.startsWith('-') ? undefined : value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return arg.slice(`${key}=`.length);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function firstNonEmptyValue(
|
|
54
|
+
values: Array<string | string[] | undefined>,
|
|
55
|
+
): string | string[] | undefined {
|
|
56
|
+
return values.find((value) => {
|
|
57
|
+
if (typeof value === 'string') return value.trim().length > 0;
|
|
58
|
+
return Array.isArray(value) && value.some((entry) => typeof entry === 'string' && entry.trim());
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function pluralizeSelectionKeySegment(value: string): string {
|
|
63
|
+
if (value.endsWith('s')) return value;
|
|
64
|
+
if (value.endsWith('y')) return `${value.slice(0, -1)}ies`;
|
|
65
|
+
return `${value}s`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeSelectionKey(value: string): string {
|
|
69
|
+
const segments = value
|
|
70
|
+
.trim()
|
|
71
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
72
|
+
.replace(/[_\s]+/g, '-')
|
|
73
|
+
.toLowerCase()
|
|
74
|
+
.split('-')
|
|
75
|
+
.filter(Boolean);
|
|
76
|
+
|
|
77
|
+
if (!segments.length) return '';
|
|
78
|
+
|
|
79
|
+
const lastSegment = segments[segments.length - 1] as string;
|
|
80
|
+
segments[segments.length - 1] = pluralizeSelectionKeySegment(lastSegment);
|
|
81
|
+
|
|
82
|
+
return segments.join('-');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function toEnvKey(value: string): string {
|
|
86
|
+
return value.replaceAll('-', '_').toUpperCase();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveCliKeys(input: DescriptorSelectionSeedInput): string[] {
|
|
90
|
+
if (input.cliKeys) return input.cliKeys;
|
|
91
|
+
|
|
92
|
+
const key = input.key ? normalizeSelectionKey(input.key) : '';
|
|
93
|
+
return key ? [`--${key}`] : [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveEnvKeys(input: DescriptorSelectionSeedInput): string[] {
|
|
97
|
+
if (input.envKeys) return input.envKeys;
|
|
98
|
+
|
|
99
|
+
const key = input.key ? normalizeSelectionKey(input.key) : '';
|
|
100
|
+
return key ? [`npm_config_${key.replaceAll('-', '_')}`, `LORION_${toEnvKey(key)}`] : [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function resolveDescriptorSelectionSeed(
|
|
104
|
+
input: DescriptorSelectionSeedInput = {},
|
|
105
|
+
): DescriptorId[] {
|
|
106
|
+
return parseDescriptorIds(
|
|
107
|
+
firstNonEmptyValue([
|
|
108
|
+
...resolveCliKeys(input).map((key) => readCliValue(input.argv ?? [], key)),
|
|
109
|
+
...resolveEnvKeys(input).map((key) => input.env?.[key]),
|
|
110
|
+
input.defaultValue,
|
|
111
|
+
]),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function assertKnownDescriptorIds(
|
|
116
|
+
descriptorMap: DescriptorMap,
|
|
117
|
+
ids: DescriptorId[],
|
|
118
|
+
label: string,
|
|
119
|
+
): void {
|
|
120
|
+
const missing: DescriptorId[] = [...new Set(ids)]
|
|
121
|
+
.filter((id) => id && !descriptorMap.has(id))
|
|
122
|
+
.sort();
|
|
123
|
+
|
|
124
|
+
if (!missing.length) return;
|
|
125
|
+
|
|
126
|
+
throw new Error(`Unknown ${label}: ${missing.join(', ')}`);
|
|
127
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export {
|
|
2
|
+
assertKnownDescriptorIds,
|
|
3
|
+
buildDescriptorMap,
|
|
4
|
+
parseDescriptorIds,
|
|
5
|
+
resolveDescriptorSelectionSeed,
|
|
6
|
+
} from './descriptorMap';
|
|
7
|
+
export {
|
|
8
|
+
buildDescriptorGraph,
|
|
9
|
+
defaultRelationDescriptors,
|
|
10
|
+
explainPath,
|
|
11
|
+
explainPathsBatch,
|
|
12
|
+
getCompositionProvenance,
|
|
13
|
+
getDependents,
|
|
14
|
+
getIncomingRelationMap,
|
|
15
|
+
getTransitiveTargets,
|
|
16
|
+
} from './descriptorGraph';
|
|
17
|
+
export { createDescriptorCatalog } from './descriptorCatalog';
|
|
18
|
+
export { createCompositionSelection, defaultCompositionPolicy } from './compositionSelection';
|
|
19
|
+
export type {
|
|
20
|
+
CompositionOriginType,
|
|
21
|
+
CompositionPolicy,
|
|
22
|
+
CompositionProvenance,
|
|
23
|
+
CompositionProvenanceOrigin,
|
|
24
|
+
CompositionSelection,
|
|
25
|
+
Descriptor,
|
|
26
|
+
DescriptorCatalog,
|
|
27
|
+
DescriptorEdge,
|
|
28
|
+
DescriptorGraph,
|
|
29
|
+
DescriptorId,
|
|
30
|
+
DescriptorIds,
|
|
31
|
+
DescriptorMap,
|
|
32
|
+
DescriptorProfile,
|
|
33
|
+
RelationDescriptor,
|
|
34
|
+
RelationId,
|
|
35
|
+
ResolutionStep,
|
|
36
|
+
VersionConstraintMap,
|
|
37
|
+
} from './types';
|
|
38
|
+
export type { DescriptorSelectionSeedInput } from './descriptorMap';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export type DescriptorId = string;
|
|
2
|
+
|
|
3
|
+
export type DescriptorIds = DescriptorId[];
|
|
4
|
+
|
|
5
|
+
export type VersionConstraintMap = Record<DescriptorId, string>;
|
|
6
|
+
|
|
7
|
+
export type RelationId = string;
|
|
8
|
+
|
|
9
|
+
export type Descriptor = {
|
|
10
|
+
id: DescriptorId;
|
|
11
|
+
version: string;
|
|
12
|
+
providesFor?: DescriptorId | DescriptorId[];
|
|
13
|
+
defaultFor?: DescriptorId | DescriptorId[];
|
|
14
|
+
capabilities?: string[];
|
|
15
|
+
dependencies?: VersionConstraintMap;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
location?: string;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type DescriptorMap = Map<DescriptorId, Descriptor>;
|
|
22
|
+
|
|
23
|
+
export type RelationDescriptor = {
|
|
24
|
+
direction?: 'outgoing' | 'incoming';
|
|
25
|
+
id: RelationId;
|
|
26
|
+
field?: string;
|
|
27
|
+
targetMode?: 'keys' | 'values';
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type DescriptorEdge = {
|
|
31
|
+
from: DescriptorId;
|
|
32
|
+
to: DescriptorId;
|
|
33
|
+
relation: RelationId;
|
|
34
|
+
source: 'descriptor' | 'derived';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type ResolutionStep = {
|
|
38
|
+
from: DescriptorId;
|
|
39
|
+
to: DescriptorId;
|
|
40
|
+
relation: RelationId;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type CompositionOriginType = 'selected' | 'base' | 'resolved';
|
|
44
|
+
|
|
45
|
+
export type CompositionProvenanceOrigin = {
|
|
46
|
+
originType: CompositionOriginType;
|
|
47
|
+
path: ResolutionStep[];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type CompositionProvenance = {
|
|
51
|
+
descriptorId: DescriptorId;
|
|
52
|
+
origins: CompositionProvenanceOrigin[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type DescriptorGraph = {
|
|
56
|
+
descriptorMap: DescriptorMap;
|
|
57
|
+
edges: DescriptorEdge[];
|
|
58
|
+
outgoing: Map<DescriptorId, DescriptorEdge[]>;
|
|
59
|
+
incoming: Map<DescriptorId, DescriptorEdge[]>;
|
|
60
|
+
relationDescriptors: Map<RelationId, RelationDescriptor>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type DescriptorProfile = {
|
|
64
|
+
id: DescriptorId;
|
|
65
|
+
location?: string;
|
|
66
|
+
disabled: boolean;
|
|
67
|
+
providesFor?: DescriptorId | DescriptorId[];
|
|
68
|
+
capabilities: string[];
|
|
69
|
+
outgoing: Record<RelationId, string[]>;
|
|
70
|
+
incoming: Record<RelationId, string[]>;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type CompositionPolicy = {
|
|
74
|
+
resolutionRelationIds: RelationId[];
|
|
75
|
+
provenanceRelationIds: RelationId[];
|
|
76
|
+
inspectionRelationIds: RelationId[];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type DescriptorCatalog = {
|
|
80
|
+
getDescriptorMap: () => DescriptorMap;
|
|
81
|
+
getAllDescriptors: () => Descriptor[];
|
|
82
|
+
getDescriptor: (id: DescriptorId) => Descriptor | undefined;
|
|
83
|
+
getGraph: () => DescriptorGraph;
|
|
84
|
+
getRelationDescriptors: () => RelationDescriptor[];
|
|
85
|
+
getProfiles: (input?: { ids?: DescriptorId[]; includeDisabled?: boolean }) => DescriptorProfile[];
|
|
86
|
+
getIncomingRelationMap: (relationId: RelationId) => Map<DescriptorId, DescriptorId[]>;
|
|
87
|
+
getTransitiveTargets: (input: {
|
|
88
|
+
start: DescriptorId[];
|
|
89
|
+
relationIds: RelationId[];
|
|
90
|
+
}) => DescriptorIds;
|
|
91
|
+
getDependents: (input: {
|
|
92
|
+
target: DescriptorId;
|
|
93
|
+
relationIds: RelationId[];
|
|
94
|
+
transitive?: boolean;
|
|
95
|
+
}) => DescriptorIds;
|
|
96
|
+
explain: (input: {
|
|
97
|
+
from: DescriptorId;
|
|
98
|
+
to: DescriptorId;
|
|
99
|
+
relationIds: RelationId[];
|
|
100
|
+
}) => ResolutionStep[];
|
|
101
|
+
explainPathsBatch: (input: {
|
|
102
|
+
pairs: Array<{ from: DescriptorId; to: DescriptorId }>;
|
|
103
|
+
relationIds: RelationId[];
|
|
104
|
+
}) => Array<{ from: DescriptorId; to: DescriptorId; path: ResolutionStep[] }>;
|
|
105
|
+
resolveSelection: (input: {
|
|
106
|
+
selected?: DescriptorId[];
|
|
107
|
+
baseDescriptors?: DescriptorId[];
|
|
108
|
+
policy?: Partial<CompositionPolicy>;
|
|
109
|
+
}) => CompositionSelection;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type CompositionSelection = {
|
|
113
|
+
getCatalog: () => DescriptorCatalog;
|
|
114
|
+
getGraph: () => DescriptorGraph;
|
|
115
|
+
getSelected: () => DescriptorIds;
|
|
116
|
+
getBaseDescriptors: () => DescriptorIds;
|
|
117
|
+
getResolved: () => DescriptorIds;
|
|
118
|
+
getResolvedDescriptors: () => Descriptor[];
|
|
119
|
+
getProvenance: () => CompositionProvenance[];
|
|
120
|
+
getDependentsFor: (
|
|
121
|
+
target: DescriptorId,
|
|
122
|
+
input?: { transitive?: boolean; relationIds?: RelationId[] },
|
|
123
|
+
) => DescriptorIds;
|
|
124
|
+
explain: (input: {
|
|
125
|
+
from: DescriptorId;
|
|
126
|
+
to: DescriptorId;
|
|
127
|
+
relationIds?: RelationId[];
|
|
128
|
+
}) => ResolutionStep[];
|
|
129
|
+
explainPathsBatch: (input: {
|
|
130
|
+
pairs: Array<{ from: DescriptorId; to: DescriptorId }>;
|
|
131
|
+
relationIds?: RelationId[];
|
|
132
|
+
}) => Array<{ from: DescriptorId; to: DescriptorId; path: ResolutionStep[] }>;
|
|
133
|
+
};
|