@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.
- package/MIGRATION_ROADMAP.md +36 -1
- package/PERFORMANCE.md +79 -1
- package/README.md +2 -2
- package/RELEASES.md +29 -0
- package/bin/kudzu.mjs +10 -1
- package/docs/next-architecture/0.9-baseline.md +1199 -0
- package/docs/next-architecture/0.9-benchmark-contracts.md +507 -0
- package/docs/next-architecture/0.9-component-property-contract.md +89 -0
- package/docs/next-architecture/0.9-compression-ledger.md +227 -0
- package/docs/next-architecture/0.9-final-proof-audit.md +176 -0
- package/docs/next-architecture/0.9-implementation-plan.md +1819 -0
- package/docs/next-architecture/0.9-resource-lifecycle.md +118 -0
- package/docs/next-architecture/0.9-semantic-compression.md +384 -0
- package/docs/next-architecture/README.md +16 -12
- package/docs/next-architecture/compiler-current-architecture.md +7 -7
- package/docs/next-architecture/large-application-ai-native-roadmap.md +5 -3
- package/docs/next-architecture/versioning.md +1 -1
- package/framework/README.md +2 -0
- package/framework/binding-runtime.js +4 -4
- package/framework/build.mjs +135 -30
- package/framework/compiler/ast-helpers.mjs +5 -0
- package/framework/compiler/browser-signal-passes.mjs +2 -7
- package/framework/compiler/collection-analysis.mjs +4 -0
- package/framework/compiler/descriptor-session.mjs +36 -12
- package/framework/compiler/effect-analysis.mjs +28 -8
- package/framework/compiler/effect-codegen.mjs +79 -36
- package/framework/compiler/effect-private-ref-pass.mjs +4 -8
- package/framework/compiler/handler-lowering.mjs +12 -7
- package/framework/compiler/ir/module-ir.mjs +26 -4
- package/framework/compiler/list-runtime-codegen.mjs +4 -2
- package/framework/compiler/optimize/command-specialization.mjs +4 -7
- package/framework/compiler/route-artifact-report.mjs +4 -3
- package/framework/compiler/route-build-record.mjs +12 -0
- package/framework/compiler/route-capability-planner.mjs +3 -3
- package/framework/compiler/route-ir.mjs +27 -11
- package/framework/compiler/runtime-codegen.mjs +2 -2
- package/framework/compiler/source-compiler.mjs +359 -78
- package/framework/core.d.ts +1 -0
- package/framework/core.mjs +18 -5
- package/framework/dependency-runtime.js +1 -1
- package/framework/effect-runtime.js +2 -2
- package/framework/list-runtime.js +67 -24
- package/framework/native-runtime.js +12 -9
- package/framework/runtime.js +1 -1
- package/framework/serialization.js +13 -6
- package/framework/shared-runtime.js +14 -12
- 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
|
|
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)
|
|
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
|
|
180
|
-
if (
|
|
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
|
|
192
|
+
for (const key of keys) {
|
|
183
193
|
if (Array.isArray(value) && key === "length") continue
|
|
184
|
-
|
|
185
|
-
|
|
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(
|
|
11
|
-
["export function registerUnmountHook(unmount) {\n unmountHooks.push(unmount)\n}", "export function registerUnmountHook(unmount) {\n unmountHooks.push(
|
|
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
|
}
|