@kudzujs/core 0.8.36 → 0.8.38
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/MIGRATION_ROADMAP.md +1 -1
- package/PERFORMANCE.md +50 -0
- package/README.md +1 -1
- package/RELEASES.md +64 -0
- package/docs/next-architecture/README.md +1 -1
- package/docs/next-architecture/compiler-current-architecture.md +14 -12
- package/docs/next-architecture/large-application-ai-native-roadmap.md +7 -1
- package/docs/next-architecture/versioning.md +3 -1
- package/framework/README.md +1 -1
- package/framework/build.mjs +48 -43
- package/framework/compiler/analysis/binding-index.mjs +15 -1
- package/framework/compiler/analysis/component-analysis.mjs +8 -2
- package/framework/compiler/descriptor-session.mjs +72 -28
- package/framework/compiler/ir/module-ir.mjs +141 -30
- package/framework/compiler/route-build-record.mjs +81 -0
- package/framework/compiler/route-capability-planner.mjs +6 -3
- package/framework/compiler/source-compiler.mjs +108 -44
- package/framework/core.d.ts +1 -1
- package/framework/core.mjs +28 -11
- package/package.json +1 -1
|
@@ -3,23 +3,45 @@ import { createComponentAnalysis } from "./analysis/component-analysis.mjs"
|
|
|
3
3
|
import { knownGlobalNames } from "./analysis/binding-index.mjs"
|
|
4
4
|
import { bindingNames, isFunctionLike, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, unwrapExpression } from "./ast-helpers.mjs"
|
|
5
5
|
import { generateCommandBehavior } from "./codegen/command-codegen.mjs"
|
|
6
|
-
import { assertModuleIRReferences, createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler } from "./ir/module-ir.mjs"
|
|
6
|
+
import { assertModuleIRReferences, createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler, registerSignal } from "./ir/module-ir.mjs"
|
|
7
7
|
|
|
8
8
|
export function createSemanticArtifact(file) {
|
|
9
9
|
return { componentAnalysis: createComponentAnalysis(file), moduleIR: createModuleIR(file) }
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export function createDescriptorSession({ semantic, handlerUrl, factory, context, bindingIndex, compileEventCommand, handlerLowering, isPrimitiveLiteral, rejectWorkerConstructions, sourceName = source => source.fileName }) {
|
|
12
|
+
export function createDescriptorSession({ semantic, handlerUrl, factory, context, bindingIndex, compileEventCommand, handlerLowering, isPrimitiveLiteral, rejectWorkerConstructions, stateReferences = () => new Map(), symbolReference, sourceName = source => source.fileName }) {
|
|
13
13
|
const { moduleIR } = semantic
|
|
14
|
+
moduleIR.symbols = bindingIndex.bindings()
|
|
14
15
|
const nativeHandlers = []
|
|
15
16
|
const effectHandlers = []
|
|
16
17
|
const reactiveBindings = []
|
|
17
18
|
const listExpressions = []
|
|
19
|
+
const pendingEffects = []
|
|
18
20
|
const clientModules = new Set()
|
|
19
21
|
|
|
22
|
+
const signal = (name, node, references, aliases = []) => {
|
|
23
|
+
let reference = references?.get(name) ?? stateReferences(node).get(name)
|
|
24
|
+
if (!reference && bindingIndex) {
|
|
25
|
+
const original = ts.getOriginalNode(node)
|
|
26
|
+
const names = new Set([name, ...aliases])
|
|
27
|
+
let symbol = bindingIndex.references(original, original)?.find(entry => names.has(entry.debugName))?.slot
|
|
28
|
+
if (symbol === undefined) {
|
|
29
|
+
for (let current = node; current && symbol === undefined; current = current.parent) if (isFunctionLike(current)) {
|
|
30
|
+
const parameter = current.parameters.map(parameter => bindingIdentifier(parameter.name, names)).find(Boolean)
|
|
31
|
+
symbol = parameter && bindingIndex.resolveBinding(parameter)?.slot
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (symbol !== undefined) reference = { kind: "symbol", symbol }
|
|
36
|
+
}
|
|
37
|
+
reference ??= symbolReference?.(name, node, aliases)
|
|
38
|
+
if (!reference) throw new Error(`ModuleIR state ${JSON.stringify(name)} has no resolved StateRef`)
|
|
39
|
+
return registerSignal(moduleIR, reference, name).slot
|
|
40
|
+
}
|
|
41
|
+
|
|
20
42
|
function compileListExpression(read, expression, item, index, states = new Set(), keyedBlock, indexedBindingIndex) {
|
|
21
43
|
const exportName = `listExpression${listExpressions.length}`
|
|
22
|
-
listExpressions.push({ exportName, expression, item, index, states, role: "list-expression", keyedBlock, bindingIndex: indexedBindingIndex })
|
|
44
|
+
listExpressions.push({ exportName, expression, item, index, states, signalRefs: new Map([...states].map(name => [name, signal(name, expression)])), role: "list-expression", keyedBlock, bindingIndex: indexedBindingIndex })
|
|
23
45
|
const arguments_ = [read, factory.createStringLiteral(handlerUrl), factory.createStringLiteral(exportName)]
|
|
24
46
|
if (states.size) arguments_.push(factory.createArrayLiteralExpression([...states].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))))
|
|
25
47
|
return factory.createCallExpression(factory.createIdentifier("__kListExpression"), undefined, arguments_)
|
|
@@ -28,7 +50,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
28
50
|
function compileListConditional(entry) {
|
|
29
51
|
const exportName = `listExpression${listExpressions.length}`
|
|
30
52
|
const indexed = indexedReferences(bindingIndex, entry.condition, entry.condition)
|
|
31
|
-
listExpressions.push({ exportName, expression: entry.condition, item: entry.item, index: entry.index, role: "list-conditional", keyedBlock: entry.keyedBlock, bindingIndex: indexed ? bindingIndex : undefined })
|
|
53
|
+
listExpressions.push({ exportName, expression: entry.condition, item: entry.item, index: entry.index, states: new Set(), signalRefs: new Map(), role: "list-conditional", keyedBlock: entry.keyedBlock, bindingIndex: indexed ? bindingIndex : undefined })
|
|
32
54
|
const read = factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), entry.condition)
|
|
33
55
|
const thunk = branch => factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), branch)
|
|
34
56
|
return factory.createCallExpression(factory.createIdentifier("__kListConditional"), undefined, [
|
|
@@ -57,9 +79,10 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
57
79
|
const parts = conditionalParts(expression)
|
|
58
80
|
const state = parts && directStateIdentifier(parts.condition, setters, bindingIndex)
|
|
59
81
|
if (state && isPrimitiveLiteral(parts.truthy) && isPrimitiveLiteral(parts.falsy)) {
|
|
60
|
-
return factory.createCallExpression(factory.createIdentifier("__kSelect"), undefined, [state, parts.truthy, parts.falsy])
|
|
82
|
+
return { node: factory.createCallExpression(factory.createIdentifier("__kSelect"), undefined, [state, parts.truthy, parts.falsy]) }
|
|
61
83
|
}
|
|
62
|
-
|
|
84
|
+
const binding = reactiveBindings.length
|
|
85
|
+
return { node: factory.createCallExpression(factory.createIdentifier("__kBinding"), undefined, compileReactiveExpression(expression, setters, importBindings, keyedBlock)), binding }
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
function compileConditional(kind, expression, truthy, falsy, setters) {
|
|
@@ -81,7 +104,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
81
104
|
? new Set(indexed.filter(reference => ["capture", "unresolved"].includes(reference.kind) && !setters.has(reference.debugName) && !allStateNames.has(reference.debugName) && !importedNames.has(reference.debugName)).map(reference => reference.debugName))
|
|
82
105
|
: new Set([...captureNames(expression, expression, setters)].filter(name => !importedNames.has(name)))
|
|
83
106
|
const exportName = `binding${reactiveBindings.length}`
|
|
84
|
-
reactiveBindings.push({ exportName, expression, captures, states: usedStates, imports, role: "binding", keyedBlock, ...(indexed ? { bindingIndex } : {}) })
|
|
107
|
+
reactiveBindings.push({ slot: reactiveBindings.length, exportName, expression, captures, states: usedStates, signalRefs: new Map([...usedStates].map(name => [name, signal(name, expression)])), imports, role: "binding", keyedBlock, ...(indexed ? { bindingIndex } : {}) })
|
|
85
108
|
const states = [...usedStates].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))
|
|
86
109
|
const scope = [...captures].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), factory.createIdentifier(name)]))
|
|
87
110
|
const stateNames = new Set(usedStates)
|
|
@@ -107,7 +130,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
107
130
|
const optimized = referencedReducerDispatches(expression.body, reducers, expression).size ? undefined : compileOptimizedEvent(expression, setters, stateOwners, owner, keyedBlock)
|
|
108
131
|
if (optimized) return optimized
|
|
109
132
|
rejectWorkerConstructions(expression)
|
|
110
|
-
const descriptor = compileNativeCallback(expression, { setters, reducers, entries: nativeHandlers, importBindings, prefix: "handler", role: "native", listItem, keyedBlock })
|
|
133
|
+
const descriptor = compileNativeCallback(expression, { setters, reducers, entries: nativeHandlers, importBindings, prefix: "handler", role: "native", listItem, keyedBlock, stateOwners })
|
|
111
134
|
return factory.createCallExpression(factory.createIdentifier("__kNativeBehavior"), undefined, [
|
|
112
135
|
factory.createStringLiteral(handlerUrl), factory.createStringLiteral(descriptor.exportName), descriptor.states, descriptor.scope
|
|
113
136
|
])
|
|
@@ -117,7 +140,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
117
140
|
return compileNativeCallback(expression, { ...options, entries: effectHandlers, prefix: "effect", role: "effect" })
|
|
118
141
|
}
|
|
119
142
|
|
|
120
|
-
function compileNativeCallback(expression, { setters, reducers, entries, importBindings, prefix, role, listItem, keyedBlock, deferValues = false, snapshotNested = false, liveStates = new Set() }) {
|
|
143
|
+
function compileNativeCallback(expression, { setters, reducers, entries, importBindings, prefix, role, listItem, keyedBlock, stateOwners, deferValues = false, snapshotNested = false, liveStates = new Set() }) {
|
|
121
144
|
const indexedBindingIndex = indexedReferences(bindingIndex, expression, expression) ? bindingIndex : undefined
|
|
122
145
|
const allCaptures = nativeCaptureNames(expression, setters, indexedBindingIndex)
|
|
123
146
|
const usedReducers = referencedReducerDispatches(expression.body, reducers, expression, indexedBindingIndex)
|
|
@@ -131,11 +154,13 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
131
154
|
if (reducer.contextAction) for (const state of referencedStateNames(reducer.contextAction.body, reducer.states, reducer.contextAction, bindingIndex)) usedStates.add(state)
|
|
132
155
|
}
|
|
133
156
|
const exportName = `${prefix}${entries.length}`
|
|
134
|
-
|
|
157
|
+
const entry = { exportName, expression, captures, deferValues, imports, listItem, keyedBlock, liveStates, role, setters: new Map([...setters].filter(([, state]) => usedStates.has(state))), reducers: new Map([...reducers].filter(([name]) => usedReducers.has(name))), snapshotNested, usedStates, signalRefs: new Map([...usedStates].map(name => [name, signal(name, expression, stateOwners, [...setters].filter(([, state]) => state === name).map(([setter]) => setter))])), bindingIndex: indexedBindingIndex }
|
|
158
|
+
entries.push(entry)
|
|
135
159
|
const value = name => deferValues
|
|
136
160
|
? factory.createArrowFunction(undefined, undefined, [], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createIdentifier(name))
|
|
137
161
|
: factory.createIdentifier(name)
|
|
138
162
|
return {
|
|
163
|
+
entry,
|
|
139
164
|
exportName,
|
|
140
165
|
states: factory.createArrayLiteralExpression([...usedStates].map(name => factory.createArrayLiteralExpression([factory.createStringLiteral(name), value(name)]))),
|
|
141
166
|
scope: factory.createArrayLiteralExpression([...captures].map(name => factory.createArrayLiteralExpression([
|
|
@@ -154,7 +179,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
154
179
|
if (!commands?.length || commands.some(command => !command)) return undefined
|
|
155
180
|
const original = ts.getOriginalNode(expression)
|
|
156
181
|
const source = original.pos >= 0 && original.end >= 0 ? { file: sourceName(original.getSourceFile()), start: original.getStart(), end: original.end } : undefined
|
|
157
|
-
const handler = registerCommandHandler(moduleIR, commands.map(command => ({ ...command,
|
|
182
|
+
const handler = registerCommandHandler(moduleIR, commands.map(command => ({ ...command, reference: stateOwners.get(command.state) ?? stateReferences(expression).get(command.state) })), source)
|
|
158
183
|
if (keyedBlock !== undefined) handler.keyedBlock = keyedBlock
|
|
159
184
|
return generateCommandBehavior(moduleIR, handler, factory)
|
|
160
185
|
}
|
|
@@ -176,7 +201,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
176
201
|
|
|
177
202
|
function registerDerivedResult(kind, value, states = [], node) {
|
|
178
203
|
const normalized = JSON.parse(JSON.stringify(value))
|
|
179
|
-
return registerDerived(moduleIR, { kind, [kind]: normalized,
|
|
204
|
+
return registerDerived(moduleIR, { kind, [kind]: normalized, signals: [...states].map(name => signal(name, node)), ...(source(node) ? { source: source(node) } : {}) })
|
|
180
205
|
}
|
|
181
206
|
|
|
182
207
|
function registerKeyedBlockResult(descriptor) {
|
|
@@ -184,68 +209,79 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
184
209
|
}
|
|
185
210
|
|
|
186
211
|
function registerEffectResult(handler, descriptor) {
|
|
187
|
-
|
|
212
|
+
const effect = { ...descriptor, setup: handler }
|
|
213
|
+
pendingEffects.push(effect)
|
|
214
|
+
return effect
|
|
188
215
|
}
|
|
189
216
|
|
|
190
217
|
function finalize() {
|
|
191
218
|
const callbacks = [...nativeHandlers, ...effectHandlers]
|
|
219
|
+
const imports = [...callbacks, ...reactiveBindings].flatMap(entry => entry.imports ?? []).map(importRecord)
|
|
220
|
+
moduleIR.imports = [...new Map(imports.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry])).values()].map((entry, slot) => ({ slot, ...entry }))
|
|
221
|
+
const importSlots = new Map(moduleIR.imports.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry.slot]))
|
|
222
|
+
const importSlot = entry => importSlots.get(`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`)
|
|
192
223
|
for (const entry of callbacks) {
|
|
193
224
|
const lowered = handlerLowering.lowerNativeHandler(entry)
|
|
194
225
|
const setters = Map.groupBy(entry.setters, ([, state]) => state)
|
|
195
|
-
registerModuleHandler(moduleIR, {
|
|
226
|
+
entry.handler = registerModuleHandler(moduleIR, {
|
|
196
227
|
role: entry.role,
|
|
197
228
|
...(entry.keyedBlock !== undefined ? { keyedBlock: entry.keyedBlock } : {}),
|
|
198
229
|
exportName: entry.exportName,
|
|
199
230
|
async: Boolean(entry.expression.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)),
|
|
200
231
|
generator: Boolean(entry.expression.asteriskToken),
|
|
201
232
|
signals: [...entry.usedStates].map(name => ({
|
|
233
|
+
signal: entry.signalRefs.get(name),
|
|
202
234
|
name,
|
|
203
235
|
setters: (setters.get(name) ?? []).map(([setter]) => setter),
|
|
204
236
|
value: entry.deferValues ? "deferred" : "direct",
|
|
205
237
|
snapshot: lowered.stateSnapshots.includes(name)
|
|
206
238
|
})),
|
|
207
239
|
captures: [...entry.captures].map(name => ({
|
|
240
|
+
...(symbolSlot(entry, name) !== undefined ? { symbol: symbolSlot(entry, name) } : {}),
|
|
208
241
|
name,
|
|
209
242
|
source: name === (typeof entry.listItem === "string" ? entry.listItem : entry.listItem?.item) ? "list-item" : name === entry.listItem?.index ? "list-index" : "scope",
|
|
210
243
|
value: entry.deferValues ? "deferred" : "direct",
|
|
211
244
|
snapshot: lowered.captureSnapshots.includes(name)
|
|
212
245
|
})),
|
|
213
|
-
imports: entry.imports.map(importRecord),
|
|
246
|
+
imports: entry.imports.map(entry => importSlot(importRecord(entry))),
|
|
214
247
|
code: lowered.code,
|
|
215
248
|
...(source(entry.expression) ? { source: source(entry.expression) } : {})
|
|
216
249
|
})
|
|
217
250
|
}
|
|
218
251
|
for (const entry of reactiveBindings) registerBinding(moduleIR, {
|
|
252
|
+
slot: entry.slot,
|
|
219
253
|
role: entry.role,
|
|
220
254
|
...(entry.keyedBlock !== undefined ? { keyedBlock: entry.keyedBlock } : {}),
|
|
221
255
|
exportName: entry.exportName,
|
|
222
256
|
parameters: ["__k"],
|
|
223
|
-
|
|
224
|
-
captures: [...entry.captures].map(name => ({ name, source: "scope" })),
|
|
225
|
-
imports: entry.imports.map(importRecord),
|
|
257
|
+
signals: [...entry.states].map(name => entry.signalRefs.get(name)),
|
|
258
|
+
captures: [...entry.captures].map(name => ({ ...(symbolSlot(entry, name) !== undefined ? { symbol: symbolSlot(entry, name) } : {}), name, source: "scope" })),
|
|
259
|
+
imports: entry.imports.map(entry => importSlot(importRecord(entry))),
|
|
226
260
|
code: handlerLowering.lowerReactiveBinding(entry),
|
|
227
261
|
...(source(entry.expression) ? { source: source(entry.expression) } : {})
|
|
228
262
|
})
|
|
229
|
-
for (const entry of listExpressions) registerBinding(moduleIR, {
|
|
263
|
+
for (const [index, entry] of listExpressions.entries()) registerBinding(moduleIR, {
|
|
264
|
+
slot: reactiveBindings.length + index,
|
|
230
265
|
role: entry.role,
|
|
231
266
|
...(entry.keyedBlock !== undefined ? { keyedBlock: entry.keyedBlock } : {}),
|
|
232
267
|
exportName: entry.exportName,
|
|
233
268
|
parameters: [entry.item, entry.index ?? "__kIndex", "__k"],
|
|
234
|
-
|
|
269
|
+
signals: [...(entry.states ?? [])].map(name => entry.signalRefs.get(name)),
|
|
235
270
|
captures: [],
|
|
236
271
|
imports: [],
|
|
237
272
|
code: handlerLowering.lowerListExpression(entry),
|
|
238
273
|
...(source(entry.expression) ? { source: source(entry.expression) } : {})
|
|
239
274
|
})
|
|
240
|
-
for (const effect of
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
effect.setup = { handler: handler.slot }
|
|
275
|
+
for (const effect of pendingEffects) {
|
|
276
|
+
if (!effect.setup.entry.handler) throw new Error(`Effect handler ${JSON.stringify(effect.setup.exportName)} was not finalized`)
|
|
277
|
+
registerEffect(moduleIR, { ...effect, setup: { handler: effect.setup.entry.handler.slot } })
|
|
244
278
|
}
|
|
245
|
-
const imports = [...callbacks, ...reactiveBindings].flatMap(entry => entry.imports ?? []).map(importRecord)
|
|
246
|
-
moduleIR.imports = [...new Map(imports.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry])).values()]
|
|
247
279
|
moduleIR.clientModules = [...clientModules]
|
|
248
|
-
assertModuleIRReferences(moduleIR)
|
|
280
|
+
assertModuleIRReferences(moduleIR, semantic.componentAnalysis)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function symbolSlot(entry, name) {
|
|
284
|
+
return entry.bindingIndex?.references(entry.expression, entry.expression)?.find(reference => reference.debugName === name)?.slot
|
|
249
285
|
}
|
|
250
286
|
|
|
251
287
|
function source(node) {
|
|
@@ -256,7 +292,15 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
256
292
|
|
|
257
293
|
const importRecord = entry => ({ target: entry.target, kind: entry.kind, local: entry.local, ...(entry.imported ? { imported: entry.imported } : {}), package: Boolean(entry.package) })
|
|
258
294
|
|
|
259
|
-
return { compileConditional, compileEffectCallback, compileEvent, compileListConditional, compileListValue, compileReactiveBinding, finalize, registerDerived: registerDerivedResult, registerEffect: registerEffectResult, registerKeyedBlock: registerKeyedBlockResult }
|
|
295
|
+
return { compileConditional, compileEffectCallback, compileEvent, compileListConditional, compileListValue, compileReactiveBinding, finalize, registerDerived: registerDerivedResult, registerEffect: registerEffectResult, registerKeyedBlock: registerKeyedBlockResult, signal }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function bindingIdentifier(name, names) {
|
|
299
|
+
if (ts.isIdentifier(name)) return names.has(name.text) ? name : undefined
|
|
300
|
+
for (const element of name.elements) if (ts.isBindingElement(element)) {
|
|
301
|
+
const identifier = bindingIdentifier(element.name, names)
|
|
302
|
+
if (identifier) return identifier
|
|
303
|
+
}
|
|
260
304
|
}
|
|
261
305
|
|
|
262
306
|
function directStateIdentifier(expression, setters, bindingIndex) {
|
|
@@ -1,48 +1,159 @@
|
|
|
1
1
|
export function createModuleIR(file) {
|
|
2
|
-
return { version:
|
|
2
|
+
return { version: 2, file, symbols: [], signals: [], handlers: [], bindings: [], derived: [], effects: [], keyedBlocks: [], imports: [], clientModules: [] }
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
export function assertModuleIRReferences(moduleIR) {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export function assertModuleIRReferences(moduleIR, componentAnalysis) {
|
|
6
|
+
if (moduleIR?.version !== 2) throw new Error(`Unsupported ModuleIR version: ${JSON.stringify(moduleIR?.version)}`)
|
|
7
|
+
if (componentAnalysis && componentAnalysis.version !== 2) throw new Error(`Unsupported ComponentAnalysis version: ${JSON.stringify(componentAnalysis.version)}`)
|
|
8
|
+
const slot = (records, value, label, kind) => {
|
|
9
|
+
if (!Number.isInteger(value) || value < 0 || value >= records.length) throw new Error(`${label} references missing ${kind} slot ${JSON.stringify(value)}`)
|
|
10
|
+
return records[value]
|
|
8
11
|
}
|
|
9
|
-
|
|
12
|
+
const indexed = (name, records) => {
|
|
10
13
|
records.forEach((record, index) => {
|
|
11
|
-
if (record.slot !== index) throw new Error(
|
|
14
|
+
if (record.slot !== index) throw new Error(`${name} slot ${JSON.stringify(record.slot)} must equal its index ${index}`)
|
|
12
15
|
})
|
|
13
16
|
}
|
|
17
|
+
indexed("SymbolRef", moduleIR.symbols)
|
|
18
|
+
indexed("SignalIR", moduleIR.signals)
|
|
19
|
+
indexed("HandlerIR", moduleIR.handlers)
|
|
20
|
+
indexed("BindingIR", moduleIR.bindings)
|
|
21
|
+
indexed("DerivedIR", moduleIR.derived)
|
|
22
|
+
indexed("EffectIR", moduleIR.effects)
|
|
23
|
+
indexed("KeyedBlockIR", moduleIR.keyedBlocks)
|
|
24
|
+
indexed("ImportIR", moduleIR.imports)
|
|
25
|
+
if (componentAnalysis) {
|
|
26
|
+
indexed("Component owner", componentAnalysis.owners)
|
|
27
|
+
indexed("Component specialization", componentAnalysis.specializations)
|
|
28
|
+
for (const owner of componentAnalysis.owners) {
|
|
29
|
+
indexed(`Component owner ${owner.slot} state`, owner.states)
|
|
30
|
+
indexed(`Component owner ${owner.slot} ref`, owner.refs)
|
|
31
|
+
indexed(`Component owner ${owner.slot} ID`, owner.ids)
|
|
32
|
+
for (const setter of owner.setters) slot(owner.states, setter.signal, `Component owner ${owner.slot} setter ${JSON.stringify(setter.name)}`, "state")
|
|
33
|
+
for (const state of owner.states) if (state.owner !== undefined) ownerRef(state.owner, `Component owner ${owner.slot} state ${state.slot} external owner`)
|
|
34
|
+
}
|
|
35
|
+
for (const specialization of componentAnalysis.specializations) {
|
|
36
|
+
indexed(`Component specialization ${specialization.slot} state`, specialization.states)
|
|
37
|
+
indexed(`Component specialization ${specialization.slot} ref`, specialization.refs)
|
|
38
|
+
indexed(`Component specialization ${specialization.slot} ID`, specialization.ids)
|
|
39
|
+
if (specialization.owner !== undefined) ownerRef(specialization.owner, `Component specialization ${specialization.slot} owner`)
|
|
40
|
+
for (const prop of specialization.props ?? []) for (const signal of prop.signals ?? []) slot(moduleIR.signals, signal, `Component specialization ${specialization.slot} prop ${JSON.stringify(prop.name)}`, "SignalIR")
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function moduleSymbol(symbol, label) {
|
|
44
|
+
if (!symbol || typeof symbol.id !== "string" || typeof symbol.module !== "string" || typeof symbol.site !== "string" || typeof symbol.name !== "string") throw new Error(`${label} must be a ModuleSymbol`)
|
|
45
|
+
}
|
|
46
|
+
function ownerRef(reference, label) {
|
|
47
|
+
if (!reference || typeof reference !== "object") throw new Error(`${label} must be an OwnerRef`)
|
|
48
|
+
if (reference.kind === "component") return slot(componentAnalysis?.owners ?? [], reference.slot, label, "component owner")
|
|
49
|
+
if (reference.kind === "specialization") return slot(componentAnalysis?.specializations ?? [], reference.slot, label, "component specialization")
|
|
50
|
+
if (reference.kind === "module-symbol") return moduleSymbol(reference.symbol, label)
|
|
51
|
+
if (reference.kind !== "module") throw new Error(`${label} has invalid kind ${JSON.stringify(reference.kind)}`)
|
|
52
|
+
}
|
|
53
|
+
function stateRef(reference, label) {
|
|
54
|
+
if (!reference || typeof reference !== "object") throw new Error(`${label} must be a StateRef`)
|
|
55
|
+
if (reference.kind === "module-symbol") return moduleSymbol(reference.symbol, label)
|
|
56
|
+
if (reference.kind === "symbol") return slot(moduleIR.symbols, reference.symbol, label, "SymbolRef")
|
|
57
|
+
if (reference.kind !== "state") throw new Error(`${label} has invalid kind ${JSON.stringify(reference.kind)}`)
|
|
58
|
+
const owner = ownerRef(reference.owner, `${label} owner`)
|
|
59
|
+
const states = reference.owner.kind === "component" || reference.owner.kind === "specialization" ? owner.states : []
|
|
60
|
+
slot(states, reference.slot, label, "owner state")
|
|
61
|
+
}
|
|
62
|
+
const exports = new Map()
|
|
63
|
+
const exported = (record, label) => {
|
|
64
|
+
if (record.kind !== "module-export") return
|
|
65
|
+
if (typeof record.exportName !== "string" || !record.exportName) throw new Error(`${label} requires an export name`)
|
|
66
|
+
const previous = exports.get(record.exportName)
|
|
67
|
+
if (previous) throw new Error(`ModuleIR export ${JSON.stringify(record.exportName)} is declared by both ${previous} and ${label}`)
|
|
68
|
+
exports.set(record.exportName, label)
|
|
69
|
+
}
|
|
70
|
+
for (const signal of moduleIR.signals) stateRef(signal.reference, `SignalIR ${signal.slot}`)
|
|
14
71
|
for (const handler of moduleIR.handlers) {
|
|
15
|
-
|
|
16
|
-
|
|
72
|
+
exported(handler, `HandlerIR ${handler.slot}`)
|
|
73
|
+
for (const [index, command] of (handler.commands ?? []).entries()) slot(moduleIR.signals, command.signal, `HandlerIR ${handler.slot} command ${index}`, "SignalIR")
|
|
74
|
+
for (const [index, signal] of (handler.signals ?? []).entries()) slot(moduleIR.signals, signal.signal, `HandlerIR ${handler.slot} signal ${index}`, "SignalIR")
|
|
75
|
+
for (const [index, imported] of (handler.imports ?? []).entries()) slot(moduleIR.imports, imported, `HandlerIR ${handler.slot} import ${index}`, "ImportIR")
|
|
76
|
+
for (const [index, capture] of (handler.captures ?? []).entries()) if (capture.symbol !== undefined) slot(moduleIR.symbols, capture.symbol, `HandlerIR ${handler.slot} capture ${index}`, "SymbolRef")
|
|
77
|
+
if (handler.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, handler.keyedBlock, `HandlerIR ${handler.slot} keyed block`, "KeyedBlockIR")
|
|
17
78
|
}
|
|
18
|
-
for (const binding of moduleIR.bindings)
|
|
79
|
+
for (const binding of moduleIR.bindings) {
|
|
80
|
+
exported(binding, `BindingIR ${binding.slot}`)
|
|
81
|
+
for (const [index, signal] of (binding.signals ?? []).entries()) slot(moduleIR.signals, signal, `BindingIR ${binding.slot} signal ${index}`, "SignalIR")
|
|
82
|
+
for (const [index, imported] of (binding.imports ?? []).entries()) slot(moduleIR.imports, imported, `BindingIR ${binding.slot} import ${index}`, "ImportIR")
|
|
83
|
+
for (const [index, capture] of (binding.captures ?? []).entries()) if (capture.symbol !== undefined) slot(moduleIR.symbols, capture.symbol, `BindingIR ${binding.slot} capture ${index}`, "SymbolRef")
|
|
84
|
+
if (binding.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, binding.keyedBlock, `BindingIR ${binding.slot} keyed block`, "KeyedBlockIR")
|
|
85
|
+
}
|
|
86
|
+
for (const derived of moduleIR.derived) for (const [index, signal] of (derived.signals ?? []).entries()) slot(moduleIR.signals, signal, `DerivedIR ${derived.slot} signal ${index}`, "SignalIR")
|
|
19
87
|
for (const effect of moduleIR.effects) {
|
|
20
|
-
slot(moduleIR.handlers, effect.setup?.handler, `
|
|
21
|
-
|
|
22
|
-
|
|
88
|
+
const handler = slot(moduleIR.handlers, effect.setup?.handler, `EffectIR ${effect.slot} setup`, "HandlerIR")
|
|
89
|
+
if (handler.kind !== "module-export" || handler.role !== "effect") throw new Error(`EffectIR ${effect.slot} setup HandlerIR ${handler.slot} must have role "effect"`)
|
|
90
|
+
for (const [index, dependency] of (effect.dependencies ?? []).entries()) {
|
|
91
|
+
if (dependency.kind === "signal") slot(moduleIR.signals, dependency.signal, `EffectIR ${effect.slot} dependency ${index}`, "SignalIR")
|
|
92
|
+
else if (dependency.kind === "derived") {
|
|
93
|
+
slot(moduleIR.derived, dependency.derived, `EffectIR ${effect.slot} dependency ${index}`, "DerivedIR")
|
|
94
|
+
for (const [sourceIndex, signal] of (dependency.sources ?? []).entries()) slot(moduleIR.signals, signal, `EffectIR ${effect.slot} dependency ${index} source ${sourceIndex}`, "SignalIR")
|
|
95
|
+
} else throw new Error(`EffectIR ${effect.slot} dependency ${index} has invalid kind ${JSON.stringify(dependency.kind)}`)
|
|
96
|
+
}
|
|
97
|
+
for (const [index, signal] of (effect.subscriptions ?? []).entries()) slot(moduleIR.signals, signal, `EffectIR ${effect.slot} subscription ${index}`, "SignalIR")
|
|
98
|
+
for (const [index, signal] of (effect.dependencySignals ?? []).entries()) slot(moduleIR.signals, signal, `EffectIR ${effect.slot} dependency signal ${index}`, "SignalIR")
|
|
99
|
+
if (effect.ownership?.owner) ownerRef(effect.ownership.owner, `EffectIR ${effect.slot} ownership`)
|
|
100
|
+
if (effect.ownership?.keyedBlock !== undefined) {
|
|
101
|
+
slot(moduleIR.keyedBlocks, effect.ownership.keyedBlock, `EffectIR ${effect.slot} ownership`, "KeyedBlockIR")
|
|
102
|
+
if (handler.keyedBlock !== effect.ownership.keyedBlock) throw new Error(`EffectIR ${effect.slot} and HandlerIR ${handler.slot} must reference the same KeyedBlockIR`)
|
|
103
|
+
}
|
|
23
104
|
}
|
|
24
105
|
for (const block of moduleIR.keyedBlocks) {
|
|
25
|
-
if (block.
|
|
26
|
-
|
|
27
|
-
if (block.
|
|
106
|
+
if (block.collection?.kind === "signal") slot(moduleIR.signals, block.collection.signal, `KeyedBlockIR ${block.slot} collection`, "SignalIR")
|
|
107
|
+
else if (block.collection?.kind === "binding") slot(moduleIR.bindings, block.collection.binding, `KeyedBlockIR ${block.slot} collection`, "BindingIR")
|
|
108
|
+
else if (block.collection?.kind === "symbol") slot(moduleIR.symbols, block.collection.symbol, `KeyedBlockIR ${block.slot} collection`, "SymbolRef")
|
|
109
|
+
else if (block.collection?.kind !== "static") throw new Error(`KeyedBlockIR ${block.slot} collection has invalid kind ${JSON.stringify(block.collection?.kind)}`)
|
|
110
|
+
if (block.parent !== undefined) {
|
|
111
|
+
const parent = slot(moduleIR.keyedBlocks, block.parent, `KeyedBlockIR ${block.slot} parent`, "KeyedBlockIR")
|
|
112
|
+
if (!(parent.children ?? []).includes(block.slot)) throw new Error(`KeyedBlockIR ${block.slot} parent ${parent.slot} does not reciprocally list child ${block.slot}`)
|
|
113
|
+
}
|
|
114
|
+
for (const childSlot of block.children ?? []) {
|
|
115
|
+
const child = slot(moduleIR.keyedBlocks, childSlot, `KeyedBlockIR ${block.slot} child`, "KeyedBlockIR")
|
|
116
|
+
if (child.parent !== block.slot) throw new Error(`KeyedBlockIR ${block.slot} child ${child.slot} does not reciprocally reference parent ${block.slot}`)
|
|
117
|
+
}
|
|
118
|
+
if (new Set(block.children ?? []).size !== (block.children ?? []).length) throw new Error(`KeyedBlockIR ${block.slot} has duplicate children`)
|
|
119
|
+
if (block.selector !== undefined) slot(moduleIR.derived, block.selector, `KeyedBlockIR ${block.slot} selector`, "DerivedIR")
|
|
120
|
+
for (const [index, signal] of (block.selectorSignals ?? []).entries()) slot(moduleIR.signals, signal, `KeyedBlockIR ${block.slot} selector signal ${index}`, "SignalIR")
|
|
121
|
+
for (const [index, specialization] of (block.specializations ?? []).entries()) slot(componentAnalysis?.specializations ?? [], specialization, `KeyedBlockIR ${block.slot} specialization ${index}`, "component specialization")
|
|
122
|
+
for (const [index, row] of (block.rowStates ?? []).entries()) slot(moduleIR.signals, row.signal, `KeyedBlockIR ${block.slot} row state ${index}`, "SignalIR")
|
|
123
|
+
for (const [index, row] of (block.rowRefs ?? []).entries()) {
|
|
124
|
+
const specialization = slot(componentAnalysis?.specializations ?? [], row.specialization, `KeyedBlockIR ${block.slot} row ref ${index}`, "component specialization")
|
|
125
|
+
slot(specialization.refs, row.ref, `KeyedBlockIR ${block.slot} row ref ${index}`, "specialization ref")
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const visiting = new Set()
|
|
129
|
+
const visited = new Set()
|
|
130
|
+
const visit = (block, trail) => {
|
|
131
|
+
if (visiting.has(block.slot)) throw new Error(`KeyedBlockIR parent cycle: ${[...trail, block.slot].join(" -> ")}`)
|
|
132
|
+
if (visited.has(block.slot)) return
|
|
133
|
+
visiting.add(block.slot)
|
|
134
|
+
if (block.parent !== undefined) visit(moduleIR.keyedBlocks[block.parent], [...trail, block.slot])
|
|
135
|
+
visiting.delete(block.slot)
|
|
136
|
+
visited.add(block.slot)
|
|
28
137
|
}
|
|
138
|
+
for (const block of moduleIR.keyedBlocks) visit(block, [])
|
|
29
139
|
return moduleIR
|
|
30
140
|
}
|
|
31
141
|
|
|
32
|
-
export function
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
slots.set(key, slot)
|
|
39
|
-
moduleIR.signals.push({ slot, key, debugName: state })
|
|
142
|
+
export function registerSignal(moduleIR, reference, debugName) {
|
|
143
|
+
const key = JSON.stringify(reference)
|
|
144
|
+
let signal = moduleIR.signals.find(entry => JSON.stringify(entry.reference) === key)
|
|
145
|
+
if (!signal) {
|
|
146
|
+
signal = { slot: moduleIR.signals.length, reference, debugName }
|
|
147
|
+
moduleIR.signals.push(signal)
|
|
40
148
|
}
|
|
41
|
-
|
|
149
|
+
return signal
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function registerCommandHandler(moduleIR, commands, source) {
|
|
42
153
|
const handler = {
|
|
43
154
|
slot: moduleIR.handlers.length,
|
|
44
155
|
kind: "commands",
|
|
45
|
-
commands: commands.map(({ operation,
|
|
156
|
+
commands: commands.map(({ operation, reference, state, value, syntax }) => ({ operation, signal: registerSignal(moduleIR, reference, state).slot, value, ...(syntax ? { syntax } : {}) })),
|
|
46
157
|
...(source ? { source } : {})
|
|
47
158
|
}
|
|
48
159
|
moduleIR.handlers.push(handler)
|
|
@@ -50,31 +161,31 @@ export function registerCommandHandler(moduleIR, commands, source, scope = "modu
|
|
|
50
161
|
}
|
|
51
162
|
|
|
52
163
|
export function registerModuleHandler(moduleIR, descriptor) {
|
|
53
|
-
const handler = { slot: moduleIR.handlers.length, kind: "module-export"
|
|
164
|
+
const handler = { ...descriptor, slot: moduleIR.handlers.length, kind: "module-export" }
|
|
54
165
|
moduleIR.handlers.push(handler)
|
|
55
166
|
return handler
|
|
56
167
|
}
|
|
57
168
|
|
|
58
169
|
export function registerBinding(moduleIR, descriptor) {
|
|
59
|
-
const binding = { slot: moduleIR.bindings.length, kind: "module-export"
|
|
170
|
+
const binding = { ...descriptor, slot: moduleIR.bindings.length, kind: "module-export" }
|
|
60
171
|
moduleIR.bindings.push(binding)
|
|
61
172
|
return binding
|
|
62
173
|
}
|
|
63
174
|
|
|
64
175
|
export function registerDerived(moduleIR, descriptor) {
|
|
65
|
-
const derived = { slot: moduleIR.derived.length
|
|
176
|
+
const derived = { ...descriptor, slot: moduleIR.derived.length }
|
|
66
177
|
moduleIR.derived.push(derived)
|
|
67
178
|
return derived
|
|
68
179
|
}
|
|
69
180
|
|
|
70
181
|
export function registerEffect(moduleIR, descriptor) {
|
|
71
|
-
const effect = { slot: moduleIR.effects.length
|
|
182
|
+
const effect = { ...descriptor, slot: moduleIR.effects.length }
|
|
72
183
|
moduleIR.effects.push(effect)
|
|
73
184
|
return effect
|
|
74
185
|
}
|
|
75
186
|
|
|
76
187
|
export function registerKeyedBlock(moduleIR, descriptor) {
|
|
77
|
-
const block = { slot: moduleIR.keyedBlocks.length
|
|
188
|
+
const block = { ...descriptor, slot: moduleIR.keyedBlocks.length }
|
|
78
189
|
moduleIR.keyedBlocks.push(block)
|
|
79
190
|
return block
|
|
80
191
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export function createRouteBuildRecord(input) {
|
|
2
|
+
const record = {
|
|
3
|
+
version: input.version ?? 1,
|
|
4
|
+
route: input.route,
|
|
5
|
+
output: input.output,
|
|
6
|
+
html: input.html,
|
|
7
|
+
plan: input.plan,
|
|
8
|
+
capabilities: input.capabilities,
|
|
9
|
+
artifacts: {
|
|
10
|
+
handlers: input.handlerReferences ?? [],
|
|
11
|
+
effects: (input.plan?.effects ?? []).map(({ module, handler }) => ({ module, handler })),
|
|
12
|
+
styles: [...new Set(input.styles ?? [])]
|
|
13
|
+
},
|
|
14
|
+
entries: input.entries ?? {},
|
|
15
|
+
...(input.runtimeSchema ? { runtimeSchema: input.runtimeSchema } : {})
|
|
16
|
+
}
|
|
17
|
+
return assertRouteBuildRecord(record)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function assertRouteBuildRecord(record) {
|
|
21
|
+
if (record?.version !== 1) throw new Error(`Unsupported RouteBuildRecord version: ${JSON.stringify(record?.version)}`)
|
|
22
|
+
if (typeof record.route !== "string" || typeof record.output !== "string" || typeof record.html !== "string" || !isRecord(record.plan)) throw new Error("Invalid RouteBuildRecord v1 structure")
|
|
23
|
+
if (record.plan.route !== record.route) throw new Error(`RouteBuildRecord route ${JSON.stringify(record.route)} does not match RouteIR route ${JSON.stringify(record.plan.route)}`)
|
|
24
|
+
const capabilityNames = ["navigable", "usesDependencyRuntime", "hasBehaviors", "hasBindings", "hasLists", "hasListStyles", "hasStateSeed", "hasParams", "hasEffects"]
|
|
25
|
+
if (!isRecord(record.capabilities) || !capabilityNames.every(name => typeof record.capabilities[name] === "boolean")) throw new Error("Invalid RouteBuildRecord v1 capabilities")
|
|
26
|
+
if (!isRecord(record.artifacts) || !Array.isArray(record.artifacts.handlers) || !Array.isArray(record.artifacts.effects) || !Array.isArray(record.artifacts.styles) || !isRecord(record.entries)) throw new Error("Invalid RouteBuildRecord v1 artifacts")
|
|
27
|
+
const handlers = new Set()
|
|
28
|
+
for (const reference of record.artifacts.handlers) {
|
|
29
|
+
assertHandlerReference(reference)
|
|
30
|
+
const key = referenceKey(reference)
|
|
31
|
+
if (handlers.has(key)) throw new Error(`RouteBuildRecord ${JSON.stringify(record.route)} has duplicate handler reference ${key}`)
|
|
32
|
+
handlers.add(key)
|
|
33
|
+
}
|
|
34
|
+
for (const effect of record.artifacts.effects) {
|
|
35
|
+
assertHandlerReference(effect)
|
|
36
|
+
if (!handlers.has(referenceKey(effect))) throw new Error(`RouteBuildRecord ${JSON.stringify(record.route)} effect references an unretained handler ${referenceKey(effect)}`)
|
|
37
|
+
}
|
|
38
|
+
for (const event of record.plan.events ?? []) if (event.native && !handlers.has(referenceKey(event.native))) throw new Error(`RouteBuildRecord ${JSON.stringify(record.route)} event references an unretained handler ${referenceKey(event.native)}`)
|
|
39
|
+
for (const descriptor of [...(record.plan.bindings ?? []), ...(record.plan.conditions ?? []), ...(record.plan.lists ?? []).map(list => list.source).filter(Boolean)]) {
|
|
40
|
+
if (descriptor.module && !handlers.has(referenceKey(descriptor))) throw new Error(`RouteBuildRecord ${JSON.stringify(record.route)} descriptor references an unretained handler ${referenceKey(descriptor)}`)
|
|
41
|
+
}
|
|
42
|
+
if (record.artifacts.styles.some(style => typeof style !== "string")) throw new Error("RouteBuildRecord styles must be strings")
|
|
43
|
+
for (const [kind, path] of Object.entries(record.entries)) if (!["effect", "native", "param"].includes(kind) || typeof path !== "string") throw new Error(`Invalid RouteBuildRecord entry ${JSON.stringify(kind)}`)
|
|
44
|
+
if (record.runtimeSchema !== undefined && !isRecord(record.runtimeSchema)) throw new Error("Invalid RouteBuildRecord runtime schema")
|
|
45
|
+
const hasNative = (record.plan.events ?? []).some(event => event.native)
|
|
46
|
+
if (Boolean(record.entries.effect) !== record.capabilities.hasEffects || record.capabilities.hasEffects !== Boolean(record.plan.effects?.length)) throw new Error(`RouteBuildRecord ${JSON.stringify(record.route)} has inconsistent effect artifacts`)
|
|
47
|
+
if (Boolean(record.entries.native) !== hasNative) throw new Error(`RouteBuildRecord ${JSON.stringify(record.route)} has inconsistent native artifacts`)
|
|
48
|
+
if (Boolean(record.entries.param) !== record.capabilities.hasParams) throw new Error(`RouteBuildRecord ${JSON.stringify(record.route)} has inconsistent parameter artifacts`)
|
|
49
|
+
return record
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function planRouteArtifacts(records, handlerModules, workerReferences, moduleUrl) {
|
|
53
|
+
for (const record of records) assertRouteBuildRecord(record)
|
|
54
|
+
const modules = new Map()
|
|
55
|
+
for (const module of handlerModules) {
|
|
56
|
+
const url = moduleUrl(module)
|
|
57
|
+
if (modules.has(url)) throw new Error(`Duplicate compiled handler module: ${url}`)
|
|
58
|
+
modules.set(url, module)
|
|
59
|
+
}
|
|
60
|
+
const retainedUrls = new Set(records.flatMap(record => record.artifacts.handlers.map(reference => reference.module)))
|
|
61
|
+
for (const url of retainedUrls) if (!modules.has(url)) throw new Error(`Handler module was not compiled: ${url}`)
|
|
62
|
+
const effects = new Map()
|
|
63
|
+
for (const reference of records.flatMap(record => record.artifacts.effects)) {
|
|
64
|
+
const handlers = effects.get(reference.module) ?? new Set()
|
|
65
|
+
handlers.add(reference.handler)
|
|
66
|
+
effects.set(reference.module, handlers)
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
handlerModules: handlerModules.filter(module => retainedUrls.has(moduleUrl(module))),
|
|
70
|
+
workerReferences: workerReferences.filter(reference => effects.get(reference.module)?.has(reference.handler)),
|
|
71
|
+
styles: [...new Set(records.flatMap(record => record.artifacts.styles))]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const isRecord = value => value !== null && typeof value === "object" && !Array.isArray(value)
|
|
76
|
+
|
|
77
|
+
function assertHandlerReference(reference) {
|
|
78
|
+
if (!isRecord(reference) || typeof reference.module !== "string" || !reference.module || typeof reference.handler !== "string" || !reference.handler) throw new Error("RouteBuildRecord handler references require module and handler strings")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const referenceKey = reference => `${JSON.stringify(reference.module)}#${JSON.stringify(reference.handler)}`
|
|
@@ -4,7 +4,9 @@ export function usesRouteDependencyRuntime({ plan, navigable, hasBindings, hasLi
|
|
|
4
4
|
return !navigable && hasDependencies && !plan.effects.some(effect => effect.owner) && !hasBindings && !hasLists && !plan.events.some(event => event.native)
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export function planRouteCapabilities(
|
|
7
|
+
export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}) {
|
|
8
|
+
for (const record of records) assertRouteBuildRecord(record)
|
|
9
|
+
const plans = records.map(record => record.plan)
|
|
8
10
|
for (const plan of plans) assertRouteIR(plan)
|
|
9
11
|
const commandEvents = new Set()
|
|
10
12
|
const nativeEvents = new Set()
|
|
@@ -39,7 +41,7 @@ export function planRouteCapabilities(plans, { routes = new Map(), navigationRou
|
|
|
39
41
|
|
|
40
42
|
for (let index = 0; index < plans.length; index++) {
|
|
41
43
|
const plan = plans[index]
|
|
42
|
-
const route =
|
|
44
|
+
const route = records[index].capabilities
|
|
43
45
|
for (const event of plan.events) {
|
|
44
46
|
if (event.commands) commandEvents.add(event.event)
|
|
45
47
|
if (event.native) nativeEvents.add(event.event)
|
|
@@ -77,7 +79,7 @@ export function planRouteCapabilities(plans, { routes = new Map(), navigationRou
|
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
const routeEntries =
|
|
82
|
+
const routeEntries = records.map(record => record.capabilities)
|
|
81
83
|
const routeCounts = {
|
|
82
84
|
behaviors: routeEntries.filter(route => route.hasBehaviors).length,
|
|
83
85
|
regularBehaviors: routeEntries.filter(route => route.hasBehaviors && !route.usesDependencyRuntime).length,
|
|
@@ -151,3 +153,4 @@ function hasNestedCaptureState(value, insideCapture = false) {
|
|
|
151
153
|
if (value.type === "object") return value.value.some(([, entry]) => hasNestedCaptureState(entry, true))
|
|
152
154
|
return (Array.isArray(value) ? value : Object.values(value)).some(entry => hasNestedCaptureState(entry, false))
|
|
153
155
|
}
|
|
156
|
+
import { assertRouteBuildRecord } from "./route-build-record.mjs"
|