@kudzujs/core 0.8.62 → 0.9.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 (47) hide show
  1. package/MIGRATION_ROADMAP.md +36 -1
  2. package/PERFORMANCE.md +79 -1
  3. package/README.md +2 -2
  4. package/RELEASES.md +29 -0
  5. package/bin/kudzu.mjs +10 -1
  6. package/docs/next-architecture/0.9-baseline.md +1199 -0
  7. package/docs/next-architecture/0.9-benchmark-contracts.md +507 -0
  8. package/docs/next-architecture/0.9-component-property-contract.md +89 -0
  9. package/docs/next-architecture/0.9-compression-ledger.md +227 -0
  10. package/docs/next-architecture/0.9-final-proof-audit.md +176 -0
  11. package/docs/next-architecture/0.9-implementation-plan.md +1819 -0
  12. package/docs/next-architecture/0.9-resource-lifecycle.md +118 -0
  13. package/docs/next-architecture/0.9-semantic-compression.md +384 -0
  14. package/docs/next-architecture/README.md +16 -12
  15. package/docs/next-architecture/compiler-current-architecture.md +7 -7
  16. package/docs/next-architecture/large-application-ai-native-roadmap.md +5 -3
  17. package/docs/next-architecture/versioning.md +1 -1
  18. package/framework/README.md +2 -0
  19. package/framework/binding-runtime.js +4 -4
  20. package/framework/build.mjs +135 -30
  21. package/framework/compiler/ast-helpers.mjs +5 -0
  22. package/framework/compiler/browser-signal-passes.mjs +2 -7
  23. package/framework/compiler/collection-analysis.mjs +4 -0
  24. package/framework/compiler/descriptor-session.mjs +36 -12
  25. package/framework/compiler/effect-analysis.mjs +28 -8
  26. package/framework/compiler/effect-codegen.mjs +79 -36
  27. package/framework/compiler/effect-private-ref-pass.mjs +4 -8
  28. package/framework/compiler/handler-lowering.mjs +12 -7
  29. package/framework/compiler/ir/module-ir.mjs +26 -4
  30. package/framework/compiler/list-runtime-codegen.mjs +4 -2
  31. package/framework/compiler/optimize/command-specialization.mjs +4 -7
  32. package/framework/compiler/route-artifact-report.mjs +4 -3
  33. package/framework/compiler/route-build-record.mjs +12 -0
  34. package/framework/compiler/route-capability-planner.mjs +3 -3
  35. package/framework/compiler/route-ir.mjs +27 -11
  36. package/framework/compiler/runtime-codegen.mjs +2 -2
  37. package/framework/compiler/source-compiler.mjs +359 -78
  38. package/framework/core.d.ts +1 -0
  39. package/framework/core.mjs +18 -5
  40. package/framework/dependency-runtime.js +1 -1
  41. package/framework/effect-runtime.js +2 -2
  42. package/framework/list-runtime.js +67 -24
  43. package/framework/native-runtime.js +12 -9
  44. package/framework/runtime.js +1 -1
  45. package/framework/serialization.js +13 -6
  46. package/framework/shared-runtime.js +14 -12
  47. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import { assertJsonSafe, assertRouteIR } from "./route-ir.mjs"
2
2
 
3
3
  const validated = new WeakSet()
4
+ const releasedPlans = new WeakSet()
4
5
 
