@kudzujs/core 0.8.24 → 0.8.25

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.
@@ -250,6 +250,12 @@ This queue orders the next investigations by general migration value. Start only
250
250
  - No-op normalization passes skip redundant recursive parent repair, reducing the measured 1,000-product build median by 6.26% with equivalent output.
251
251
  - Identical generated route-entry transformation remains the next evidence-backed Goal B investigation; it is not implemented without an isolated benchmark.
252
252
 
253
+ ### Completed In 0.8.25
254
+
255
+ - Seven alternating builds of the 1,000-product, 1,011-page fixture authorized build-local exact-source reuse for identical generated native, parameter, and effect route-entry transforms, improving the median by 9.17% with identical deploy paths and bytes.
256
+ - Normalization pass results and finalized ModuleIR cross-slot references now fail closed at their compiler boundaries.
257
+ - Node 22 compatibility, required Chrome coverage, packed-package installation, version alignment, and post-publish registry checks are explicit release gates.
258
+
253
259
  ## Cross-Cutting Performance Gates
254
260
 
255
261
  Every migration feature must preserve:
package/PERFORMANCE.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  Reproducibility classes: `npm run benchmark` and `npm run benchmark:keyed` are maintained in this repository; `npm run benchmark:commerce` is a maintained paired runner over the public external storefront; older excluded-workspace sections are historical provenance only and are not current framework rankings.
4
4
 
5
+ ## 0.8.25 Route Entry Transform Reuse
6
+
7
+ Measured UTC 2026-08-10 on an Intel Core i5-9500 with 6 cores, Linux 6.17.0-19-generic, Node 24.14.0, and npm 11.9.0. The public 1,000-product storefront fixture at `f2d5be1` generated 1,011 pages against the `0.8.24` tree plus the same compiler-boundary safety changes in both targets.
8
+
9
+ One warm-up followed by seven alternating clean builds compared repeated esbuild transformation with a build-local exact-source result map used only by generated native, parameter, and effect route entries. The median decreased from 13,851.0 ms to 12,581.4 ms, a 9.17% improvement. Every emitted path and SHA-256 hash matched. The catalog was generated once before timing; `dist` and `.kudzu` cleanup and manifest hashing remained outside timing.
10
+
11
+ ```text
12
+ repeated transform: [13437.4,13851.0,13844.9,13440.8,13906.1,14310.0,14901.0]
13
+ exact-source reuse: [12480.2,12826.9,12777.9,12615.9,11869.7,12121.3,12581.4]
14
+ ```
15
+
16
+ The map lasts for one build and keys the complete generated source after route-relative URLs are resolved. It is not a persistent or generalized JavaScript transform cache. The result measures the large repeated-route fixture on this Linux machine and is not directly comparable to the Apple M3 `0.8.24` build medians below.
17
+
5
18
  ## 0.8.24 Measured Goal B Optimizations
6
19
 
7
20
  Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, npm 11.18.0, and Chrome 151.0.7922.76. Baseline tag `v0.8.23` at `39a065b` and the `0.8.24` candidate used the same local volume and dependencies.
@@ -135,14 +148,16 @@ This external fixture is a candidate-finding and paired Kudzu regression benchma
135
148
 
136
149
  ## 0.8.22 Versioned Compiler Foundation
137
150
 
138
- Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.21` at `ff38092` and the current `0.8.22` compiler candidate used the same local volume and identical installed dependencies.
151
+ Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.21` at `ff38092` and the then-current `0.8.22` compiler candidate used the same local volume and identical installed dependencies.
139
152
 
140
- The candidate implementation patch over `v0.8.21` had SHA-256 `e1d89c97dd8dfb60d41ae9c14ac64dfbb50466e024f80076f430ae9e725fb28f`, produced by:
153
+ The final `v0.8.21` to `v0.8.22` tagged implementation patch has reproducible SHA-256 `5fbdc3658b8c0d4d568c7ccdbf89c2c1c20c3a275ff9d0fd20e439b479d60530`, produced by:
141
154
 
