@loutrejs/loutre 0.5.0 → 0.7.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.
Files changed (188) hide show
  1. package/README.md +63 -32
  2. package/dist/adapters/aws-lambda.d.ts +5 -3
  3. package/dist/adapters/aws-lambda.js +53 -23
  4. package/dist/adapters/bun.d.ts +7 -4
  5. package/dist/adapters/bun.js +52 -42
  6. package/dist/adapters/cloudflare-workers.d.ts +5 -3
  7. package/dist/adapters/cloudflare-workers.js +23 -28
  8. package/dist/adapters/deno.d.ts +7 -4
  9. package/dist/adapters/deno.js +94 -57
  10. package/dist/adapters/electron.d.ts +13 -5
  11. package/dist/adapters/electron.js +76 -28
  12. package/dist/application/definition.d.ts +31 -0
  13. package/dist/application/definition.js +16 -0
  14. package/dist/application/index.d.ts +4 -63
  15. package/dist/application/index.js +2 -11
  16. package/dist/application/kernel-internal.d.ts +2 -0
  17. package/dist/application/kernel-internal.js +10 -0
  18. package/dist/application/kernel.d.ts +22 -0
  19. package/dist/application/kernel.js +65 -0
  20. package/dist/core/diagnostic.d.ts +9 -0
  21. package/dist/core/diagnostic.js +9 -0
  22. package/dist/core/extension.d.ts +118 -0
  23. package/dist/core/extension.js +73 -0
  24. package/dist/core/generic-layer.d.ts +26 -0
  25. package/dist/core/generic-layer.js +116 -0
  26. package/dist/core/index.d.ts +32 -15
  27. package/dist/core/index.js +15 -15
  28. package/dist/core/injection.d.ts +4 -13
  29. package/dist/core/injection.js +63 -0
  30. package/dist/core/instrumentation.d.ts +36 -0
  31. package/dist/core/model/context.d.ts +18 -0
  32. package/dist/core/model/context.js +34 -0
  33. package/dist/core/model/lifecycle.d.ts +5 -0
  34. package/dist/core/model/lifecycle.js +44 -0
  35. package/dist/core/model/modules.d.ts +2 -0
  36. package/dist/core/model/modules.js +101 -0
  37. package/dist/core/model/providers.d.ts +4 -0
  38. package/dist/core/model/providers.js +74 -0
  39. package/dist/core/model/snapshot.d.ts +6 -0
  40. package/dist/core/model/snapshot.js +31 -0
  41. package/dist/core/model/types.d.ts +78 -0
  42. package/dist/core/model/types.js +1 -0
  43. package/dist/core/model/validation.d.ts +4 -0
  44. package/dist/core/model/validation.js +40 -0
  45. package/dist/core/model/visibility.d.ts +4 -0
  46. package/dist/core/model/visibility.js +20 -0
  47. package/dist/core/model.d.ts +10 -0
  48. package/dist/core/model.js +346 -0
  49. package/dist/core/module.d.ts +8 -13
  50. package/dist/core/source-location.d.ts +8 -0
  51. package/dist/core/source-location.js +31 -0
  52. package/dist/core/token-internal.d.ts +3 -0
  53. package/dist/core/token-internal.js +4 -0
  54. package/dist/core/type.d.ts +7 -0
  55. package/dist/core/type.js +4 -0
  56. package/dist/devtools/capsule.d.ts +51 -0
  57. package/dist/devtools/capsule.js +245 -0
  58. package/dist/devtools/index.d.ts +11 -0
  59. package/dist/devtools/index.js +6 -0
  60. package/dist/devtools/inspector.d.ts +57 -0
  61. package/dist/devtools/inspector.js +1 -0
  62. package/dist/devtools/module.d.ts +18 -0
  63. package/dist/devtools/module.js +26 -0
  64. package/dist/devtools/observer.d.ts +77 -0
  65. package/dist/devtools/observer.js +10 -0
  66. package/dist/devtools/protocol.d.ts +1 -0
  67. package/dist/devtools/protocol.js +1 -0
  68. package/dist/devtools/provider-instrumentation.d.ts +14 -0
  69. package/dist/devtools/provider-instrumentation.js +117 -0
  70. package/dist/devtools/runtime.d.ts +20 -0
  71. package/dist/devtools/runtime.js +519 -0
  72. package/dist/devtools/values.d.ts +35 -0
  73. package/dist/devtools/values.js +175 -0
  74. package/dist/generated/version.d.ts +1 -1
  75. package/dist/generated/version.js +1 -1
  76. package/dist/graph/index.d.ts +2 -3
  77. package/dist/graph/index.js +1 -3
  78. package/dist/graph/model.d.ts +34 -0
  79. package/dist/graph/model.js +127 -0
  80. package/dist/http/auth.d.ts +46 -0
  81. package/dist/http/auth.js +96 -0
  82. package/dist/http/client.d.ts +28 -31
  83. package/dist/http/client.js +58 -44
  84. package/dist/http/cors-internal.d.ts +29 -0
  85. package/dist/http/cors-internal.js +160 -0
  86. package/dist/http/cors.d.ts +5 -8
  87. package/dist/http/cors.js +7 -165
  88. package/dist/http/extension.d.ts +348 -0
  89. package/dist/http/extension.js +1061 -0
  90. package/dist/http/index.d.ts +9 -7
  91. package/dist/http/index.js +5 -7
  92. package/dist/{application → http}/openapi.d.ts +12 -3
  93. package/dist/{application → http}/openapi.js +164 -102
  94. package/dist/http/path.d.ts +1 -42
  95. package/dist/http/path.js +1 -2
  96. package/dist/http/runtime/request.d.ts +17 -0
  97. package/dist/http/runtime/request.js +109 -0
  98. package/dist/http/runtime/response.d.ts +18 -0
  99. package/dist/http/runtime/response.js +108 -0
  100. package/dist/index.d.ts +4 -0
  101. package/dist/index.js +2 -0
  102. package/dist/message-port/extension.d.ts +66 -0
  103. package/dist/message-port/extension.js +411 -0
  104. package/dist/message-port/index.d.ts +4 -73
  105. package/dist/message-port/index.js +2 -189
  106. package/dist/message-port/transport.d.ts +11 -0
  107. package/dist/message-port/transport.js +45 -0
  108. package/dist/runtime/async-iterator-lifecycle.d.ts +12 -0
  109. package/dist/runtime/async-iterator-lifecycle.js +95 -0
  110. package/dist/runtime/capability.d.ts +11 -11
  111. package/dist/runtime/capability.js +3 -3
  112. package/dist/runtime/di.d.ts +5 -21
  113. package/dist/runtime/di.js +22 -289
  114. package/dist/runtime/execution-tracker.d.ts +8 -0
  115. package/dist/runtime/execution-tracker.js +54 -0
  116. package/dist/runtime/index.d.ts +15 -9
  117. package/dist/runtime/index.js +9 -9
  118. package/dist/runtime/ingress-gate.d.ts +7 -0
  119. package/dist/runtime/ingress-gate.js +33 -0
  120. package/dist/runtime/kernel.d.ts +25 -0
  121. package/dist/runtime/kernel.js +447 -0
  122. package/dist/runtime/logger.d.ts +2 -0
  123. package/dist/runtime/logger.js +2 -0
  124. package/dist/runtime/node-capabilities.d.ts +1 -1
  125. package/dist/runtime/node-capabilities.js +1 -1
  126. package/dist/runtime/server-port.d.ts +2 -0
  127. package/dist/runtime/server-port.js +11 -0
  128. package/dist/tasks/extension.d.ts +144 -0
  129. package/dist/tasks/extension.js +525 -0
  130. package/dist/tasks/index.d.ts +2 -0
  131. package/dist/tasks/index.js +1 -0
  132. package/dist/tasks/runtime/cron.d.ts +6 -0
  133. package/dist/tasks/runtime/cron.js +89 -0
  134. package/dist/websocket/errors.d.ts +9 -0
  135. package/dist/websocket/errors.js +18 -0
  136. package/dist/websocket/extension.d.ts +154 -0
  137. package/dist/websocket/extension.js +533 -0
  138. package/dist/websocket/index.d.ts +3 -0
  139. package/dist/websocket/index.js +2 -0
  140. package/package.json +16 -16
  141. package/dist/application/binding.d.ts +0 -33
  142. package/dist/application/binding.js +0 -295
  143. package/dist/application/host.d.ts +0 -8
  144. package/dist/application/host.js +0 -14
  145. package/dist/application/queue.d.ts +0 -3
  146. package/dist/application/queue.js +0 -4
  147. package/dist/application/scheduler.d.ts +0 -2
  148. package/dist/application/scheduler.js +0 -45
  149. package/dist/core/context-key.d.ts +0 -16
  150. package/dist/core/context-key.js +0 -17
  151. package/dist/core/contract-internal.d.ts +0 -22
  152. package/dist/core/contract-internal.js +0 -61
  153. package/dist/core/contract.d.ts +0 -96
  154. package/dist/core/contract.js +0 -88
  155. package/dist/core/execution.d.ts +0 -78
  156. package/dist/core/execution.js +0 -61
  157. package/dist/core/implementation.d.ts +0 -44
  158. package/dist/core/implementation.js +0 -35
  159. package/dist/core/layer.d.ts +0 -137
  160. package/dist/core/layer.js +0 -81
  161. package/dist/graph/compiler.d.ts +0 -4
  162. package/dist/graph/compiler.js +0 -228
  163. package/dist/graph/graph.d.ts +0 -18
  164. package/dist/graph/graph.js +0 -1118
  165. package/dist/graph/ir.d.ts +0 -175
  166. package/dist/http/application.d.ts +0 -18
  167. package/dist/http/application.js +0 -515
  168. package/dist/http/basic-auth.d.ts +0 -37
  169. package/dist/http/basic-auth.js +0 -67
  170. package/dist/http/definitions.d.ts +0 -363
  171. package/dist/http/definitions.js +0 -310
  172. package/dist/http/hosted-protocol.d.ts +0 -4
  173. package/dist/http/hosted-protocol.js +0 -4
  174. package/dist/http/internal.d.ts +0 -8
  175. package/dist/http/internal.js +0 -1
  176. package/dist/http/params.d.ts +0 -2
  177. package/dist/http/params.js +0 -19
  178. package/dist/http/request-body.d.ts +0 -4
  179. package/dist/http/request-body.js +0 -6
  180. package/dist/http/validate.d.ts +0 -20
  181. package/dist/http/validate.js +0 -6
  182. package/dist/message-port/environment.d.ts +0 -2
  183. package/dist/message-port/environment.js +0 -4
  184. package/dist/runtime/application.d.ts +0 -38
  185. package/dist/runtime/application.js +0 -339
  186. package/dist/runtime/pipeline.d.ts +0 -11
  187. package/dist/runtime/pipeline.js +0 -161
  188. /package/dist/{graph/ir.js → core/instrumentation.js} +0 -0
