@loutrejs/loutre 0.5.0 → 0.6.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/README.md +63 -32
- package/dist/adapters/aws-lambda.d.ts +5 -3
- package/dist/adapters/aws-lambda.js +53 -23
- package/dist/adapters/bun.d.ts +7 -4
- package/dist/adapters/bun.js +52 -42
- package/dist/adapters/cloudflare-workers.d.ts +5 -3
- package/dist/adapters/cloudflare-workers.js +23 -28
- package/dist/adapters/deno.d.ts +7 -4
- package/dist/adapters/deno.js +94 -57
- package/dist/adapters/electron.d.ts +13 -5
- package/dist/adapters/electron.js +76 -28
- package/dist/application/definition.d.ts +31 -0
- package/dist/application/definition.js +16 -0
- package/dist/application/index.d.ts +4 -63
- package/dist/application/index.js +2 -11
- package/dist/application/kernel-internal.d.ts +2 -0
- package/dist/application/kernel-internal.js +10 -0
- package/dist/application/kernel.d.ts +21 -0
- package/dist/application/kernel.js +65 -0
- package/dist/core/diagnostic.d.ts +9 -0
- package/dist/core/diagnostic.js +9 -0
- package/dist/core/extension.d.ts +107 -0
- package/dist/core/extension.js +73 -0
- package/dist/core/generic-layer.d.ts +26 -0
- package/dist/core/generic-layer.js +116 -0
- package/dist/core/index.d.ts +31 -15
- package/dist/core/index.js +15 -15
- package/dist/core/injection.d.ts +4 -13
- package/dist/core/injection.js +63 -0
- package/dist/core/model/context.d.ts +18 -0
- package/dist/core/model/context.js +34 -0
- package/dist/core/model/lifecycle.d.ts +5 -0
- package/dist/core/model/lifecycle.js +44 -0
- package/dist/core/model/modules.d.ts +2 -0
- package/dist/core/model/modules.js +101 -0
- package/dist/core/model/providers.d.ts +4 -0
- package/dist/core/model/providers.js +74 -0
- package/dist/core/model/snapshot.d.ts +6 -0
- package/dist/core/model/snapshot.js +31 -0
- package/dist/core/model/types.d.ts +78 -0
- package/dist/core/model/validation.d.ts +4 -0
- package/dist/core/model/validation.js +40 -0
- package/dist/core/model/visibility.d.ts +4 -0
- package/dist/core/model/visibility.js +20 -0
- package/dist/core/model.d.ts +10 -0
- package/dist/core/model.js +346 -0
- package/dist/core/module.d.ts +8 -13
- package/dist/core/source-location.d.ts +8 -0
- package/dist/core/source-location.js +31 -0
- package/dist/core/token-internal.d.ts +3 -0
- package/dist/core/token-internal.js +4 -0
- package/dist/core/type.d.ts +7 -0
- package/dist/core/type.js +4 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/graph/index.d.ts +2 -3
- package/dist/graph/index.js +1 -3
- package/dist/graph/model.d.ts +34 -0
- package/dist/graph/model.js +127 -0
- package/dist/http/auth.d.ts +46 -0
- package/dist/http/auth.js +96 -0
- package/dist/http/client.d.ts +28 -31
- package/dist/http/client.js +58 -44
- package/dist/http/cors-internal.d.ts +29 -0
- package/dist/http/cors-internal.js +160 -0
- package/dist/http/cors.d.ts +5 -8
- package/dist/http/cors.js +7 -165
- package/dist/http/extension.d.ts +348 -0
- package/dist/http/extension.js +947 -0
- package/dist/http/index.d.ts +9 -7
- package/dist/http/index.js +5 -7
- package/dist/{application → http}/openapi.d.ts +12 -3
- package/dist/{application → http}/openapi.js +164 -102
- package/dist/http/path.d.ts +1 -42
- package/dist/http/path.js +1 -2
- package/dist/http/runtime/request.d.ts +17 -0
- package/dist/http/runtime/request.js +109 -0
- package/dist/http/runtime/response.d.ts +18 -0
- package/dist/http/runtime/response.js +108 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/message-port/extension.d.ts +66 -0
- package/dist/message-port/extension.js +395 -0
- package/dist/message-port/index.d.ts +4 -73
- package/dist/message-port/index.js +2 -189
- package/dist/message-port/transport.d.ts +11 -0
- package/dist/message-port/transport.js +45 -0
- package/dist/runtime/async-iterator-lifecycle.d.ts +12 -0
- package/dist/runtime/async-iterator-lifecycle.js +95 -0
- package/dist/runtime/capability.d.ts +11 -11
- package/dist/runtime/capability.js +3 -3
- package/dist/runtime/di.d.ts +2 -22
- package/dist/runtime/di.js +8 -285
- package/dist/runtime/execution-tracker.d.ts +8 -0
- package/dist/runtime/execution-tracker.js +54 -0
- package/dist/runtime/index.d.ts +15 -9
- package/dist/runtime/index.js +9 -9
- package/dist/runtime/ingress-gate.d.ts +7 -0
- package/dist/runtime/ingress-gate.js +33 -0
- package/dist/runtime/kernel.d.ts +23 -0
- package/dist/runtime/kernel.js +354 -0
- package/dist/runtime/logger.d.ts +2 -0
- package/dist/runtime/logger.js +2 -0
- package/dist/runtime/node-capabilities.d.ts +1 -1
- package/dist/runtime/node-capabilities.js +1 -1
- package/dist/runtime/server-port.d.ts +2 -0
- package/dist/runtime/server-port.js +11 -0
- package/dist/tasks/extension.d.ts +144 -0
- package/dist/tasks/extension.js +499 -0
- package/dist/tasks/index.d.ts +2 -0
- package/dist/tasks/index.js +1 -0
- package/dist/tasks/runtime/cron.d.ts +6 -0
- package/dist/tasks/runtime/cron.js +89 -0
- package/dist/websocket/errors.d.ts +9 -0
- package/dist/websocket/errors.js +18 -0
- package/dist/websocket/extension.d.ts +154 -0
- package/dist/websocket/extension.js +524 -0
- package/dist/websocket/index.d.ts +3 -0
- package/dist/websocket/index.js +2 -0
- package/package.json +12 -16
- package/dist/application/binding.d.ts +0 -33
- package/dist/application/binding.js +0 -295
- package/dist/application/host.d.ts +0 -8
- package/dist/application/host.js +0 -14
- package/dist/application/queue.d.ts +0 -3
- package/dist/application/queue.js +0 -4
- package/dist/application/scheduler.d.ts +0 -2
- package/dist/application/scheduler.js +0 -45
- package/dist/core/context-key.d.ts +0 -16
- package/dist/core/context-key.js +0 -17
- package/dist/core/contract-internal.d.ts +0 -22
- package/dist/core/contract-internal.js +0 -61
- package/dist/core/contract.d.ts +0 -96
- package/dist/core/contract.js +0 -88
- package/dist/core/execution.d.ts +0 -78
- package/dist/core/execution.js +0 -61
- package/dist/core/implementation.d.ts +0 -44
- package/dist/core/implementation.js +0 -35
- package/dist/core/layer.d.ts +0 -137
- package/dist/core/layer.js +0 -81
- package/dist/graph/compiler.d.ts +0 -4
- package/dist/graph/compiler.js +0 -228
- package/dist/graph/graph.d.ts +0 -18
- package/dist/graph/graph.js +0 -1118
- package/dist/graph/ir.d.ts +0 -175
- package/dist/http/application.d.ts +0 -18
- package/dist/http/application.js +0 -515
- package/dist/http/basic-auth.d.ts +0 -37
- package/dist/http/basic-auth.js +0 -67
- package/dist/http/definitions.d.ts +0 -363
- package/dist/http/definitions.js +0 -310
- package/dist/http/hosted-protocol.d.ts +0 -4
- package/dist/http/hosted-protocol.js +0 -4
- package/dist/http/internal.d.ts +0 -8
- package/dist/http/internal.js +0 -1
- package/dist/http/params.d.ts +0 -2
- package/dist/http/params.js +0 -19
- package/dist/http/request-body.d.ts +0 -4
- package/dist/http/request-body.js +0 -6
- package/dist/http/validate.d.ts +0 -20
- package/dist/http/validate.js +0 -6
- package/dist/message-port/environment.d.ts +0 -2
- package/dist/message-port/environment.js +0 -4
- package/dist/runtime/application.d.ts +0 -38
- package/dist/runtime/application.js +0 -339
- package/dist/runtime/pipeline.d.ts +0 -11
- package/dist/runtime/pipeline.js +0 -161
- /package/dist/{graph/ir.js → core/model/types.js} +0 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { diagnostic, isErrorDiagnostic } from './diagnostic.js';
|
|
2
|
+
import { isExecutionDefinition, } from './extension.js';
|
|
3
|
+
import { isFrameworkProvidedToken } from './token-internal.js';
|
|
4
|
+
import { tokenName } from './token.js';
|
|
5
|
+
import { createModelBuildContext } from './model/context.js';
|
|
6
|
+
import { resolveLifecycleDependencies } from './model/lifecycle.js';
|
|
7
|
+
import { collectModuleDeclarations } from './model/modules.js';
|
|
8
|
+
import { compileArgumentsProvider, resolveModuleExports, resolveProviderDependencies, } from './model/providers.js';
|
|
9
|
+
import { snapshotExecutionExtension } from './model/snapshot.js';
|
|
10
|
+
import { validateHostNamespaces, validateNodeIds } from './model/validation.js';
|
|
11
|
+
import { getSourceLocation } from './source-location.js';
|
|
12
|
+
import { isTokenVisible, moduleDeclaresToken } from './model/visibility.js';
|
|
13
|
+
class ApplicationModelExtensionRegistry {
|
|
14
|
+
#ordered;
|
|
15
|
+
#groups;
|
|
16
|
+
constructor(groups) {
|
|
17
|
+
this.#ordered = Object.freeze([...groups]);
|
|
18
|
+
this.#groups = new Map(groups.map((group) => [group.extension.identity, group]));
|
|
19
|
+
}
|
|
20
|
+
get(extension) {
|
|
21
|
+
return this.#groups.get(extension.identity);
|
|
22
|
+
}
|
|
23
|
+
values() {
|
|
24
|
+
return this.#ordered;
|
|
25
|
+
}
|
|
26
|
+
[Symbol.iterator]() {
|
|
27
|
+
return this.#ordered[Symbol.iterator]();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class ApplicationModelError extends Error {
|
|
31
|
+
diagnostics;
|
|
32
|
+
code = 'LUTRE_APPLICATION_MODEL_INVALID';
|
|
33
|
+
constructor(diagnostics) {
|
|
34
|
+
super(`Application Model is invalid:\n${diagnostics
|
|
35
|
+
.map((item) => `${item.code} at ${item.path}: ${item.message}`)
|
|
36
|
+
.join('\n')}`);
|
|
37
|
+
this.diagnostics = diagnostics;
|
|
38
|
+
this.name = 'ApplicationModelError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function assertValidApplicationModel(model) {
|
|
42
|
+
const errors = model.diagnostics.filter(isErrorDiagnostic);
|
|
43
|
+
if (errors.length > 0)
|
|
44
|
+
throw new ApplicationModelError(errors);
|
|
45
|
+
return model;
|
|
46
|
+
}
|
|
47
|
+
export function buildApplicationModel(options) {
|
|
48
|
+
const context = createModelBuildContext(options);
|
|
49
|
+
collectModuleDeclarations(context);
|
|
50
|
+
resolveModuleExports(context);
|
|
51
|
+
compileArgumentsProvider(context);
|
|
52
|
+
resolveProviderDependencies(context);
|
|
53
|
+
resolveLifecycleDependencies(context);
|
|
54
|
+
const { modules, moduleIds, nodes, edges, diagnostics, providers, executions, providerNodes, } = context;
|
|
55
|
+
const extensionExecutions = new Map();
|
|
56
|
+
const extensionNames = new Map();
|
|
57
|
+
const extensionIdentities = new Map();
|
|
58
|
+
const executionIds = new Set();
|
|
59
|
+
const capabilityNodeIds = new Set();
|
|
60
|
+
const visitedDefinitions = new Set();
|
|
61
|
+
const compiledDefinitions = new Map();
|
|
62
|
+
const executionReferences = [];
|
|
63
|
+
const executionReferenceResults = new Map();
|
|
64
|
+
const getExecutionReferences = (definition) => {
|
|
65
|
+
const cached = executionReferenceResults.get(definition);
|
|
66
|
+
if (cached)
|
|
67
|
+
return cached;
|
|
68
|
+
const references = definition.extension.references;
|
|
69
|
+
if (!references) {
|
|
70
|
+
const result = { references: Object.freeze([]) };
|
|
71
|
+
executionReferenceResults.set(definition, result);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const result = {
|
|
76
|
+
references: references(definition),
|
|
77
|
+
};
|
|
78
|
+
executionReferenceResults.set(definition, result);
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const result = { error };
|
|
83
|
+
executionReferenceResults.set(definition, result);
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const rootDefinitionOwners = new Map();
|
|
88
|
+
for (const module of modules) {
|
|
89
|
+
for (const value of module.definition.executions ?? []) {
|
|
90
|
+
if (!isExecutionDefinition(value))
|
|
91
|
+
continue;
|
|
92
|
+
const owners = rootDefinitionOwners.get(value) ?? new Set();
|
|
93
|
+
owners.add(module);
|
|
94
|
+
rootDefinitionOwners.set(value, owners);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const reachableDefinitionModules = new Map();
|
|
98
|
+
for (const module of modules) {
|
|
99
|
+
const queue = (module.definition.executions ?? []).filter(isExecutionDefinition);
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
let queueIndex = 0;
|
|
102
|
+
while (queueIndex < queue.length) {
|
|
103
|
+
const value = queue[queueIndex++];
|
|
104
|
+
if (seen.has(value))
|
|
105
|
+
continue;
|
|
106
|
+
seen.add(value);
|
|
107
|
+
const explicitOwners = rootDefinitionOwners.get(value);
|
|
108
|
+
if (explicitOwners && !explicitOwners.has(module))
|
|
109
|
+
continue;
|
|
110
|
+
const reachableModules = reachableDefinitionModules.get(value) ?? new Set();
|
|
111
|
+
reachableModules.add(module);
|
|
112
|
+
reachableDefinitionModules.set(value, reachableModules);
|
|
113
|
+
const referenceResult = getExecutionReferences(value);
|
|
114
|
+
if ('error' in referenceResult)
|
|
115
|
+
continue;
|
|
116
|
+
for (const reference of referenceResult.references) {
|
|
117
|
+
if (!isExecutionDefinition(reference))
|
|
118
|
+
continue;
|
|
119
|
+
if (reference.extension.identity !== value.extension.identity)
|
|
120
|
+
continue;
|
|
121
|
+
queue.push(reference);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const definitionOwners = new Map();
|
|
126
|
+
const ambiguousDefinitions = new Set();
|
|
127
|
+
for (const [definition, explicitOwners] of rootDefinitionOwners) {
|
|
128
|
+
if (explicitOwners.size === 1) {
|
|
129
|
+
definitionOwners.set(definition, [...explicitOwners][0]);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
ambiguousDefinitions.add(definition);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const [definition, reachableModules] of reachableDefinitionModules) {
|
|
136
|
+
if (rootDefinitionOwners.has(definition))
|
|
137
|
+
continue;
|
|
138
|
+
if (reachableModules.size === 1) {
|
|
139
|
+
definitionOwners.set(definition, [...reachableModules][0]);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
ambiguousDefinitions.add(definition);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (ambiguousDefinitions.size > 0) {
|
|
146
|
+
diagnostics.push(diagnostic('LUTRE_EXECUTION_OWNER_AMBIGUOUS', 'Execution Definition ownership is ambiguous across multiple Modules. Register each shared Definition in exactly one Module.executions; other Modules may reference it.', 'application.executions'));
|
|
147
|
+
}
|
|
148
|
+
for (const module of modules) {
|
|
149
|
+
const moduleId = moduleIds.get(module);
|
|
150
|
+
const rootDefinitions = module.definition.executions ?? [];
|
|
151
|
+
const rootDefinitionIndexes = new Map();
|
|
152
|
+
for (const [definitionIndex, value] of rootDefinitions.entries()) {
|
|
153
|
+
if (isExecutionDefinition(value) && !rootDefinitionIndexes.has(value)) {
|
|
154
|
+
rootDefinitionIndexes.set(value, definitionIndex);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const discoveredDefinitionIndexes = new Map();
|
|
158
|
+
const queue = rootDefinitions.map((value, definitionIndex) => ({
|
|
159
|
+
value,
|
|
160
|
+
definitionIndex,
|
|
161
|
+
path: `${moduleId}.executions.${definitionIndex}`,
|
|
162
|
+
}));
|
|
163
|
+
let nextDiscoveredDefinitionIndex = rootDefinitions.length;
|
|
164
|
+
let queueIndex = 0;
|
|
165
|
+
while (queueIndex < queue.length) {
|
|
166
|
+
const { value, definitionIndex, path } = queue[queueIndex++];
|
|
167
|
+
if (!isExecutionDefinition(value)) {
|
|
168
|
+
diagnostics.push(diagnostic('LUTRE_EXECUTION_DEFINITION_INVALID', 'Module executions and referenced executions accept only branded Execution Definitions.', path));
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (ambiguousDefinitions.has(value))
|
|
172
|
+
continue;
|
|
173
|
+
if (definitionOwners.get(value) !== module)
|
|
174
|
+
continue;
|
|
175
|
+
if (visitedDefinitions.has(value))
|
|
176
|
+
continue;
|
|
177
|
+
visitedDefinitions.add(value);
|
|
178
|
+
const extension = value.extension;
|
|
179
|
+
const namedIdentity = extensionNames.get(extension.name);
|
|
180
|
+
if (namedIdentity && namedIdentity.identity !== extension.identity) {
|
|
181
|
+
diagnostics.push(diagnostic('LUTRE_EXTENSION_ABI_MISMATCH', `Extension ${extension.name} mixes incompatible ABI versions ${namedIdentity.abiVersion} and ${extension.abiVersion}.`, path));
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const identityOwner = extensionIdentities.get(extension.identity);
|
|
185
|
+
if (identityOwner && identityOwner.name !== extension.name) {
|
|
186
|
+
diagnostics.push(diagnostic('LUTRE_EXTENSION_IDENTITY_COLLISION', `Extension identity is shared by ${identityOwner.name} and ${extension.name}.`, path));
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const canonicalExtension = identityOwner?.extension ?? snapshotExecutionExtension(extension);
|
|
190
|
+
extensionNames.set(extension.name, {
|
|
191
|
+
identity: extension.identity,
|
|
192
|
+
abiVersion: extension.abiVersion,
|
|
193
|
+
});
|
|
194
|
+
extensionIdentities.set(extension.identity, {
|
|
195
|
+
name: extension.name,
|
|
196
|
+
extension: canonicalExtension,
|
|
197
|
+
});
|
|
198
|
+
let contribution;
|
|
199
|
+
try {
|
|
200
|
+
contribution = extension.compile(value, {
|
|
201
|
+
moduleId,
|
|
202
|
+
definitionIndex,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
diagnostics.push(diagnostic('LUTRE_EXTENSION_COMPILE', describeError(error), path));
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (executionIds.has(contribution.id)) {
|
|
210
|
+
diagnostics.push(diagnostic('LUTRE_EXECUTION_ID_COLLISION', `Execution id ${contribution.id} is already registered.`, path));
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
executionIds.add(contribution.id);
|
|
214
|
+
const executionSource = getSourceLocation(value);
|
|
215
|
+
const execution = Object.freeze({
|
|
216
|
+
...contribution,
|
|
217
|
+
dependencies: Object.freeze([...contribution.dependencies]),
|
|
218
|
+
capabilities: Object.freeze([...contribution.capabilities]),
|
|
219
|
+
moduleId,
|
|
220
|
+
...(executionSource === undefined ? {} : { source: executionSource }),
|
|
221
|
+
});
|
|
222
|
+
compiledDefinitions.set(value, execution);
|
|
223
|
+
executions.push(execution);
|
|
224
|
+
nodes.push(execution);
|
|
225
|
+
edges.push({ from: moduleId, to: execution.id, kind: 'owns' });
|
|
226
|
+
const grouped = extensionExecutions.get(extension.identity) ?? {
|
|
227
|
+
extension: canonicalExtension,
|
|
228
|
+
executions: [],
|
|
229
|
+
};
|
|
230
|
+
grouped.executions.push(execution);
|
|
231
|
+
extensionExecutions.set(extension.identity, grouped);
|
|
232
|
+
for (const dependency of execution.dependencies) {
|
|
233
|
+
const provider = providerNodes.get(dependency);
|
|
234
|
+
if (provider) {
|
|
235
|
+
edges.push({ from: execution.id, to: provider.id, kind: 'injects' });
|
|
236
|
+
const providerModule = modules.find((candidate) => moduleIds.get(candidate) === provider.moduleId);
|
|
237
|
+
if (providerModule &&
|
|
238
|
+
provider.moduleId !== moduleId &&
|
|
239
|
+
!moduleDeclaresToken(module, dependency) &&
|
|
240
|
+
!isTokenVisible(module, providerModule, dependency)) {
|
|
241
|
+
diagnostics.push(diagnostic('LUTRE_MODULE_VISIBILITY', `Execution ${execution.id} depends on private ${tokenName(dependency)} from another Module.`, execution.id));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else if (!isFrameworkProvidedToken(dependency)) {
|
|
245
|
+
diagnostics.push(diagnostic('LUTRE_EXECUTION_DEPENDENCY_MISSING', `Execution ${execution.id} requires ${tokenName(dependency)}, but no provider is declared.`, execution.id));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
for (const capability of execution.capabilities) {
|
|
249
|
+
const capabilityId = `capability:${capability.id}`;
|
|
250
|
+
if (!capabilityNodeIds.has(capabilityId)) {
|
|
251
|
+
capabilityNodeIds.add(capabilityId);
|
|
252
|
+
nodes.push({
|
|
253
|
+
kind: 'framework',
|
|
254
|
+
id: capabilityId,
|
|
255
|
+
frameworkKind: 'runtime-capability',
|
|
256
|
+
name: capability.id,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
edges.push({
|
|
260
|
+
from: execution.id,
|
|
261
|
+
to: capabilityId,
|
|
262
|
+
kind: 'requires',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
const referenceResult = getExecutionReferences(value);
|
|
266
|
+
if ('error' in referenceResult) {
|
|
267
|
+
diagnostics.push(diagnostic('LUTRE_EXTENSION_REFERENCES', describeError(referenceResult.error), path));
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
for (const [referenceIndex, reference,] of referenceResult.references.entries()) {
|
|
271
|
+
const referencePath = `${path}.references.${referenceIndex}`;
|
|
272
|
+
if (!isExecutionDefinition(reference)) {
|
|
273
|
+
diagnostics.push(diagnostic('LUTRE_EXECUTION_REFERENCE_INVALID', 'Execution references accept only branded Execution Definitions.', referencePath));
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (reference.extension.identity !== extension.identity) {
|
|
277
|
+
diagnostics.push(diagnostic('LUTRE_EXECUTION_REFERENCE_EXTENSION', `Execution ${execution.id} cannot reference an execution owned by ${reference.extension.name}.`, referencePath));
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
executionReferences.push({ from: value, to: reference });
|
|
281
|
+
let referencedDefinitionIndex = rootDefinitionIndexes.get(reference);
|
|
282
|
+
if (referencedDefinitionIndex === undefined) {
|
|
283
|
+
referencedDefinitionIndex = discoveredDefinitionIndexes.get(reference);
|
|
284
|
+
if (referencedDefinitionIndex === undefined) {
|
|
285
|
+
referencedDefinitionIndex = nextDiscoveredDefinitionIndex++;
|
|
286
|
+
discoveredDefinitionIndexes.set(reference, referencedDefinitionIndex);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
queue.push({
|
|
290
|
+
value: reference,
|
|
291
|
+
definitionIndex: referencedDefinitionIndex,
|
|
292
|
+
path: referencePath,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
for (const reference of executionReferences) {
|
|
298
|
+
const from = compiledDefinitions.get(reference.from);
|
|
299
|
+
const to = compiledDefinitions.get(reference.to);
|
|
300
|
+
if (!from || !to)
|
|
301
|
+
continue;
|
|
302
|
+
edges.push({ from: from.id, to: to.id, kind: 'references' });
|
|
303
|
+
}
|
|
304
|
+
const extensionGroups = [...extensionExecutions.values()].map(({ extension, executions: ownedExecutions }) => {
|
|
305
|
+
const extensionId = `extension:${extension.name}`;
|
|
306
|
+
nodes.push({
|
|
307
|
+
kind: 'framework',
|
|
308
|
+
id: extensionId,
|
|
309
|
+
frameworkKind: 'execution-extension',
|
|
310
|
+
name: extension.name,
|
|
311
|
+
});
|
|
312
|
+
for (const execution of ownedExecutions) {
|
|
313
|
+
edges.push({ from: extensionId, to: execution.id, kind: 'starts' });
|
|
314
|
+
}
|
|
315
|
+
if (extension.validate) {
|
|
316
|
+
try {
|
|
317
|
+
diagnostics.push(...extension.validate({ executions: ownedExecutions }));
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
diagnostics.push(diagnostic('LUTRE_EXTENSION_VALIDATE', describeError(error), extensionId));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return Object.freeze({
|
|
324
|
+
extension,
|
|
325
|
+
executions: Object.freeze([...ownedExecutions]),
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
const extensions = new ApplicationModelExtensionRegistry(extensionGroups);
|
|
329
|
+
validateHostNamespaces(extensionGroups, diagnostics);
|
|
330
|
+
validateNodeIds(nodes, diagnostics);
|
|
331
|
+
return Object.freeze({
|
|
332
|
+
kind: 'application-model',
|
|
333
|
+
...(options.arguments === undefined
|
|
334
|
+
? {}
|
|
335
|
+
: { arguments: options.arguments }),
|
|
336
|
+
providers: Object.freeze(providers),
|
|
337
|
+
nodes: Object.freeze(nodes.map((node) => Object.freeze(node))),
|
|
338
|
+
edges: Object.freeze(edges.map((edge) => Object.freeze(edge))),
|
|
339
|
+
executions: Object.freeze(executions),
|
|
340
|
+
extensions,
|
|
341
|
+
diagnostics: Object.freeze(diagnostics.map((item) => Object.freeze({ ...item }))),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
function describeError(error) {
|
|
345
|
+
return error instanceof Error ? error.message : String(error);
|
|
346
|
+
}
|
package/dist/core/module.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EnvClass } from './env.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ExecutionDefinition, ExtensionOfDefinition, HostApiOfExtension } from './extension.js';
|
|
3
3
|
import type { ModuleLifecycle } from './lifecycle.js';
|
|
4
4
|
import { type ProviderDeclaration } from './provider.js';
|
|
5
5
|
export interface ModuleDefinition {
|
|
@@ -8,24 +8,19 @@ export interface ModuleDefinition {
|
|
|
8
8
|
readonly imports?: readonly ModuleInstance[];
|
|
9
9
|
readonly environment?: readonly EnvClass[];
|
|
10
10
|
readonly providers?: readonly ProviderDeclaration[];
|
|
11
|
-
readonly
|
|
11
|
+
readonly executions?: readonly ExecutionDefinition[];
|
|
12
12
|
readonly exports?: readonly unknown[];
|
|
13
13
|
readonly lifecycle?: ModuleLifecycle;
|
|
14
|
-
readonly requires?: readonly string[];
|
|
15
14
|
}
|
|
16
15
|
export declare const moduleTypeInfo: unique symbol;
|
|
17
16
|
export interface ModuleTypeInfo<TDefinition extends ModuleDefinition = ModuleDefinition> {
|
|
18
|
-
readonly
|
|
19
|
-
readonly capabilities: CapabilitiesOfModuleDefinition<TDefinition>;
|
|
17
|
+
readonly extensions: ExtensionsOfModuleDefinition<TDefinition>;
|
|
20
18
|
}
|
|
21
|
-
type
|
|
22
|
-
type
|
|
23
|
-
export type
|
|
24
|
-
type
|
|
25
|
-
type
|
|
26
|
-
export type CapabilitiesOfModuleDefinition<TDefinition extends ModuleDefinition> = DirectCapabilities<TDefinition> | ImportedCapabilities<TDefinition>;
|
|
27
|
-
export type ModuleProtocols<TModule> = TModule extends ModuleInstance<infer TDefinition> ? ProtocolsOfModuleDefinition<TDefinition> : never;
|
|
28
|
-
export type ModuleCapabilities<TModule> = TModule extends ModuleInstance<infer TDefinition> ? CapabilitiesOfModuleDefinition<TDefinition> : never;
|
|
19
|
+
type DirectExtensions<TDefinition extends ModuleDefinition> = TDefinition['executions'] extends readonly ExecutionDefinition[] ? ExtensionOfDefinition<TDefinition['executions'][number]> : never;
|
|
20
|
+
type ImportedExtensions<TDefinition extends ModuleDefinition> = TDefinition['imports'] extends readonly ModuleInstance[] ? ModuleExtensions<TDefinition['imports'][number]> : never;
|
|
21
|
+
export type ExtensionsOfModuleDefinition<TDefinition extends ModuleDefinition> = DirectExtensions<TDefinition> | ImportedExtensions<TDefinition>;
|
|
22
|
+
export type ModuleExtensions<TModule> = TModule extends ModuleInstance<infer TDefinition> ? ExtensionsOfModuleDefinition<TDefinition> : never;
|
|
23
|
+
export type ModuleHostApis<TModule> = HostApiOfExtension<ModuleExtensions<TModule>>;
|
|
29
24
|
export interface ModuleInstance<TDefinition extends ModuleDefinition = ModuleDefinition> {
|
|
30
25
|
readonly kind: 'module-instance';
|
|
31
26
|
readonly template: AnyModuleTemplate;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SourceLocation {
|
|
2
|
+
readonly file: string;
|
|
3
|
+
readonly line?: number;
|
|
4
|
+
readonly column?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function registerSourceLocation<T>(value: T, source: SourceLocation): T;
|
|
7
|
+
export declare function getSourceLocation(value: unknown): SourceLocation | undefined;
|
|
8
|
+
export declare function inheritSourceLocation<T>(target: T, source: unknown): T;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const sourceMetadata = new WeakMap();
|
|
2
|
+
function asObject(value) {
|
|
3
|
+
return (typeof value === 'object' && value !== null) ||
|
|
4
|
+
typeof value === 'function'
|
|
5
|
+
? value
|
|
6
|
+
: undefined;
|
|
7
|
+
}
|
|
8
|
+
function snapshotSourceLocation(source) {
|
|
9
|
+
return Object.freeze({
|
|
10
|
+
file: source.file,
|
|
11
|
+
...(source.line === undefined ? {} : { line: source.line }),
|
|
12
|
+
...(source.column === undefined ? {} : { column: source.column }),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function registerSourceLocation(value, source) {
|
|
16
|
+
const target = asObject(value);
|
|
17
|
+
if (!target || sourceMetadata.has(target))
|
|
18
|
+
return value;
|
|
19
|
+
sourceMetadata.set(target, snapshotSourceLocation(source));
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
export function getSourceLocation(value) {
|
|
23
|
+
const target = asObject(value);
|
|
24
|
+
return target ? sourceMetadata.get(target) : undefined;
|
|
25
|
+
}
|
|
26
|
+
export function inheritSourceLocation(target, source) {
|
|
27
|
+
const location = getSourceLocation(source);
|
|
28
|
+
return location === undefined
|
|
29
|
+
? target
|
|
30
|
+
: registerSourceLocation(target, location);
|
|
31
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const LOUTRE_VERSION = "0.
|
|
1
|
+
export declare const LOUTRE_VERSION = "0.6.0";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// package manifestとのずれを避けるため、scripts/generate-version.mjsがbuild前に更新する。
|
|
2
|
-
export const LOUTRE_VERSION = '0.
|
|
2
|
+
export const LOUTRE_VERSION = '0.6.0';
|
package/dist/graph/index.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export {
|
|
3
|
-
export { buildApplicationGraph, compileApplication } from './compiler.js';
|
|
1
|
+
export { projectApplicationModel } from './model.js';
|
|
2
|
+
export type { ApplicationModelGraphIR, GraphEdgeIR, GraphNodeIR, JsonPrimitive, JsonValue, } from './model.js';
|
package/dist/graph/index.js
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type ApplicationModel, type ApplicationModelEdge, type ApplicationModelNode, type Diagnostic, type SourceLocation } from '../core/index.js';
|
|
2
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
3
|
+
export type JsonValue = JsonPrimitive | {
|
|
4
|
+
readonly [key: string]: JsonValue;
|
|
5
|
+
} | readonly JsonValue[];
|
|
6
|
+
export interface GraphNodeIR {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly kind: ApplicationModelNode['kind'];
|
|
9
|
+
readonly name?: string;
|
|
10
|
+
readonly module?: string;
|
|
11
|
+
readonly executionKind?: string;
|
|
12
|
+
readonly capabilities?: readonly string[];
|
|
13
|
+
readonly extension?: {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly hostNamespace?: string;
|
|
16
|
+
readonly metadata?: JsonValue;
|
|
17
|
+
};
|
|
18
|
+
readonly attributes?: Readonly<Record<string, JsonValue>>;
|
|
19
|
+
readonly source?: SourceLocation;
|
|
20
|
+
}
|
|
21
|
+
export interface GraphEdgeIR {
|
|
22
|
+
readonly from: string;
|
|
23
|
+
readonly to: string;
|
|
24
|
+
readonly kind: ApplicationModelEdge['kind'];
|
|
25
|
+
}
|
|
26
|
+
export interface ApplicationModelGraphIR {
|
|
27
|
+
readonly nodes: readonly GraphNodeIR[];
|
|
28
|
+
readonly edges: readonly GraphEdgeIR[];
|
|
29
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
30
|
+
readonly modules: readonly GraphNodeIR[];
|
|
31
|
+
readonly providers: readonly GraphNodeIR[];
|
|
32
|
+
readonly executions: readonly GraphNodeIR[];
|
|
33
|
+
}
|
|
34
|
+
export declare function projectApplicationModel(model: ApplicationModel): ApplicationModelGraphIR;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { diagnostic, tokenName, } from '../core/index.js';
|
|
2
|
+
export function projectApplicationModel(model) {
|
|
3
|
+
const diagnostics = [...model.diagnostics];
|
|
4
|
+
const projectedExecutions = new Map();
|
|
5
|
+
for (const group of model.extensions) {
|
|
6
|
+
for (const execution of projectExtensionGroup(group, diagnostics)) {
|
|
7
|
+
projectedExecutions.set(execution.id, execution);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const nodes = model.nodes.map((node) => node.kind === 'execution'
|
|
11
|
+
? (projectedExecutions.get(node.id) ?? projectExecutionBase(node))
|
|
12
|
+
: projectCoreNode(node));
|
|
13
|
+
const edges = model.edges.map((edge) => ({ ...edge }));
|
|
14
|
+
return Object.freeze({
|
|
15
|
+
nodes: Object.freeze(nodes),
|
|
16
|
+
edges: Object.freeze(edges),
|
|
17
|
+
diagnostics: Object.freeze(diagnostics),
|
|
18
|
+
modules: Object.freeze(nodes.filter((node) => node.kind === 'module')),
|
|
19
|
+
providers: Object.freeze(nodes.filter((node) => node.kind === 'provider')),
|
|
20
|
+
executions: Object.freeze(nodes.filter((node) => node.kind === 'execution')),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function projectCoreNode(node) {
|
|
24
|
+
switch (node.kind) {
|
|
25
|
+
case 'module':
|
|
26
|
+
return {
|
|
27
|
+
id: node.id,
|
|
28
|
+
kind: node.kind,
|
|
29
|
+
...(node.source === undefined ? {} : { source: node.source }),
|
|
30
|
+
...(node.name === undefined ? {} : { name: node.name }),
|
|
31
|
+
...(node.description === undefined
|
|
32
|
+
? {}
|
|
33
|
+
: { attributes: { description: node.description } }),
|
|
34
|
+
};
|
|
35
|
+
case 'provider':
|
|
36
|
+
return {
|
|
37
|
+
id: node.id,
|
|
38
|
+
kind: node.kind,
|
|
39
|
+
name: tokenName(node.token),
|
|
40
|
+
module: node.moduleId,
|
|
41
|
+
...(node.source === undefined ? {} : { source: node.source }),
|
|
42
|
+
attributes: {
|
|
43
|
+
providerKind: node.provider.kind,
|
|
44
|
+
scope: node.provider.scope,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
case 'lifecycle':
|
|
48
|
+
return {
|
|
49
|
+
id: node.id,
|
|
50
|
+
kind: node.kind,
|
|
51
|
+
name: node.phase,
|
|
52
|
+
module: node.moduleId,
|
|
53
|
+
...(node.source === undefined ? {} : { source: node.source }),
|
|
54
|
+
};
|
|
55
|
+
case 'framework':
|
|
56
|
+
return {
|
|
57
|
+
id: node.id,
|
|
58
|
+
kind: node.kind,
|
|
59
|
+
name: node.name,
|
|
60
|
+
...(node.source === undefined ? {} : { source: node.source }),
|
|
61
|
+
attributes: { frameworkKind: node.frameworkKind },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function projectExtensionGroup(group, diagnostics) {
|
|
66
|
+
return group.executions.map((execution) => {
|
|
67
|
+
let metadata;
|
|
68
|
+
const project = group.extension.projectGraph;
|
|
69
|
+
if (project) {
|
|
70
|
+
try {
|
|
71
|
+
const projected = project({ execution });
|
|
72
|
+
if (projected !== undefined) {
|
|
73
|
+
if (!isJsonValue(projected)) {
|
|
74
|
+
diagnostics.push(diagnostic('LUTRE_EXTENSION_PROJECTION_NOT_SERIALIZABLE', `Extension ${group.extension.name} returned non-serializable metadata.`, execution.id));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
metadata = projected;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
diagnostics.push(diagnostic('LUTRE_EXTENSION_PROJECTION', error instanceof Error ? error.message : String(error), execution.id));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
...projectExecutionBase(execution),
|
|
87
|
+
extension: {
|
|
88
|
+
name: group.extension.name,
|
|
89
|
+
...(group.extension.host === undefined
|
|
90
|
+
? {}
|
|
91
|
+
: { hostNamespace: group.extension.host.namespace }),
|
|
92
|
+
...(metadata === undefined ? {} : { metadata }),
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function projectExecutionBase(execution) {
|
|
98
|
+
return {
|
|
99
|
+
id: execution.id,
|
|
100
|
+
kind: execution.kind,
|
|
101
|
+
module: execution.moduleId,
|
|
102
|
+
executionKind: execution.executionKind,
|
|
103
|
+
capabilities: execution.capabilities.map((capability) => capability.id),
|
|
104
|
+
...(execution.source === undefined ? {} : { source: execution.source }),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function isJsonValue(value, ancestors = new Set()) {
|
|
108
|
+
if (value === null ||
|
|
109
|
+
typeof value === 'string' ||
|
|
110
|
+
typeof value === 'boolean') {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (typeof value === 'number')
|
|
114
|
+
return Number.isFinite(value);
|
|
115
|
+
if (typeof value !== 'object')
|
|
116
|
+
return false;
|
|
117
|
+
if (ancestors.has(value))
|
|
118
|
+
return false;
|
|
119
|
+
const nextAncestors = new Set(ancestors).add(value);
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
return value.every((item) => isJsonValue(item, nextAncestors));
|
|
122
|
+
}
|
|
123
|
+
const prototype = Object.getPrototypeOf(value);
|
|
124
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
125
|
+
return false;
|
|
126
|
+
return Object.values(value).every((item) => isJsonValue(item, nextAncestors));
|
|
127
|
+
}
|