142
155
  ```bash
143
- git diff --binary v0.8.21 -- framework/build.mjs framework/compiler/list-runtime-codegen.mjs framework/compiler/param-codegen.mjs framework/compiler/route-capability-planner.mjs framework/compiler/runtime-codegen.mjs framework/core.mjs framework/core.d.ts | shasum -a 256
156
+ git diff --binary v0.8.21 v0.8.22 -- framework/build.mjs framework/compiler/list-runtime-codegen.mjs framework/compiler/param-codegen.mjs framework/compiler/route-capability-planner.mjs framework/compiler/runtime-codegen.mjs framework/core.mjs framework/core.d.ts | shasum -a 256
144
157
  ```
145
158
 
159
+ The previously recorded candidate hash does not match this tagged patch, and no intermediate commit exists between the two release tags. The measured candidate is therefore not independently identifiable as the final `v0.8.22` tree from repository history.
160
+
146
161
  Both targets received one warm-up followed by seven clean `worker-effects` production builds in alternating round-robin order. Cleanup remained outside timing. Both medians were exactly 250.1 ms; the distributions overlap and establish no material change.
147
162
 
148
163
  | Target | Build median | Worker raw / gzip | Window raw / gzip |
@@ -159,14 +174,16 @@ Before release-content updates, the complete 135-page site and the `bindings`, `
159
174
 
160
175
  ## 0.8.21 Explicit Effect Ownership
161
176
 
162
- Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.20` at `7fb6e37` and the current `0.8.21` compiler candidate used the same local volume and identical installed dependencies.
177
+ Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.20` at `7fb6e37` and the then-current `0.8.21` compiler candidate used the same local volume and identical installed dependencies.
163
178
 
164
- The candidate implementation patch over `v0.8.20` had SHA-256 `ac0b1921bbbfb72f45d9b53338bec96bf9ab3d1680446148dfd8548b871bcbe4`, produced by:
179
+ The final `v0.8.20` to `v0.8.21` tagged implementation patch has reproducible SHA-256 `c78159ccce1f88a5ed06445d5e0b113953576529a5845e7772ab386b8adf166a`, produced by:
165
180
 
166
181
  ```bash