@@ -1,1118 +0,0 @@
1
- import { argumentsProvider, asModuleInstance, childPipelineOf, contextKeyName, defineModule, isArgsClass, isEnvClass, layerDefinitionOf, normalizeProvider, queueRuntimeToken, tokenName, } from '../core/index.js';
2
- import { contractOfBinding, contractRootOf, resolveContractProcedureIdentity, } from '../core/contract-internal.js';
3
- import { Container, Logger } from '../runtime/index.js';
4
- export class StaticValidationError extends Error {
5
- diagnostics;
6
- graph;
7
- constructor(diagnostics, graph) {
8
- super(diagnostics
9
- .map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`)
10
- .join('\n'));
11
- this.diagnostics = diagnostics;
12
- this.graph = graph;
13
- this.name = 'StaticValidationError';
14
- }
15
- }
16
- export function compileApplication(input) {
17
- const publicTasks = input.tasks ?? [];
18
- const triggers = input.triggers ?? [];
19
- const tasks = uniqueTasks([
20
- ...publicTasks,
21
- ...triggers.map((trigger) => trigger.task),
22
- ]);
23
- const argumentModule = input.arguments
24
- ? defineModule(() => ({
25
- name: '@loutrejs/application-arguments',
26
- providers: [argumentsProvider(input.arguments)],
27
- }))()
28
- : undefined;
29
- const modules = collectModules(argumentModule ? [...input.modules, argumentModule] : input.modules);
30
- const visibleModules = argumentModule
31
- ? modules.filter((module) => module !== argumentModule)
32
- : modules;
33
- const providers = modules.flatMap((module) => (module.definition.providers ?? []).map(normalizeProvider));
34
- const queueTriggers = triggers.filter((trigger) => trigger.trigger === 'queue-consumer');
35
- const queues = uniqueQueues(queueTriggers.map((trigger) => trigger.queue));
36
- const diagnostics = [];
37
- validateNamedDescriptors(tasks, 'LUTRE_TASK_001', diagnostics);
38
- validateNamedDescriptors(triggers, 'LUTRE_TRIGGER_DUPLICATE', diagnostics);
39
- validateNamedDescriptors(queues, 'LUTRE_QUEUE_DUPLICATE', diagnostics);
40
- validateCronTriggers(triggers, diagnostics);
41
- diagnostics.push(...validateQueueDrivers(input.modules, queueTriggers));
42
- const applicationContract = input.contract === undefined ? undefined : contractOfBinding(input.contract);
43
- const contractIds = new Map();
44
- const idContract = (contract) => {
45
- const current = contractIds.get(contract);
46
- if (current)
47
- return current;
48
- const id = `contract:${contractIds.size + 1}`;
49
- contractIds.set(contract, id);
50
- return id;
51
- };
52
- const implementationIds = new Map();
53
- const idImplementation = (implementation) => {
54
- const current = implementationIds.get(implementation);
55
- if (current)
56
- return current;
57
- const id = `implementation:${implementationIds.size + 1}`;
58
- implementationIds.set(implementation, id);
59
- return id;
60
- };
61
- if (applicationContract)
62
- idContract(applicationContract);
63
- const descriptors = modules.flatMap((module) => module.definition.implementations ?? []);
64
- for (const implementation of new Set(descriptors)) {
65
- const firstProcedure = implementation.procedures[0];
66
- const identity = firstProcedure === undefined
67
- ? { contract: contractRootOf(implementation.contract) }
68
- : resolveContractProcedureIdentity(implementation.contract, firstProcedure, applicationContract);
69
- idContract(identity.contract);
70
- idImplementation(implementation);
71
- }
72
- const targets = [];
73
- for (const implementation of descriptors) {
74
- for (const procedureName of implementation.procedures) {
75
- const identity = resolveContractProcedureIdentity(implementation.contract, procedureName, applicationContract);
76
- const procedure = implementation.contract.procedures[procedureName];
77
- const protocol = procedure?.protocols[implementation.protocol];
78
- if (!procedure || !protocol) {
79
- diagnostics.push({
80
- code: 'LUTRE_IMPL_003',
81
- message: `${procedureName} is not declared for protocol ${implementation.protocol}`,
82
- path: `${idContract(identity.contract)}.${identity.procedure}.${implementation.protocol}`,
83
- });
84
- continue;
85
- }
86
- targets.push({
87
- implementation,
88
- contractId: idContract(identity.contract),
89
- implementationId: idImplementation(implementation),
90
- procedure: identity.procedure,
91
- protocol: implementation.protocol,
92
- dispatchKey: protocol.dispatchKey,
93
- pipeline: protocol.pipeline ?? protocol.definition?.pipeline ?? [],
94
- interaction: protocol.interaction ?? protocol.definition?.interaction ?? 'unary',
95
- ...(protocol.definition?.responses === undefined
96
- ? {}
97
- : { responses: protocol.definition.responses }),
98
- });
99
- }
100
- }
101
- validateDispatchKeys(targets, diagnostics);
102
- validateCoverage(descriptors, targets, contractIds, diagnostics, applicationContract);
103
- validateDuplicateProviders(modules, diagnostics);
104
- const tokensById = collectCustomTokens(providers, targets, diagnostics);
105
- const contextKeysByName = collectContextKeys(targets, diagnostics);
106
- const pipelines = [];
107
- for (const target of targets) {
108
- validatePipeline(target, diagnostics);
109
- pipelines.push({
110
- contract: target.contractId,
111
- procedure: target.procedure,
112
- protocol: target.protocol,
113
- layers: target.pipeline.map(toLayerIR),
114
- });
115
- }
116
- const implementations = [...new Set(descriptors)].map((implementation) => {
117
- const implementationTargets = targets.filter((target) => target.implementation === implementation);
118
- return {
119
- id: idImplementation(implementation),
120
- name: implementation.name,
121
- contract: implementationTargets[0]?.contractId ??
122
- idContract(contractRootOf(implementation.contract)),
123
- protocol: implementation.protocol,
124
- procedures: implementationTargets.map((target) => target.procedure),
125
- };
126
- });
127
- const dependencyGraph = buildDependencyGraph(modules, descriptors, targets, tasks, diagnostics);
128
- const probedTokenIds = dependencyGraph.nodes
129
- .filter((node) => node.kind === 'token')
130
- .map((node) => node.label);
131
- const publicTaskSet = new Set(publicTasks);
132
- const graph = {
133
- modules: visibleModules.map((module) => {
134
- const index = modules.indexOf(module);
135
- return {
136
- id: `module:${index + 1}`,
137
- ...(module.definition.name === undefined
138
- ? {}
139
- : { name: module.definition.name }),
140
- ...(module.definition.description === undefined
141
- ? {}
142
- : { description: module.definition.description }),
143
- imports: (module.definition.imports ?? []).map((imported) => {
144
- const importedIndex = modules.indexOf(imported);
145
- return `module:${importedIndex + 1}`;
146
- }),
147
- environment: (module.definition.environment ?? []).map((environment) => environment.name),
148
- providers: (module.definition.providers ?? [])
149
- .map(normalizeProvider)
150
- .filter((provider) => provider.kind !== 'environment')
151
- .map((provider) => tokenName(provider.provide)),
152
- exports: (module.definition.exports ?? []).map((value) => typeof value === 'function'
153
- ? value.name
154
- : typeof value === 'object' &&
155
- value !== null &&
156
- 'kind' in value &&
157
- value.kind === 'token' &&
158
- 'id' in value
159
- ? String(value.id)
160
- : String(value)),
161
- lifecycle: Object.keys(module.definition.lifecycle ?? {}),
162
- requires: module.definition.requires ?? [],
163
- };
164
- }),
165
- ...(input.arguments ? { arguments: { name: input.arguments.name } } : {}),
166
- providers: dedupeProviders(providers).map((provider) => ({
167
- token: tokenName(provider.provide),
168
- kind: provider.kind,
169
- scope: provider.scope,
170
- dependencies: provider.kind === 'factory'
171
- ? provider.inject.map(tokenName)
172
- : provider.kind === 'conditional'
173
- ? [tokenName(provider.select.contract)]
174
- : [],
175
- })),
176
- tokens: [...new Set([...tokensById.keys(), ...probedTokenIds])].map((id) => ({ id })),
177
- contextKeys: [...contextKeysByName.keys()].map((name) => ({ name })),
178
- contracts: [...contractIds.entries()].map(([contract, id]) => toContractIR(contract, id)),
179
- pipelines,
180
- implementations,
181
- tasks: tasks.map((task) => ({
182
- id: `task:${task.name}`,
183
- name: task.name,
184
- public: publicTaskSet.has(task),
185
- })),
186
- queues: queues.map(toQueueIR),
187
- executions: [
188
- ...targets.map((target) => ({
189
- id: `protocol:${target.contractId}:${target.procedure}:${target.protocol}`,
190
- kind: 'protocol',
191
- protocol: target.protocol,
192
- contract: target.contractId,
193
- procedure: target.procedure,
194
- implementation: target.implementationId,
195
- })),
196
- ...publicTasks.map((task) => ({
197
- id: `task:${task.name}`,
198
- kind: 'task',
199
- name: task.name,
200
- })),
201
- ...triggers.map(toTriggerExecution),
202
- ],
203
- capabilities: [
204
- ...targets.flatMap((target) => {
205
- const requiredBy = `${target.contractId}.${target.procedure}`;
206
- return [
207
- { name: 'crypto.random', scope: 'execution', requiredBy },
208
- ...(target.protocol === 'http'
209
- ? [{ name: 'http.server', scope: 'execution', requiredBy }]
210
- : []),
211
- ...(target.protocol === 'messagePort'
212
- ? [
213
- {
214
- name: 'messagePort.send',
215
- scope: 'execution',
216
- requiredBy,
217
- },
218
- {
219
- name: 'messagePort.receive',
220
- scope: 'execution',
221
- requiredBy,
222
- },
223
- ]
224
- : []),
225
- ...(target.interaction === 'server-stream'
226
- ? [
227
- {
228
- name: 'stream.readable',
229
- scope: 'execution',
230
- requiredBy,
231
- },
232
- ...(target.protocol === 'http'
233
- ? [
234
- {
235
- name: 'http.response.streaming',
236
- scope: 'execution',
237
- requiredBy,
238
- },
239
- ]
240
- : []),
241
- ]
242
- : []),
243
- ];
244
- }),
245
- ...visibleModules.flatMap((module) => {
246
- const index = modules.indexOf(module);
247
- return [
248
- ...(module.definition.environment?.length
249
- ? [
250
- {
251
- name: 'env.runtime',
252
- scope: 'application',
253
- requiredBy: `module:${index + 1}`,
254
- },
255
- ]
256
- : []),
257
- ...(module.definition.requires ?? []).map((name) => ({
258
- name,
259
- scope: 'application',
260
- requiredBy: `module:${index + 1}`,
261
- })),
262
- ];
263
- }),
264
- ...(input.arguments
265
- ? [
266
- {
267
- name: 'args.runtime',
268
- scope: 'application',
269
- requiredBy: 'application',
270
- },
271
- ]
272
- : []),
273
- ],
274
- hostCapabilities: collectHostCapabilities(input.modules),
275
- ...dependencyGraph,
276
- diagnostics,
277
- };
278
- return { graph, diagnostics };
279
- }
280
- function validateDispatchKeys(targets, diagnostics) {
281
- const targetsByKey = new Map();
282
- for (const target of targets) {
283
- if (target.dispatchKey === null)
284
- continue;
285
- const path = `${target.contractId}.${target.procedure}.${target.protocol}`;
286
- const existing = targetsByKey.get(target.dispatchKey);
287
- if (existing) {
288
- if (existing.contractId === target.contractId &&
289
- existing.procedure === target.procedure &&
290
- existing.protocol === target.protocol) {
291
- continue;
292
- }
293
- const existingPath = `${existing.contractId}.${existing.procedure}.${existing.protocol}`;
294
- diagnostics.push({
295
- code: 'LUTRE_PROTOCOL_001',
296
- message: `Duplicate protocol dispatch key "${target.dispatchKey}": ${existingPath}, ${path}`,
297
- path,
298
- });
299
- continue;
300
- }
301
- targetsByKey.set(target.dispatchKey, target);
302
- }
303
- }
304
- export const buildApplicationGraph = compileApplication;
305
- function validateNamedDescriptors(descriptors, code, diagnostics) {
306
- const names = new Map();
307
- for (const descriptor of descriptors) {
308
- const existing = names.get(descriptor.name);
309
- if (existing && existing !== descriptor) {
310
- diagnostics.push({
311
- code,
312
- message: `Name ${descriptor.name} is declared by multiple descriptors.`,
313
- path: descriptor.name,
314
- });
315
- continue;
316
- }
317
- names.set(descriptor.name, descriptor);
318
- }
319
- }
320
- function validateCronTriggers(triggers, diagnostics) {
321
- for (const trigger of triggers) {
322
- if (trigger.trigger !== 'cron')
323
- continue;
324
- if (!isValidCronExpression(trigger.expression)) {
325
- diagnostics.push({
326
- code: 'LUTRE_TRIGGER_INVALID_CRON',
327
- message: `Trigger ${trigger.name} must use a portable 5-field cron expression.`,
328
- path: `trigger:${trigger.name}`,
329
- });
330
- }
331
- try {
332
- new Intl.DateTimeFormat('en-US', {
333
- timeZone: trigger.timezone,
334
- }).format();
335
- }
336
- catch {
337
- diagnostics.push({
338
- code: 'LUTRE_TRIGGER_INVALID_TIMEZONE',
339
- message: `Trigger ${trigger.name} must use a valid IANA timezone.`,
340
- path: `trigger:${trigger.name}`,
341
- });
342
- }
343
- }
344
- }
345
- function isValidCronExpression(expression) {
346
- const fields = expression.trim().split(/\s+/);
347
- if (fields.length !== 5)
348
- return false;
349
- const ranges = [
350
- [0, 59],
351
- [0, 23],
352
- [1, 31],
353
- [1, 12],
354
- [0, 7],
355
- ];
356
- return fields.every((field, index) => {
357
- const range = ranges[index];
358
- return range !== undefined && isValidCronField(field, range[0], range[1]);
359
- });
360
- }
361
- function isValidCronField(field, minimum, maximum) {
362
- return field.split(',').every((segment) => {
363
- const parts = segment.split('/');
364
- if (parts.length > 2)
365
- return false;
366
- const rangeExpression = parts[0];
367
- const stepExpression = parts[1];
368
- if (!rangeExpression)
369
- return false;
370
- if (stepExpression !== undefined &&
371
- (!/^\d+$/.test(stepExpression) || Number(stepExpression) <= 0)) {
372
- return false;
373
- }
374
- if (rangeExpression === '*')
375
- return true;
376
- const bounds = rangeExpression.split('-');
377
- if (bounds.length > 2 || bounds.some((bound) => !/^\d+$/.test(bound))) {
378
- return false;
379
- }
380
- const start = Number(bounds[0]);
381
- const end = bounds[1] === undefined ? start : Number(bounds[1]);
382
- return start >= minimum && end <= maximum && start <= end;
383
- });
384
- }
385
- function uniqueTasks(tasks) {
386
- return [...new Set(tasks)];
387
- }
388
- function uniqueQueues(queues) {
389
- return [...new Set(queues)];
390
- }
391
- function toTriggerExecution(trigger) {
392
- switch (trigger.trigger) {
393
- case 'cron':
394
- return {
395
- id: `trigger:${trigger.name}`,
396
- kind: 'trigger',
397
- trigger: 'cron',
398
- name: trigger.name,
399
- expression: trigger.expression,
400
- timezone: trigger.timezone,
401
- overlap: trigger.overlap,
402
- task: trigger.task.name,
403
- };
404
- case 'fixed-delay':
405
- return {
406
- id: `trigger:${trigger.name}`,
407
- kind: 'trigger',
408
- trigger: 'fixed-delay',
409
- name: trigger.name,
410
- delay: trigger.delay,
411
- immediate: trigger.immediate,
412
- task: trigger.task.name,
413
- };
414
- case 'queue-consumer':
415
- return {
416
- id: `trigger:${trigger.name}`,
417
- kind: 'trigger',
418
- trigger: 'queue-consumer',
419
- name: trigger.name,
420
- queue: trigger.queue.name,
421
- task: trigger.task.name,
422
- };
423
- }
424
- }
425
- function toQueueIR(queue) {
426
- return {
427
- id: `queue:${queue.name}`,
428
- name: queue.name,
429
- payloadSchema: schemaIdentity(queue.payload),
430
- };
431
- }
432
- function schemaIdentity(schema) {
433
- const standard = schema['~standard'];
434
- const vendor = typeof standard.vendor === 'string' ? standard.vendor : 'standard-schema';
435
- const version = typeof standard.version === 'number' || typeof standard.version === 'string'
436
- ? String(standard.version)
437
- : '1';
438
- return `${vendor}@${version}`;
439
- }
440
- function validateQueueDrivers(roots, triggers) {
441
- const providers = collectModules(roots).flatMap((module) => (module.definition.providers ?? []).map(normalizeProvider));
442
- return triggers.flatMap((trigger) => {
443
- const runtimeToken = queueRuntimeToken(trigger.queue);
444
- return providers.some((provider) => provider.provide === runtimeToken)
445
- ? []
446
- : [
447
- {
448
- code: 'LUTRE_QUEUE_DRIVER_UNBOUND',
449
- message: `Queue ${trigger.queue.name} has a consumer trigger but no runtime driver binding.`,
450
- path: `trigger:${trigger.name}`,
451
- },
452
- ];
453
- });
454
- }
455
- function collectHostCapabilities(roots) {
456
- return [
457
- ...new Set(collectModules(roots).flatMap((module) => (module.definition.implementations ?? []).flatMap((implementation) => implementation.capabilities))),
458
- ];
459
- }
460
- export function validateGraph(graph) {
461
- return graph.diagnostics;
462
- }
463
- function buildDependencyGraph(modules, descriptors, targets, tasks, diagnostics) {
464
- const nodes = [];
465
- const edges = [];
466
- const ids = new Map();
467
- const implementationIds = new Map();
468
- const taskIds = new Map();
469
- const modulesByProvider = new Map();
470
- const providersByToken = new Map();
471
- const customTokensById = new Map();
472
- modules.forEach((module, index) => {
473
- const moduleId = `module:${index + 1}`;
474
- for (const declaration of module.definition.providers ?? []) {
475
- const provider = normalizeProvider(declaration);
476
- if (!modulesByProvider.has(provider.provide)) {
477
- modulesByProvider.set(provider.provide, moduleId);
478
- }
479
- if (!providersByToken.has(provider.provide)) {
480
- providersByToken.set(provider.provide, provider);
481
- }
482
- }
483
- });
484
- const ensureNode = (token, overrides = {}) => {
485
- if (typeof token !== 'function') {
486
- const registered = customTokensById.get(token.id);
487
- if (registered &&
488
- registered !== token &&
489
- !diagnostics.some((diagnostic) => diagnostic.code === 'LUTRE_TOKEN_001' &&
490
- diagnostic.message.includes(token.id))) {
491
- diagnostics.push({
492
- code: 'LUTRE_TOKEN_001',
493
- message: `Token ID ${token.id} is duplicated across different token declarations`,
494
- path: `dependency:${token.id}`,
495
- });
496
- }
497
- else if (!registered) {
498
- customTokensById.set(token.id, token);
499
- }
500
- }
501
- const current = ids.get(token);
502
- if (current)
503
- return current;
504
- const base = isArgsClass(token)
505
- ? `arguments:${token.name}`
506
- : typeof token === 'function'
507
- ? `class:${token.name}`
508
- : `token:${token.id}`;
509
- let id = base;
510
- let sequence = 2;
511
- while (nodes.some((node) => node.id === id))
512
- id = `${base}:${sequence++}`;
513
- ids.set(token, id);
514
- const provider = providersByToken.get(token);
515
- const scope = overrides.scope ?? provider?.scope;
516
- const module = provider?.kind === 'arguments'
517
- ? undefined
518
- : (overrides.module ?? modulesByProvider.get(token));
519
- nodes.push({
520
- id,
521
- label: tokenName(token),
522
- kind: overrides.kind ??
523
- (provider?.kind === 'environment' || isEnvClass(token)
524
- ? 'environment'
525
- : provider?.kind === 'arguments' || isArgsClass(token)
526
- ? 'arguments'
527
- : typeof token === 'function'
528
- ? 'class'
529
- : 'token'),
530
- ...(scope === undefined ? {} : { scope }),
531
- ...(module === undefined ? {} : { module }),
532
- });
533
- return id;
534
- };
535
- const ensureImplementationNode = (implementation) => {
536
- const current = implementationIds.get(implementation);
537
- if (current)
538
- return current;
539
- const id = `implementation:${implementationIds.size + 1}`;
540
- implementationIds.set(implementation, id);
541
- nodes.push({
542
- id,
543
- label: implementation.name,
544
- kind: 'implementation',
545
- scope: 'application',
546
- });
547
- return id;
548
- };
549
- const ensureTaskNode = (task) => {
550
- const current = taskIds.get(task);
551
- if (current)
552
- return current;
553
- const id = `task:${task.name}`;
554
- taskIds.set(task, id);
555
- nodes.push({
556
- id,
557
- label: task.name,
558
- kind: 'task',
559
- scope: 'application',
560
- });
561
- return id;
562
- };
563
- const addEdge = (edge) => {
564
- if (!edges.some((candidate) => candidate.from === edge.from &&
565
- candidate.to === edge.to &&
566
- candidate.kind === edge.kind &&
567
- candidate.source === edge.source &&
568
- candidate.condition?.key === edge.condition?.key &&
569
- candidate.condition?.equals === edge.condition?.equals)) {
570
- edges.push(edge);
571
- }
572
- };
573
- const validateDeclaredDependency = (dependency, path) => {
574
- if (providersByToken.has(dependency) ||
575
- dependency === Logger)
576
- return;
577
- if (isEnvClass(dependency)) {
578
- diagnostics.push({
579
- code: 'LUTRE_ENV_002',
580
- message: `${dependency.name} is injected but is not declared by any Module.environment.`,
581
- path,
582
- });
583
- return;
584
- }
585
- if (isArgsClass(dependency)) {
586
- diagnostics.push({
587
- code: 'LUTRE_ARGS_002',
588
- message: `${dependency.name} is injected but is not declared by Application.arguments.`,
589
- path,
590
- });
591
- return;
592
- }
593
- diagnostics.push({
594
- code: 'LUTRE_DI_UNRESOLVED',
595
- message: `${path} requires ${tokenName(dependency)}, but no provider is declared for ${tokenName(dependency)}.`,
596
- path,
597
- });
598
- };
599
- for (const provider of providersByToken.values()) {
600
- const providerId = ensureNode(provider.provide);
601
- if (provider.kind === 'class' && provider.provide !== provider.useClass) {
602
- addEdge({
603
- from: providerId,
604
- to: ensureNode(provider.useClass, { scope: provider.scope }),
605
- kind: 'framework',
606
- source: 'declared',
607
- });
608
- }
609
- if (provider.kind === 'factory') {
610
- for (const dependency of provider.inject) {
611
- validateDeclaredDependency(dependency, tokenName(provider.provide));
612
- addEdge({
613
- from: providerId,
614
- to: ensureNode(dependency),
615
- kind: 'factory',
616
- source: 'declared',
617
- });
618
- }
619
- if (provider.useFactory.constructor.name === 'AsyncFunction') {
620
- diagnostics.push({
621
- code: 'LUTRE_DI_ASYNC_FACTORY',
622
- message: 'Async factory providers are not supported. Move asynchronous resource initialization to application lifecycle.',
623
- path: tokenName(provider.provide),
624
- });
625
- }
626
- }
627
- if (provider.kind === 'conditional') {
628
- validateDeclaredDependency(provider.select.contract, tokenName(provider.provide));
629
- addEdge({
630
- from: providerId,
631
- to: ensureNode(provider.select.contract),
632
- kind: 'conditional',
633
- source: 'declared',
634
- });
635
- for (const equals of Reflect.ownKeys(provider.mapping)) {
636
- const candidate = provider.mapping[equals];
637
- if (!candidate)
638
- continue;
639
- addEdge({
640
- from: providerId,
641
- to: ensureNode(candidate, { scope: provider.scope }),
642
- kind: 'conditional',
643
- source: 'declared',
644
- condition: {
645
- source: provider.select.source,
646
- contract: tokenName(provider.select.contract),
647
- key: provider.select.key,
648
- equals,
649
- },
650
- });
651
- }
652
- }
653
- }
654
- for (const implementation of descriptors) {
655
- ensureImplementationNode(implementation);
656
- }
657
- modules.forEach((module, index) => {
658
- const lifecycle = module.definition.lifecycle;
659
- if (!lifecycle)
660
- return;
661
- for (const [hookName, hook] of Object.entries(lifecycle)) {
662
- if (!hook)
663
- continue;
664
- const hookId = `lifecycle:module:${index + 1}:${hookName}`;
665
- nodes.push({
666
- id: hookId,
667
- label: `${hookName} (module:${index + 1})`,
668
- kind: 'framework',
669
- });
670
- for (const dependency of hook.inject) {
671
- validateDeclaredDependency(dependency, hookId);
672
- addEdge({
673
- from: hookId,
674
- to: ensureNode(dependency),
675
- kind: 'lifecycle',
676
- source: 'declared',
677
- });
678
- }
679
- }
680
- });
681
- const recorder = {
682
- record(consumer, dependency) {
683
- addEdge({
684
- from: ensureConsumerNode(consumer, nodes, ensureNode),
685
- to: ensureNode(dependency),
686
- kind: 'inject',
687
- source: 'probed',
688
- });
689
- },
690
- };
691
- const container = new Container([...providersByToken.values()], {
692
- recorder,
693
- probe: true,
694
- });
695
- const managedClasses = new Set();
696
- for (const provider of providersByToken.values()) {
697
- if (provider.kind === 'class')
698
- managedClasses.add(provider.useClass);
699
- if (provider.kind === 'conditional') {
700
- for (const key of Reflect.ownKeys(provider.mapping)) {
701
- const candidate = provider.mapping[key];
702
- if (candidate)
703
- managedClasses.add(candidate);
704
- }
705
- }
706
- }
707
- for (const target of managedClasses) {
708
- try {
709
- container.probeClass(target);
710
- }
711
- catch (error) {
712
- const message = error instanceof Error ? error.message : String(error);
713
- diagnostics.push({
714
- code: diagnosticCode(message),
715
- message,
716
- path: target.name,
717
- });
718
- }
719
- }
720
- const uniqueImplementations = [...new Set(descriptors)];
721
- uniqueImplementations.forEach((implementation, index) => {
722
- const consumer = {
723
- kind: 'implementation-consumer',
724
- id: ensureImplementationNode(implementation),
725
- name: implementation.name,
726
- };
727
- try {
728
- container.probeImplementation(implementation, consumer);
729
- }
730
- catch (error) {
731
- const message = error instanceof Error ? error.message : String(error);
732
- diagnostics.push({
733
- code: diagnosticCode(message),
734
- message,
735
- path: `implementation:${index + 1}:${implementation.name}`,
736
- });
737
- }
738
- });
739
- tasks.forEach((task) => {
740
- const consumer = {
741
- kind: 'task-consumer',
742
- id: ensureTaskNode(task),
743
- name: task.name,
744
- };
745
- try {
746
- container.probeTask(task, consumer);
747
- }
748
- catch (error) {
749
- const message = error instanceof Error ? error.message : String(error);
750
- diagnostics.push({
751
- code: diagnosticCode(message),
752
- message,
753
- path: consumer.id,
754
- });
755
- }
756
- });
757
- for (const target of targets) {
758
- visitPipelineItems(target.pipeline, (item, indexPath) => {
759
- if (item.kind !== 'layer')
760
- return;
761
- const definition = layerDefinitionOf(item);
762
- const consumer = {
763
- kind: 'layer-consumer',
764
- id: `layer:${target.contractId}/${target.procedure}/${target.protocol}/${indexPath.join('.')}`,
765
- name: definition.name,
766
- };
767
- if (!nodes.some(({ id }) => id === consumer.id)) {
768
- nodes.push({ id: consumer.id, label: consumer.name, kind: 'layer' });
769
- }
770
- try {
771
- container.probeLayer(definition, consumer);
772
- }
773
- catch (error) {
774
- const message = error instanceof Error ? error.message : String(error);
775
- diagnostics.push({
776
- code: diagnosticCode(message),
777
- message,
778
- path: consumer.id,
779
- });
780
- }
781
- });
782
- }
783
- return { nodes, edges };
784
- }
785
- function diagnosticCode(message) {
786
- for (const code of [
787
- 'LUTRE_ENV_001',
788
- 'LUTRE_ENV_002',
789
- 'LUTRE_ENV_004',
790
- 'LUTRE_ARGS_001',
791
- 'LUTRE_ARGS_002',
792
- 'LUTRE_DI_CYCLE',
793
- 'LUTRE_DI_ASYNC_FACTORY',
794
- 'LUTRE_DI_CONSTRUCTOR',
795
- 'LUTRE_IMPL_ASYNC_FACTORY',
796
- 'LUTRE_IMPL_FACTORY_RESULT',
797
- 'LUTRE_IMPL_004',
798
- 'LUTRE_LAYER_ASYNC_FACTORY',
799
- 'LUTRE_LAYER_FACTORY_RESULT',
800
- 'LUTRE_TASK_ASYNC_FACTORY',
801
- 'LUTRE_TASK_FACTORY_RESULT',
802
- ]) {
803
- if (message.includes(code))
804
- return code;
805
- }
806
- return 'LUTRE_DI_UNRESOLVED';
807
- }
808
- function validateDuplicateProviders(modules, diagnostics) {
809
- const declarations = new Map();
810
- modules.forEach((module, moduleIndex) => {
811
- const moduleName = describeModule(module, moduleIndex);
812
- for (const declaration of module.definition.providers ?? []) {
813
- const provider = normalizeProvider(declaration);
814
- const existing = declarations.get(provider.provide);
815
- if (existing) {
816
- if (existing.provider.kind === 'environment' &&
817
- provider.kind === 'environment') {
818
- continue;
819
- }
820
- if (existing.provider.kind === 'environment' ||
821
- provider.kind === 'environment') {
822
- diagnostics.push({
823
- code: 'LUTRE_ENV_001',
824
- message: `Environment ${tokenName(provider.provide)} is runtime-managed and cannot also be declared as a normal provider.`,
825
- path: `${moduleName}.environment.${tokenName(provider.provide)}`,
826
- });
827
- continue;
828
- }
829
- diagnostics.push({
830
- code: 'LUTRE_DI_003',
831
- message: `Provider ${tokenName(provider.provide)} is duplicated in ${existing.module} and ${moduleName}`,
832
- path: `${moduleName}.providers.${tokenName(provider.provide)}`,
833
- });
834
- continue;
835
- }
836
- declarations.set(provider.provide, { module: moduleName, provider });
837
- }
838
- });
839
- }
840
- function describeModule(module, index) {
841
- const id = `module:${index + 1}`;
842
- return module.definition.description === undefined
843
- ? id
844
- : `${id} (${module.definition.description})`;
845
- }
846
- function collectCustomTokens(providers, targets, diagnostics) {
847
- const tokens = new Map();
848
- const register = (candidate, path) => {
849
- if (typeof candidate === 'function')
850
- return;
851
- const existing = tokens.get(candidate.id);
852
- if (existing && existing !== candidate) {
853
- diagnostics.push({
854
- code: 'LUTRE_TOKEN_001',
855
- message: `Token ID ${candidate.id} is duplicated across different token declarations`,
856
- path,
857
- });
858
- return;
859
- }
860
- tokens.set(candidate.id, candidate);
861
- };
862
- for (const provider of providers) {
863
- register(provider.provide, `provider:${tokenName(provider.provide)}`);
864
- if (provider.kind === 'factory') {
865
- for (const dependency of provider.inject) {
866
- register(dependency, `provider:${tokenName(provider.provide)}`);
867
- }
868
- }
869
- }
870
- return tokens;
871
- }
872
- function collectContextKeys(targets, diagnostics) {
873
- const keys = new Map();
874
- for (const target of targets) {
875
- const path = `${target.contractId}.${target.procedure}.${target.protocol}`;
876
- visitPipelineItems(target.pipeline, (item) => {
877
- if (item.kind !== 'layer')
878
- return;
879
- for (const key of [...item.requires, ...item.provides]) {
880
- const existing = keys.get(key.name);
881
- if (existing && existing !== key) {
882
- diagnostics.push({
883
- code: 'LUTRE_CONTEXT_002',
884
- message: `Context Key ${key.name} is duplicated across different declarations`,
885
- path,
886
- });
887
- continue;
888
- }
889
- keys.set(key.name, key);
890
- }
891
- });
892
- }
893
- return keys;
894
- }
895
- function toContractIR(contract, id) {
896
- return {
897
- id,
898
- procedures: Object.entries(contract.procedures).map(([procedureName, procedure]) => ({
899
- name: procedureName,
900
- protocols: Object.entries(procedure.protocols).map(([protocolName, protocol]) => ({
901
- name: protocolName,
902
- dispatchKey: protocol.dispatchKey,
903
- interaction: protocol.interaction ?? 'unary',
904
- })),
905
- })),
906
- };
907
- }
908
- export function assertValidCompilation(result) {
909
- if (result.diagnostics.length > 0) {
910
- throw new StaticValidationError(result.diagnostics, result.graph);
911
- }
912
- return result.graph;
913
- }
914
- function collectModules(roots) {
915
- const modules = [];
916
- const seen = new Set();
917
- const visit = (candidate) => {
918
- const module = asModuleInstance(candidate);
919
- if (seen.has(module))
920
- return;
921
- seen.add(module);
922
- for (const imported of module.definition.imports ?? [])
923
- visit(imported);
924
- modules.push(module);
925
- };
926
- for (const root of roots)
927
- visit(root);
928
- return modules;
929
- }
930
- function validateCoverage(implementations, targets, contractIds, diagnostics, applicationContract) {
931
- const contracts = applicationContract
932
- ? [applicationContract]
933
- : [
934
- ...new Set(implementations.map((implementation) => contractRootOf(implementation.contract))),
935
- ];
936
- for (const contract of contracts) {
937
- const contractId = contractIds.get(contract) ?? 'contract:unknown';
938
- for (const [procedureName, procedure] of Object.entries(contract.procedures)) {
939
- for (const protocol of Object.keys(procedure.protocols)) {
940
- const covering = targets.filter((target) => target.contractId === contractId &&
941
- target.protocol === protocol &&
942
- target.procedure === procedureName);
943
- const path = `${contractId}.${procedureName}.${protocol}`;
944
- if (covering.length === 0) {
945
- diagnostics.push({
946
- code: 'LUTRE_IMPL_001',
947
- message: `Missing implementation for ${path}`,
948
- path,
949
- });
950
- }
951
- else if (covering.length > 1) {
952
- diagnostics.push({
953
- code: 'LUTRE_IMPL_002',
954
- message: `Duplicate implementation for ${path}`,
955
- path,
956
- });
957
- }
958
- }
959
- }
960
- }
961
- }
962
- function validatePipeline(target, diagnostics) {
963
- const path = `${target.contractId}.${target.procedure}.${target.protocol}`;
964
- const flattened = [];
965
- visitPipelineItems(target.pipeline, (item, indexPath) => {
966
- flattened.push({ item, indexPath });
967
- });
968
- const terminals = flattened.filter(({ item }) => item.kind === 'terminal');
969
- if (terminals.length !== 1) {
970
- diagnostics.push({
971
- code: 'LUTRE_PIPELINE_001',
972
- message: `Pipeline must contain exactly one ${target.protocol} terminal`,
973
- path,
974
- });
975
- }
976
- else {
977
- const terminal = terminals[0];
978
- if (terminal !== flattened.at(-1)) {
979
- diagnostics.push({
980
- code: 'LUTRE_PIPELINE_002',
981
- message: `${terminal.item.name} must be the final Pipeline item`,
982
- path,
983
- });
984
- }
985
- if (terminal.item.kind === 'terminal' &&
986
- terminal.item.protocol !== target.protocol) {
987
- diagnostics.push({
988
- code: 'LUTRE_PIPELINE_003',
989
- message: `${terminal.item.name} does not match protocol ${target.protocol}`,
990
- path,
991
- });
992
- }
993
- }
994
- const available = new Set();
995
- const validated = new Set();
996
- for (const { item } of flattened) {
997
- if (item.kind === 'validation') {
998
- validated.add(item.part);
999
- continue;
1000
- }
1001
- if (item.kind !== 'layer')
1002
- continue;
1003
- for (const required of item.requiresValidated) {
1004
- if (!validated.has(required)) {
1005
- diagnostics.push({
1006
- code: 'LUTRE_VALIDATION_001',
1007
- message: `${item.name} requires validated ${required} but appears before validate.${required}`,
1008
- path,
1009
- });
1010
- }
1011
- }
1012
- for (const required of item.requires) {
1013
- if (!available.has(required)) {
1014
- diagnostics.push({
1015
- code: 'LUTRE_PIPELINE_004',
1016
- message: `Context Key ${contextKeyName(required)} required by ${item.name} is unavailable`,
1017
- path,
1018
- });
1019
- }
1020
- }
1021
- for (const provided of item.provides) {
1022
- if (available.has(provided)) {
1023
- diagnostics.push({
1024
- code: 'LUTRE_CONTEXT_003',
1025
- message: `${item.name} cannot implicitly overwrite existing Context Key ${contextKeyName(provided)}`,
1026
- path,
1027
- });
1028
- }
1029
- available.add(provided);
1030
- }
1031
- for (const shortCircuit of item.shortCircuits) {
1032
- if (shortCircuit.protocol !== target.protocol)
1033
- continue;
1034
- const response = target.responses?.[shortCircuit.variant];
1035
- if (!response) {
1036
- diagnostics.push({
1037
- code: 'LUTRE_SHORT_CIRCUIT_001',
1038
- message: `Short-circuit variant ${shortCircuit.variant} from ${item.name} is not declared in the response`,
1039
- path,
1040
- });
1041
- continue;
1042
- }
1043
- const expectedStatus = shortCircuit.response?.status;
1044
- if (typeof expectedStatus === 'number' &&
1045
- response.status !== expectedStatus) {
1046
- diagnostics.push({
1047
- code: 'LUTRE_SHORT_CIRCUIT_002',
1048
- message: `Short-circuit variant ${shortCircuit.variant} from ${item.name} must use HTTP ${expectedStatus}`,
1049
- path,
1050
- });
1051
- }
1052
- }
1053
- }
1054
- }
1055
- function toLayerIR(item, index) {
1056
- const child = item.kind === 'layer' ? childPipelineOf(item) : undefined;
1057
- return {
1058
- index,
1059
- name: item.name,
1060
- role: item.role,
1061
- requires: item.kind === 'layer' ? item.requires.map(contextKeyName) : [],
1062
- provides: item.kind === 'layer' ? item.provides.map(contextKeyName) : [],
1063
- requiresValidated: item.kind === 'layer' ? item.requiresValidated : [],
1064
- ...(child === undefined ? {} : { pipeline: child.map(toLayerIR) }),
1065
- ...(item.kind !== 'layer' || item.shortCircuits.length === 0
1066
- ? {}
1067
- : {
1068
- shortCircuits: item.shortCircuits.map((shortCircuit) => ({
1069
- protocol: shortCircuit.protocol,
1070
- variant: shortCircuit.variant,
1071
- ...(shortCircuit.response === undefined
1072
- ? {}
1073
- : { response: shortCircuit.response }),
1074
- })),
1075
- }),
1076
- };
1077
- }
1078
- function visitPipelineItems(pipeline, visit, parentPath = []) {
1079
- pipeline.forEach((item, index) => {
1080
- const indexPath = [...parentPath, index];
1081
- visit(item, indexPath);
1082
- if (item.kind === 'layer') {
1083
- const child = childPipelineOf(item);
1084
- if (child)
1085
- visitPipelineItems(child, visit, indexPath);
1086
- }
1087
- });
1088
- }
1089
- function ensureConsumerNode(consumer, nodes, ensureTokenNode) {
1090
- if (typeof consumer === 'function' || consumer.kind === 'token') {
1091
- return ensureTokenNode(consumer);
1092
- }
1093
- if (!nodes.some(({ id }) => id === consumer.id)) {
1094
- nodes.push({
1095
- id: consumer.id,
1096
- label: consumer.name,
1097
- kind: consumer.kind === 'implementation-consumer'
1098
- ? 'implementation'
1099
- : consumer.kind === 'task-consumer'
1100
- ? 'task'
1101
- : 'layer',
1102
- });
1103
- }
1104
- return consumer.id;
1105
- }
1106
- function dedupeProviders(providers) {
1107
- const result = [];
1108
- const seenEnvironment = new Set();
1109
- for (const provider of providers) {
1110
- if (provider.kind === 'environment') {
1111
- if (seenEnvironment.has(provider.provide))
1112
- continue;
1113
- seenEnvironment.add(provider.provide);
1114
- }
1115
- result.push(provider);
1116
- }
1117
- return result;
1118
- }