5
6
  export function createRouteBuildRecord(input) {
6
7
  const record = {
@@ -22,6 +23,10 @@ export function createRouteBuildRecord(input) {
22
23
  }
23
24
 
24
25
  export function assertRouteBuildRecord(record) {
26
+ if (releasedPlans.has(record)) {
27
+ if (record.plan !== undefined) throw new Error("Released RouteBuildRecord plan was restored")
28
+ return record
29
+ }
25
30
  if (validated.has(record)) return record
26
31
  if (record?.version !== 1) throw new Error(`Unsupported RouteBuildRecord version: ${JSON.stringify(record?.version)}`)
27
32
  if (typeof record.route !== "string" || typeof record.output !== "string" || typeof record.html !== "string" || !isRecord(record.plan)) throw new Error("Invalid RouteBuildRecord v1 structure")
@@ -60,6 +65,13 @@ export function assertRouteBuildRecord(record) {
60
65
  return record
61
66
  }
62
67
 
68
+ export function releaseRouteBuildRecordPlan(record) {
69
+ assertRouteBuildRecord(record)
70
+ record.plan = undefined
71
+ releasedPlans.add(record)
72
+ return record
73
+ }
74
+
63
75
  export function planRouteArtifacts(records, handlerModules, workerReferences, moduleUrl) {
64
76
  for (const record of records) assertRouteBuildRecord(record)
65
77
  const modules = new Map()
@@ -1,8 +1,8 @@
1
1
  import { assertRouteBuildRecord } from "./route-build-record.mjs"
2
2
  import { assertJsonSafe, assertRouteIR } from "./route-ir.mjs"
3
3
 
4
- export function usesRouteDependencyRuntime({ plan, navigable, hasBindings, hasLists }) {
5
- assertRouteIR(plan)
4
+ export function usesRouteDependencyRuntime({ plan, navigable, hasBindings, hasLists }, validate = true) {
5
+ if (validate) assertRouteIR(plan)
6
6
  const hasDependencies = plan.effects.some(effect => effect.dependencies?.length)
7
7
  return !navigable && hasDependencies && !plan.effects.some(effect => effect.owner) && !hasBindings && !hasLists && !plan.events.some(event => event.native)
8
8
  }
@@ -109,7 +109,7 @@ export function planRouteCapabilities(records, { navigationRouteCount = 0 } = {}
109
109
  dependency: routeEntries.some(route => route.usesDependencyRuntime)
110
110
  }
111
111
  }
112
- return validate ? assertCapabilityIR(capabilityIR, records, { navigationRouteCount }) : capabilityIR
112
+ return validate ? assertCapabilityIR(capabilityIR) : capabilityIR
113
113
  }
114
114
 
115
115
  export function assertCapabilityIR(capabilityIR, records, options = {}) {
@@ -1,5 +1,6 @@
1
1
  const markerFields = new Set(["cleanup", "list", "svg", "mount", "static", "indexed", "reducer", "nested", "effects", "conditions", "conditionHandlers", "textRanges", "attributes", "events", "expressions", "expressionAttributes", "fastRelease"])
2
2
  const validated = new WeakSet()
3
+ const jsonSafe = new WeakSet()
3
4
 
4
5
  export function assertRouteIR(plan, { concrete = false } = {}) {
5
6
  if (validated.has(plan) && (!concrete || typeof plan.route === "string")) return plan
@@ -62,7 +63,7 @@ export function assertRouteIR(plan, { concrete = false } = {}) {
62
63
  function assertEvent(event, index, ids) {
63
64
  if (!isRecord(event) || !nonempty(event.event) || event.commands === undefined && event.native === undefined) throw new Error(`Invalid RouteIR v1 event at index ${index}`)
64
65
  for (const command of event.commands ?? []) {
65
- if (!Array.isArray(command) || command.length !== 3 || !["set", "add", "log"].includes(command[0])) throw new Error(`RouteIR event ${index} command has unsupported operation ${JSON.stringify(command?.[0])}`)
66
+ if (!Array.isArray(command) || command.length !== 3 || !["set", "add", "toggle", "log"].includes(command[0])) throw new Error(`RouteIR event ${index} command has unsupported operation ${JSON.stringify(command?.[0])}`)
66
67
  if (!ids.has(command[1]) && !rowTemplate(command[1])) throw new Error(`RouteIR event ${index} command references missing state ${JSON.stringify(command[1])}`)
67
68
  if (command[0] === "add" && (typeof command[2] !== "number" || !Number.isFinite(command[2]))) throw new Error(`RouteIR event ${index} add command requires a finite number`)
68
69
  }
@@ -76,6 +77,13 @@ function assertEffect(effect, index, ids, lists) {
76
77
  if (new Set(effect.dependencies ?? []).size !== (effect.dependencies ?? []).length) throw new Error(`${label} has duplicate dependencies`)
77
78
  for (const state of Object.values(effect.dependencyStates ?? {})) if (!ids.has(state) && !rowTemplate(state)) throw new Error(`${label} derived dependency references missing state ${JSON.stringify(state)}`)
78
79
  if (effect.dependencyExpressions !== undefined && !Array.isArray(effect.dependencyExpressions) || effect.itemDependencies !== undefined && (!Array.isArray(effect.itemDependencies) || effect.itemDependencies.some(field => !nonempty(field)))) throw new Error(`${label} has invalid dependencies`)
80
+ if (effect.dependencyEvaluators !== undefined) {
81
+ if (!Array.isArray(effect.dependencyEvaluators) || !effect.dependencyEvaluators.length) throw new Error(`${label} has invalid calculation dependency evaluators`)
82
+ for (const [dependencyIndex, evaluator] of effect.dependencyEvaluators.entries()) {
83
+ assertReactiveDescriptor(evaluator, `${label} calculation dependency ${dependencyIndex}`, ids)
84
+ if (!nonempty(evaluator.field) || ["__proto__", "constructor", "prototype"].includes(evaluator.field)) throw new Error(`${label} calculation dependency ${dependencyIndex} has invalid field`)
85
+ }
86
+ }
79
87
  if (effect.itemDependencies?.length) {
80
88
  if (!nonempty(effect.listState) || !lists.some(list => list.state === effect.listState) || !effect.owner) throw new Error(`${label} item dependencies require a matching owned list`)
81
89
  }
@@ -165,30 +173,38 @@ function validSeed(seed) {
165
173
  }
166
174
 
167
175
  export function assertJsonSafe(value, label = "Value") {
168
- const invalid = invalidJsonPath(value, new Set(), "$")
176
+ const invalid = invalidJsonPath(value, new Set(), jsonSafe, [])
169
177
  if (invalid) throw new Error(`${label} is not JSON-safe at ${invalid}`)
170
178
  return value
171
179
  }
172
180
 
173
- function invalidJsonPath(value, seen, path) {
181
+ function invalidJsonPath(value, seen, safe, path) {
174
182
  if (value === null || typeof value === "string" || typeof value === "boolean") return undefined
175
- if (typeof value === "number") return Number.isFinite(value) && !Object.is(value, -0) ? undefined : path
176
- if (!value || typeof value !== "object" || seen.has(value) || Object.getOwnPropertySymbols(value).length) return path
183
+ if (typeof value === "number") return Number.isFinite(value) && !Object.is(value, -0) ? undefined : jsonPath(path)
184
+ if (!value || typeof value !== "object" || seen.has(value)) return jsonPath(path)
185
+ if (safe.has(value)) return undefined
177
186
  const prototype = Object.getPrototypeOf(value)
178
- if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return path
179
- const descriptors = Object.getOwnPropertyDescriptors(value)
180
- if (Array.isArray(value) && (Object.keys(descriptors).some(key => key !== "length" && !/^(0|[1-9]\d*)$/.test(key)) || Object.keys(value).length !== value.length)) return path
187
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return jsonPath(path)
188
+ const keys = Reflect.ownKeys(value)
189
+ if (keys.some(key => typeof key !== "string")) return jsonPath(path)
190
+ if (Array.isArray(value) && (keys.some(key => key !== "length" && !/^(0|[1-9]\d*)$/.test(key)) || keys.length - 1 !== value.length)) return jsonPath(path)
181
191
  seen.add(value)
182
- for (const [key, descriptor] of Object.entries(descriptors)) {
192
+ for (const key of keys) {
183
193
  if (Array.isArray(value) && key === "length") continue
184
- if (!descriptor.enumerable || !("value" in descriptor)) return `${path}.${key}`
185
- const invalid = invalidJsonPath(descriptor.value, seen, `${path}.${key}`)
194
+ const descriptor = Object.getOwnPropertyDescriptor(value, key)
195
+ path.push(key)
196
+ if (!descriptor.enumerable || !("value" in descriptor)) return jsonPath(path)
197
+ const invalid = invalidJsonPath(descriptor.value, seen, safe, path)
186
198
  if (invalid) return invalid
199
+ path.pop()
187
200
  }
188
201
  seen.delete(value)
202
+ safe.add(value)
189
203
  return undefined
190
204
  }
191
205
 
206
+ const jsonPath = path => `$${path.map(key => `.${key}`).join("")}`
207
+
192
208
  const isRecord = value => value !== null && typeof value === "object" && !Array.isArray(value)
193
209
  const nonempty = value => typeof value === "string" && value.length > 0
194
210
  const rowTemplate = value => nonempty(value) && value.includes("$k")
@@ -7,8 +7,8 @@ export function generateCoreRuntime(source, capabilityIR) {
7
7
  if (!effects.itemDependencies && capabilityIR.runtime.shared) runtime = replaceRequired(runtime, /\/\* list-item-hooks \*\/[\s\S]*?\/\* list-item-hooks-end \*\/\n/, "", "list item hooks", "shared-runtime.js")
8
8
  if (effects.navigable) runtime = replaceRequired(runtime, "export function registerCommitter(commit) {\n committers.push(commit)\n}", "export function registerCommitter(commit) {\n committers.push(commit)\n return () => {\n const index = committers.indexOf(commit)\n if (index !== -1) committers.splice(index, 1)\n }\n}", "navigable committer", "shared-runtime.js")
9
9
  if (effects.navigableOwners) runtime = replaceSequenceRequired(runtime, [
10
- ["export function registerMountHook(mount) {\n mountHooks.push(mount)\n}", "export function registerMountHook(mount) {\n mountHooks.push(mount)\n return () => {\n const index = mountHooks.indexOf(mount)\n if (index !== -1) mountHooks.splice(index, 1)\n }\n}", "navigable mount hook"],
11
- ["export function registerUnmountHook(unmount) {\n unmountHooks.push(unmount)\n}", "export function registerUnmountHook(unmount) {\n unmountHooks.push(unmount)\n return () => {\n const index = unmountHooks.indexOf(unmount)\n if (index !== -1) unmountHooks.splice(index, 1)\n }\n}", "navigable unmount hook"]
10
+ ["export function registerMountHook(mount, capability) {\n mountHooks.push({ mount, capability })\n}", "export function registerMountHook(mount, capability) {\n const entry = { mount, capability }\n mountHooks.push(entry)\n return () => {\n const index = mountHooks.indexOf(entry)\n if (index !== -1) mountHooks.splice(index, 1)\n }\n}", "navigable mount hook"],
11
+ ["export function registerUnmountHook(unmount, capability) {\n unmountHooks.push({ unmount, capability })\n}", "export function registerUnmountHook(unmount, capability) {\n const entry = { unmount, capability }\n unmountHooks.push(entry)\n return () => {\n const index = unmountHooks.indexOf(entry)\n if (index !== -1) unmountHooks.splice(index, 1)\n }\n}", "navigable unmount hook"]
12
12
  ], "shared-runtime.js")
13
13
  return runtime
14
14
  }