167
- git diff --binary v0.8.20 -- framework/build.mjs framework/compiler/descriptor-session.mjs framework/compiler/effect-analysis.mjs framework/compiler/ir/module-ir.mjs framework/compiler/worker-compiler.mjs framework/core.d.ts | shasum -a 256
182
+ git diff --binary v0.8.20 v0.8.21 -- framework/build.mjs framework/compiler/descriptor-session.mjs framework/compiler/effect-analysis.mjs framework/compiler/ir/module-ir.mjs framework/compiler/worker-compiler.mjs framework/core.d.ts | shasum -a 256
168
183
  ```
169
184
 
185
+ The previously recorded candidate hash does not match this tagged patch, and no intermediate commit exists between the two release tags. The measured candidate is therefore not independently identifiable as the final `v0.8.21` tree from repository history.
186
+
170
187
  Both targets received one warm-up followed by seven clean `worker-effects` production builds in alternating round-robin order. Cleanup remained outside timing. The distributions overlap; the 1.02% lower candidate median does not establish a material change.
171
188
 
172
189
  | Target | Build median | Worker raw / gzip | Window raw / gzip |
package/README.md CHANGED
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
14
14
 
15
15
  > Experimental `0.8.x`: the compiler API and supported TSX surface may change.
16
16
 
17
- **Latest release: 0.8.24 - Measured Goal B optimizations.** Large keyed restoration batches mount connected flat-list additions once, and no-op normalization passes skip redundant full-AST parent repair. Maintained and external benchmarks measure 19.77% faster 2,000-row restoration and 6.26% faster 1,000-product builds. Read the [release notes](./RELEASES.md#0824---measured-goal-b-optimizations), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.24), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.8.25 - Exact route-entry reuse.** Byte-identical generated route entries share one build-local transform result, compiler boundaries fail closed, and package release gates verify what is installed and published. The 1,011-page benchmark measures a 9.17% build improvement with identical deploy output. Read the [release notes](./RELEASES.md#0825---exact-route-entry-reuse), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.25), or follow the [architecture packet](./docs/next-architecture/README.md).
18
18
 
19
19
  - [Documentation](https://kudzujs.cloud/docs)
20
20
  - [Installation guide](https://kudzujs.cloud/docs#install)
package/RELEASES.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.8.25 - Exact route-entry reuse
4
+
5
+ Kudzu 0.8.25 removes measured repeated esbuild work from large multi-route builds, strengthens compiler boundary validation, and makes package publication gates explicit without changing generated deploy bytes or browser behavior.
6
+
7
+ ### Changed in 0.8.25
8
+
9
+ - Generated native, parameter, and effect route entries reuse one build-local esbuild result when their complete source is byte-identical after route-relative URLs are resolved.
10
+ - Normalization passes now declare an immutable-root contract and reject any result that is not a TypeScript `SourceFile`.
11
+ - Finalized ModuleIR rejects invalid local slots and dangling signal, handler, derived, keyed-parent, keyed-child, keyed-selector, and ownership references.
12
+ - CI covers the minimum Node 22 runtime and the Node 24 Chrome suite, while release jobs exercise the packed package and verify package, lockfile, tag, and registry versions.
13
+ - Missing `0.8.21`, `0.8.22`, and `0.8.23` release pages and their canonical sitemap entries are restored.
14
+
15
+ ### Performance
16
+
17
+ - Seven alternating clean builds of the public 1,000-product, 1,011-page fixture measured 12,581.4 ms with exact-source reuse versus 13,851.0 ms with repeated transformation, a 9.17% improvement.
18
+ - Every deploy path and SHA-256 hash matched. The map is scoped to one build and only the three generated route-entry families; no persistent or generalized transform cache was added.
19
+ - Raw arrays, environment, fixture revision, methodology, and limitations are recorded in `PERFORMANCE.md`.
20
+
21
+ ### Validation
22
+
23
+ - `npm run check`, `npm run test:package`, and all 174 tests pass.
24
+ - The packed-package smoke test installs the produced tarball into a temporary consumer, imports both public entry points, runs the packed CLI, and verifies generated HTML.
25
+ - The external 1,011-page benchmark produced byte-identical deploy manifests across both targets.
26
+ - No accepted syntax, public runtime API, VDOM, hydration, scheduler, retained browser component tree, or `create-kudzu` template change was added.
27
+
28
+ ### Upgrade
29
+
30
+ ```bash
31
+ npm install @kudzujs/core@^0.8.25
32
+ ```
33
+
3
34
  ## 0.8.24 - Measured Goal B optimizations
4
35
 
5
36
  Kudzu 0.8.24 starts the architecture optimization Goal B with two benchmark-proven optimizations for large keyed restoration and large multi-route builds while preserving complete HTML, direct DOM ownership, and existing compiler output. This is distinct from the completed historical Worker capability milestone in `GOAL_B.md`.
@@ -31,6 +31,8 @@ The maintained benchmark measured a 26.3 ms restoration median. For top-level fl
31
31
 
32
32
  [`SimYunSup/kudzu-based-bench`](https://github.com/SimYunSup/kudzu-based-bench) then supplied a 1,000-product, 1,011-page build fixture. Twenty-one alternating clean builds measured 6,684.7 ms for clean `0.8.23` and 6,266.5 ms after skipping parent-pointer repair for normalization passes that return the unchanged AST, a 6.26% improvement. The output retained 3,056 files and only the separately optimized `kudzu-list.js` changed. Full raw arrays and limitations are recorded in [`PERFORMANCE.md`](../../PERFORMANCE.md).
33
33
 
34
+ The `0.8.25` investigation isolated repeated esbuild work by comparing ordinary transformation with build-local reuse keyed by the complete generated route-entry source. Seven alternating 1,011-page builds measured 13,851.0 ms and 12,581.4 ms medians, a 9.17% improvement, with identical emitted paths and hashes. The retained implementation applies only to native, parameter, and effect route entries and keeps no data beyond one build.
35
+
34
36
  ## Candidate Order
35
37
 
36
38
  1. Profile the largest measured loss in a maintained fixture.
@@ -40,7 +42,7 @@ The maintained benchmark measured a 26.3 ms restoration median. For top-level fl
40
42
 
41
43
  Known historical pressure points include large keyed-list removal/reconciliation and broad runtime specialization, but neither is authorized without a reproduced current loss.
42
44
 
43
- The next evidence-backed investigation is repeated esbuild transformation of byte-identical generated route entries. The 1,000-product fixture emitted identical effect entries and identical native entries across product routes, but their transformation cost has not yet been isolated from rendering and filesystem work. Reuse transformed source only if an interleaved benchmark establishes a material gain while every route file, URL, define, and byte remains unchanged. Do not add a generalized cache.
45
+ Repeated esbuild transformation of byte-identical generated route entries cleared the materiality and output gates. Further optimization is not authorized until another current fixture isolates a measured loss; do not broaden the route-entry map into a generalized cache.
44
46
 
45
47
  ## Benchmark Contract
46
48
 
@@ -6,7 +6,7 @@ import { build as bundle, transform } from "esbuild"
6
6
  import { createEffectCodegen } from "./compiler/effect-codegen.mjs"
7
7
  import { generateListRuntime } from "./compiler/list-runtime-codegen.mjs"
8
8
  import { assetPath, browserPath, relativeModulePath, withBase } from "./compiler/path-helpers.mjs"
9
- import { clientModulePath, collectClientModules, compileClientModule, compiledPath, compileSource, layoutExportError, orderSourceStyles, reachableSourceFiles, safeStaticFiles } from "./compiler/source-compiler.mjs"
9
+ import { collectClientModules, compileClientModule, compiledPath, compileSource, layoutExportError, orderSourceStyles, reachableSourceFiles, safeStaticFiles } from "./compiler/source-compiler.mjs"
10
10
  import { createParamCodegen } from "./compiler/param-codegen.mjs"
11
11
  import { planRouteCapabilities, usesRouteDependencyRuntime } from "./compiler/route-capability-planner.mjs"
12
12
  import { generateBindingRuntime, generateCoreRuntime, generateEffectRuntime, generateNativeRuntime, generateNavigationRuntime, specializeRuntime } from "./compiler/runtime-codegen.mjs"
@@ -95,6 +95,7 @@ export async function build({ quiet = false, minify = true } = {}) {
95
95
  const effectEntries = []
96
96
  const nativeEntries = []
97
97
  const paramEntries = []
98
+ const routeEntryTransforms = new Map()
98
99
  const rewrites = []
99
100
  const emittedRoutes = new Set()
100
101
  const emittedApplicationRoutes = new Set()
@@ -264,7 +265,7 @@ export async function build({ quiet = false, minify = true } = {}) {
264
265
  if (hasNativeHandlers) {
265
266
  const generated = generateNativeRuntime(await readFile(new URL("./native-runtime.js", import.meta.url), "utf8"), capabilityIR)
266
267
  await writeJavaScript(join(assetsDirectory, "kudzu-native.js"), generated.source, minify, generated.define)
267
- for (const entry of nativeEntries) await printNativeEntry(entry, assetsDirectory, base, minify)
268
+ for (const entry of nativeEntries) await printNativeEntry(entry, assetsDirectory, base, minify, routeEntryTransforms)
268
269
  }
269
270
  if (navigationGroups.length) {
270
271
  const navigationSource = await readFile(new URL("./navigation-runtime.js", import.meta.url), "utf8")
@@ -280,12 +281,12 @@ export async function build({ quiet = false, minify = true } = {}) {
280
281
  for (const entry of paramEntries) {
281
282
  const output = join(assetsDirectory, entry.path)
282
283
  await mkdir(dirname(output), { recursive: true })
283
- await writeJavaScript(output, printParamEntry(entry.schema, entry.params, entry.searchParams, entry.searchParamsWritable, output, assetsDirectory, base, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify)
284
+ await writeRouteEntry(output, printParamEntry(entry.schema, entry.params, entry.searchParams, entry.searchParamsWritable, output, assetsDirectory, base, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify, routeEntryTransforms)
284
285
  }
285
286
  for (const entry of effectEntries) {
286
287
  const output = join(assetsDirectory, entry.path)
287
288
  await mkdir(dirname(output), { recursive: true })
288
- await writeJavaScript(output, printEffectEntry(entry.effects, output, emittedHandlerModules, assetsDirectory, base, entry.paramPath, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify)
289
+ await writeRouteEntry(output, printEffectEntry(entry.effects, output, emittedHandlerModules, assetsDirectory, base, entry.paramPath, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify, routeEntryTransforms)
289
290
  }
290
291
  const clientModules = await collectClientModules(emittedHandlerModules.flatMap(module => module.clientImports).map(file => resolve(root, file)), sourceFileSet)
291
292
  for (const file of clientModules) {
@@ -358,13 +359,13 @@ function preloadModules(html) {
358
359
  return html.replace(scripts[0][0], `${links}${scripts[0][0]}`)
359
360
  }
360
361
 
361
- async function printNativeEntry(entry, assetsDirectory, base, minify) {
362
+ async function printNativeEntry(entry, assetsDirectory, base, minify, transforms) {
362
363
  const output = join(assetsDirectory, entry.path)
363
364
  await mkdir(dirname(output), { recursive: true })
364
365
  const imports = entry.modules.map((module, index) => `import * as __kNativeModule${index} from ${JSON.stringify(module)}`).join("\n")
365
366
  const registrations = entry.modules.map((module, index) => `[${JSON.stringify(module)}, __kNativeModule${index}]`).join(",")
366
367
  const runtime = assetPath(base, "assets/kudzu-native.js")
367
- await writeJavaScript(output, `import { registerNativeModules } from ${JSON.stringify(runtime)}\n${imports}\nregisterNativeModules([${registrations}])`, minify)
368
+ await writeRouteEntry(output, `import { registerNativeModules } from ${JSON.stringify(runtime)}\n${imports}\nregisterNativeModules([${registrations}])`, minify, transforms)
368
369
  }
369
370
 
370
371
  function runtimeEffects(effects, lifetimes = false) {
@@ -388,6 +389,15 @@ async function writeJavaScript(file, source, minify, define) {
388
389
  await writeFile(file, code)
389
390
  }
390
391
 
392
+ async function writeRouteEntry(file, source, minify, transforms) {
393
+ let code = transforms.get(source)
394
+ if (code === undefined) {
395
+ code = minify ? (await transform(source, { format: "esm", legalComments: "none", minify, target: "es2022" })).code : source
396
+ transforms.set(source, code)
397
+ }
398
+ await writeFile(file, code)
399
+ }
400
+
391
401
  async function writeBundledJavaScript(file, source, minify, define) {
392
402
  const result = await bundle({
393
403
  stdin: { contents: source, resolveDir: dirname(file), sourcefile: file },
@@ -2,7 +2,7 @@ import ts from "typescript"
2
2
  import { createComponentAnalysis } from "./analysis/component-analysis.mjs"
3
3
  import { bindingNames, isFunctionLike, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, unwrapExpression } from "./ast-helpers.mjs"
4
4
  import { generateCommandBehavior } from "./codegen/command-codegen.mjs"
5
- import { createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler } from "./ir/module-ir.mjs"
5
+ import { assertModuleIRReferences, createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler } from "./ir/module-ir.mjs"
6
6
 
7
7
  export function createSemanticArtifact(file) {
8
8
  return { componentAnalysis: createComponentAnalysis(file), moduleIR: createModuleIR(file) }
@@ -222,6 +222,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
222
222
  const imports = [...callbacks, ...reactiveBindings].flatMap(entry => entry.imports ?? []).map(importRecord)
223
223
  moduleIR.imports = [...new Map(imports.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry])).values()]
224
224
  moduleIR.clientModules = [...clientModules]
225
+ assertModuleIRReferences(moduleIR)
225
226
  }
226
227
 
227
228
  function source(node) {
@@ -2,6 +2,33 @@ export function createModuleIR(file) {
2
2
  return { version: 1, file, signals: [], handlers: [], bindings: [], derived: [], effects: [], keyedBlocks: [], imports: [], clientModules: [] }
3
3
  }
4
4
 
5
+ export function assertModuleIRReferences(moduleIR) {
6
+ const slot = (records, value, label) => {
7
+ if (!Number.isInteger(value) || value < 0 || value >= records.length) throw new Error(`ModuleIR ${label} references missing slot ${JSON.stringify(value)}`)
8
+ }
9
+ for (const [name, records] of [["signal", moduleIR.signals], ["handler", moduleIR.handlers], ["binding", moduleIR.bindings], ["derived", moduleIR.derived], ["effect", moduleIR.effects], ["keyed block", moduleIR.keyedBlocks]]) {
10
+ records.forEach((record, index) => {
11
+ if (record.slot !== index) throw new Error(`ModuleIR ${name} slot ${JSON.stringify(record.slot)} must equal its index ${index}`)
12
+ })
13
+ }
14
+ for (const handler of moduleIR.handlers) {
15
+ for (const command of handler.commands ?? []) slot(moduleIR.signals, command.signal, `handler ${handler.slot} command signal`)
16
+ if (handler.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, handler.keyedBlock, `handler ${handler.slot} keyed block`)
17
+ }
18
+ for (const binding of moduleIR.bindings) if (binding.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, binding.keyedBlock, `binding ${binding.slot} keyed block`)
19
+ for (const effect of moduleIR.effects) {
20
+ slot(moduleIR.handlers, effect.setup?.handler, `effect ${effect.slot} setup handler`)
21
+ for (const dependency of effect.dependencies ?? []) if (dependency.kind === "derived") slot(moduleIR.derived, dependency.derived, `effect ${effect.slot} derived dependency`)
22
+ if (effect.ownership?.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, effect.ownership.keyedBlock, `effect ${effect.slot} keyed block`)
23
+ }
24
+ for (const block of moduleIR.keyedBlocks) {
25
+ if (block.parent !== undefined) slot(moduleIR.keyedBlocks, block.parent, `keyed block ${block.slot} parent`)
26
+ for (const child of block.children ?? []) slot(moduleIR.keyedBlocks, child, `keyed block ${block.slot} child`)
27
+ if (block.selector !== undefined) slot(moduleIR.derived, block.selector, `keyed block ${block.slot} selector`)
28
+ }
29
+ return moduleIR
30
+ }
31
+
5
32
  export function registerCommandHandler(moduleIR, commands, source, scope = "module") {
6
33
  const slots = new Map(moduleIR.signals.map(signal => [signal.key, signal.slot]))
7
34
  for (const { state, owner = scope } of commands) {
@@ -1,8 +1,10 @@
1
1
  import ts from "typescript"
2
2
 
3
3
  export function applyNormalizationPasses(sourceFile, passes) {
4
- for (const pass of passes) {
4
+ // Passes treat their input root as immutable and return the SourceFile used by the next pass.
5
+ for (const [index, pass] of passes.entries()) {
5
6
  const next = pass(sourceFile)
7
+ if (!next || !ts.isSourceFile(next)) throw new TypeError(`Normalization pass ${index + 1} must return a TypeScript SourceFile`)
6
8
  if (next !== sourceFile) ts.setParentRecursive(next, false)
7
9
  sourceFile = next
8
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.8.24",
3
+ "version": "0.8.25",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,6 +54,7 @@
54
54
  "dev": "node ./bin/kudzu.mjs dev",
55
55
  "check": "tsc --noEmit && tsc -p test/fixtures/tsconfig.json --noEmit && node ./bin/kudzu.mjs build",
56
56
  "test": "node --test test/*.test.mjs",
57
+ "test:package": "node test/package-smoke.mjs",
57
58
  "benchmark": "node test/performance.mjs",
58
59
  "benchmark:keyed": "node test/keyed-performance.mjs",
59
60
  "benchmark:commerce": "node test/commerce-build-performance.mjs",