@kudzujs/core 0.8.18 → 0.8.20

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.
@@ -0,0 +1,278 @@
1
+ import ts from "typescript"
2
+ import { isFunctionLike, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, sourceNodeError } from "./ast-helpers.mjs"
3
+
4
+ export function createHandlerLowering({ cloneAst, synthesizeTree }) {
5
+ return { lowerListExpression: printListExpression, lowerNativeHandler: printNativeHandler, lowerReactiveBinding: printReactiveBinding }
6
+
7
+ function printNativeHandler({ exportName, expression, captures, setters, reducers = new Map(), snapshotNested, liveStates = new Set() }) {
8
+ const factory = ts.factory
9
+ const stateNames = new Set(setters.values())
10
+ const snapshotNames = snapshotNested ? nestedStateNames(expression, setters, liveStates) : new Set()
11
+ const snapshots = new Map([...snapshotNames].map(name => [name, factory.createUniqueName("__kEffectState")]))
12
+ const captureSnapshotNames = snapshotNested ? nestedCaptureNames(expression, captures) : new Set()
13
+ const captureSnapshots = new Map([...captureSnapshotNames].map(name => [name, factory.createUniqueName("__kEffectCapture")]))
14
+ const transformer = context => root => {
15
+ const visitor = node => {
16
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && reducers.has(node.expression.text) && !isShadowedIdentifier(node.expression, expression)) {
17
+ const reducer = reducers.get(node.expression.text)
18
+ if (reducer.contextAction) {
19
+ const action = synthesizeTree(cloneAst(reducer.contextAction, factory, context))
20
+ const call = factory.createCallExpression(action, undefined, node.arguments)
21
+ ts.setParentRecursive(call, false)
22
+ return ts.visitNode(call, visitor)
23
+ }
24
+ if (reducer.store) return zustandActionDispatch(factory, reducer, node.arguments.map(argument => ts.visitNode(argument, visitor)))
25
+ if (node.arguments.length !== 1) throw sourceNodeError(node, expression.getSourceFile(), "Reducer dispatches require exactly one action")
26
+ return reducerDispatch(factory, reducer, ts.visitNode(node.arguments[0], visitor))
27
+ }
28
+ if (ts.isShorthandPropertyAssignment(node) && reducers.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
29
+ if (reducers.get(node.name.text).contextAction) throw sourceNodeError(node, expression.getSourceFile(), "Context actions must be called directly inside an event handler")
30
+ if (reducers.get(node.name.text).store) throw sourceNodeError(node, expression.getSourceFile(), "Zustand actions must be called directly inside an event handler")
31
+ return factory.createPropertyAssignment(node.name, reducerReference(factory, reducers.get(node.name.text)))
32
+ }
33
+ if (ts.isIdentifier(node) && reducers.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
34
+ if (reducers.get(node.text).contextAction) throw sourceNodeError(node, expression.getSourceFile(), "Context actions must be called directly inside an event handler")
35
+ if (reducers.get(node.text).store) throw sourceNodeError(node, expression.getSourceFile(), "Zustand actions must be called directly inside an event handler")
36
+ return reducerReference(factory, reducers.get(node.text))
37
+ }
38
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && setters.has(node.expression.text) && !isShadowedIdentifier(node.expression, expression)) {
39
+ return factory.createCallExpression(
40
+ factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"),
41
+ undefined,
42
+ [factory.createStringLiteral(setters.get(node.expression.text)), ...node.arguments.map(argument => ts.visitNode(argument, visitor))]
43
+ )
44
+ }
45
+ if (ts.isShorthandPropertyAssignment(node) && setters.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
46
+ return factory.createPropertyAssignment(node.name, setterReference(factory, setters.get(node.name.text)))
47
+ }
48
+ if (ts.isIdentifier(node) && setters.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
49
+ return setterReference(factory, setters.get(node.text))
50
+ }
51
+ if (ts.isShorthandPropertyAssignment(node) && stateNames.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
52
+ if (snapshots.has(node.name.text) && insideNestedFunction(node, expression)) return factory.createPropertyAssignment(node.name, snapshots.get(node.name.text))
53
+ return factory.createPropertyAssignment(node.name, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.name.text)]))
54
+ }
55
+ if (ts.isIdentifier(node) && stateNames.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
56
+ if (snapshots.has(node.text) && insideNestedFunction(node, expression)) return snapshots.get(node.text)
57
+ return factory.createCallExpression(
58
+ factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"),
59
+ undefined,
60
+ [factory.createStringLiteral(node.text)]
61
+ )
62
+ }
63
+ if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text) && !isShadowedIdentifier(node.name, expression)) {
64
+ if (captureSnapshots.has(node.name.text) && insideNestedFunction(node, expression)) return factory.createPropertyAssignment(node.name, captureSnapshots.get(node.name.text))
65
+ return factory.createPropertyAssignment(node.name, scopeRead(factory, node.name.text))
66
+ }
67
+ if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node) && !isShadowedIdentifier(node, expression)) {
68
+ if (captureSnapshots.has(node.text) && insideNestedFunction(node, expression)) return captureSnapshots.get(node.text)
69
+ return scopeRead(factory, node.text)
70
+ }
71
+ return ts.visitEachChild(node, visitor, context)
72
+ }
73
+ return ts.visitNode(root, visitor)
74
+ }
75
+ const transformed = ts.transform(expression.body, [transformer])
76
+ try {
77
+ let body = ts.isBlock(expression.body)
78
+ ? transformed.transformed[0]
79
+ : factory.createBlock([factory.createReturnStatement(transformed.transformed[0])], true)
80
+ const snapshotDeclarations = [
81
+ ...[...snapshots].map(([name, identifier]) => factory.createVariableDeclaration(identifier, undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(name)]))),
82
+ ...[...captureSnapshots].map(([name, identifier]) => factory.createVariableDeclaration(identifier, undefined, undefined, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "scope"), undefined, [factory.createStringLiteral(name)])))
83
+ ]
84
+ if (snapshotDeclarations.length) body = factory.updateBlock(body, [
85
+ factory.createVariableStatement(undefined, factory.createVariableDeclarationList(snapshotDeclarations, ts.NodeFlags.Const)),
86
+ ...body.statements
87
+ ])
88
+ const modifiers = [factory.createModifier(ts.SyntaxKind.ExportKeyword)]
89
+ if (expression.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) modifiers.push(factory.createModifier(ts.SyntaxKind.AsyncKeyword))
90
+ const declaration = factory.createFunctionDeclaration(
91
+ modifiers,
92
+ expression.asteriskToken,
93
+ exportName,
94
+ undefined,
95
+ [factory.createParameterDeclaration(undefined, undefined, "__k"), ...expression.parameters],
96
+ undefined,
97
+ body
98
+ )
99
+ return {
100
+ code: ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile()),
101
+ stateSnapshots: [...snapshotNames],
102
+ captureSnapshots: [...captureSnapshotNames]
103
+ }
104
+ } finally {
105
+ transformed.dispose()
106
+ }
107
+ }
108
+
109
+ function nestedCaptureNames(expression, captures) {
110
+ const names = new Set()
111
+ const visit = node => {
112
+ if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node) && insideNestedFunction(node, expression) && !isShadowedIdentifier(node, expression)) names.add(node.text)
113
+ ts.forEachChild(node, visit)
114
+ }
115
+ visit(expression.body)
116
+ return names
117
+ }
118
+
119
+ function nestedStateNames(expression, setters, liveStates = new Set()) {
120
+ const states = new Set(setters.values())
121
+ const names = new Set()
122
+ const visit = node => {
123
+ if (ts.isIdentifier(node) && states.has(node.text) && !liveStates.has(node.text) && isReferenceIdentifier(node) && insideNestedFunction(node, expression) && !isShadowedIdentifier(node, expression)) names.add(node.text)
124
+ ts.forEachChild(node, visit)
125
+ }
126
+ visit(expression.body)
127
+ return names
128
+ }
129
+
130
+ function insideNestedFunction(node, root) {
131
+ for (let current = node.parent; current && current !== root; current = current.parent) {
132
+ if (isFunctionLike(current)) return true
133
+ }
134
+ return false
135
+ }
136
+
137
+ function setterReference(factory, stateName) {
138
+ return factory.createArrowFunction(
139
+ undefined,
140
+ undefined,
141
+ [factory.createParameterDeclaration(undefined, undefined, "value")],
142
+ undefined,
143
+ factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
144
+ factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(stateName), factory.createIdentifier("value")])
145
+ )
146
+ }
147
+
148
+ function reducerReference(factory, reducer) {
149
+ const action = factory.createUniqueName("__kAction")
150
+ return factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, action)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), reducerDispatch(factory, reducer, action))
151
+ }
152
+
153
+ function reducerDispatch(factory, reducer, action) {
154
+ if (reducer.store) return zustandActionDispatch(factory, reducer, [action])
155
+ const previous = factory.createUniqueName("__kPrevious")
156
+ const update = factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, previous)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createCallExpression(factory.createIdentifier(reducer.reducer), undefined, [previous, action]))
157
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(reducer.state), update])
158
+ }
159
+
160
+ function zustandActionDispatch(factory, reducer, args) {
161
+ const previous = factory.createUniqueName("__kPrevious")
162
+ const current = factory.createUniqueName("__kStore")
163
+ const updateValue = factory.createUniqueName("__kUpdate")
164
+ const partial = factory.createUniqueName("__kPartial")
165
+ const action = factory.createUniqueName("__kAction")
166
+ const set = factory.createIdentifier(reducer.store.setName)
167
+ const merge = factory.createExpressionStatement(factory.createBinaryExpression(current, factory.createToken(ts.SyntaxKind.EqualsToken), factory.createObjectLiteralExpression([
168
+ factory.createSpreadAssignment(current),
169
+ factory.createSpreadAssignment(partial)
170
+ ])))
171
+ const setBody = factory.createBlock([
172
+ factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(partial, undefined, undefined, factory.createConditionalExpression(
173
+ factory.createBinaryExpression(factory.createTypeOfExpression(updateValue), factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), factory.createStringLiteral("function")),
174
+ undefined,
175
+ factory.createCallExpression(updateValue, undefined, [current]),
176
+ undefined,
177
+ updateValue
178
+ ))], ts.NodeFlags.Const)),
179
+ merge
180
+ ], true)
181
+ const body = factory.createBlock([
182
+ factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(current, undefined, undefined, factory.createObjectLiteralExpression([factory.createPropertyAssignment(reducer.store.field, previous)]))], ts.NodeFlags.Let)),
183
+ factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(set, undefined, undefined, factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, updateValue)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), setBody))], ts.NodeFlags.Const)),
184
+ factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(action, undefined, undefined, synthesizeTree(reducer.store.actions.get(reducer.action)))], ts.NodeFlags.Const)),
185
+ factory.createExpressionStatement(factory.createCallExpression(action, undefined, args)),
186
+ factory.createReturnStatement(factory.createPropertyAccessExpression(current, reducer.store.field))
187
+ ], true)
188
+ const update = factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, previous)], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), body)
189
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "set"), undefined, [factory.createStringLiteral(reducer.state), update])
190
+ }
191
+
192
+ function printReactiveBinding({ exportName, expression, captures, states }) {
193
+ const factory = ts.factory
194
+ const transformer = context => root => {
195
+ const visitor = node => {
196
+ if (ts.isShorthandPropertyAssignment(node) && states.has(node.name.text)) {
197
+ return factory.createPropertyAssignment(
198
+ node.name,
199
+ factory.createCallExpression(
200
+ factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"),
201
+ undefined,
202
+ [factory.createStringLiteral(node.name.text)]
203
+ )
204
+ )
205
+ }
206
+ if (ts.isIdentifier(node) && states.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) {
207
+ return factory.createCallExpression(
208
+ factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"),
209
+ undefined,
210
+ [factory.createStringLiteral(node.text)]
211
+ )
212
+ }
213
+ if (ts.isShorthandPropertyAssignment(node) && captures.has(node.name.text)) {
214
+ return factory.createPropertyAssignment(node.name, scopeRead(factory, node.name.text))
215
+ }
216
+ if (ts.isIdentifier(node) && captures.has(node.text) && isReferenceIdentifier(node)) {
217
+ return scopeRead(factory, node.text)
218
+ }
219
+ return ts.visitEachChild(node, visitor, context)
220
+ }
221
+ return ts.visitNode(root, visitor)
222
+ }
223
+ const transformed = ts.transform(expression, [transformer])
224
+ try {
225
+ const declaration = factory.createFunctionDeclaration(
226
+ [factory.createModifier(ts.SyntaxKind.ExportKeyword)],
227
+ undefined,
228
+ exportName,
229
+ undefined,
230
+ [factory.createParameterDeclaration(undefined, undefined, "__k")],
231
+ undefined,
232
+ factory.createBlock([factory.createReturnStatement(transformed.transformed[0])], true)
233
+ )
234
+ return ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile())
235
+ } finally {
236
+ transformed.dispose()
237
+ }
238
+ }
239
+
240
+ function printListExpression({ exportName, expression, item, index, states = new Set() }) {
241
+ const factory = ts.factory
242
+ const transformer = context => root => {
243
+ const visitor = node => {
244
+ if (ts.isShorthandPropertyAssignment(node) && states.has(node.name.text)) {
245
+ return factory.createPropertyAssignment(node.name, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.name.text)]))
246
+ }
247
+ if (ts.isIdentifier(node) && states.has(node.text) && isReferenceIdentifier(node) && !isShadowedByParameter(node, expression)) {
248
+ return factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "get"), undefined, [factory.createStringLiteral(node.text)])
249
+ }
250
+ return ts.visitEachChild(node, visitor, context)
251
+ }
252
+ return ts.visitNode(root, visitor)
253
+ }
254
+ const transformed = ts.transform(expression, [transformer])
255
+ const declaration = ts.factory.createFunctionDeclaration(
256
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
257
+ undefined,
258
+ exportName,
259
+ undefined,
260
+ [ts.factory.createParameterDeclaration(undefined, undefined, item), ts.factory.createParameterDeclaration(undefined, undefined, index ?? "__kIndex"), ts.factory.createParameterDeclaration(undefined, undefined, "__k")],
261
+ undefined,
262
+ ts.factory.createBlock([ts.factory.createReturnStatement(transformed.transformed[0])], true)
263
+ )
264
+ try {
265
+ return ts.createPrinter().printNode(ts.EmitHint.Unspecified, declaration, expression.getSourceFile())
266
+ } finally {
267
+ transformed.dispose()
268
+ }
269
+ }
270
+
271
+ function scopeRead(factory, name) {
272
+ return factory.createCallExpression(
273
+ factory.createPropertyAccessExpression(factory.createIdentifier("__k"), "scope"),
274
+ undefined,
275
+ [factory.createStringLiteral(name)]
276
+ )
277
+ }
278
+ }
@@ -1,5 +1,5 @@
1
1
  export function createModuleIR(file) {
2
- return { version: 1, file, signals: [], handlers: [] }
2
+ return { version: 1, file, signals: [], handlers: [], bindings: [], derived: [], keyedBlocks: [], imports: [], clientModules: [] }
3
3
  }
4
4
 
5
5
  export function registerCommandHandler(moduleIR, commands, source, scope = "module") {
@@ -21,3 +21,27 @@ export function registerCommandHandler(moduleIR, commands, source, scope = "modu
21
21
  moduleIR.handlers.push(handler)
22
22
  return handler
23
23
  }
24
+
25
+ export function registerModuleHandler(moduleIR, descriptor) {
26
+ const handler = { slot: moduleIR.handlers.length, kind: "module-export", ...descriptor }
27
+ moduleIR.handlers.push(handler)
28
+ return handler
29
+ }
30
+
31
+ export function registerBinding(moduleIR, descriptor) {
32
+ const binding = { slot: moduleIR.bindings.length, kind: "module-export", ...descriptor }
33
+ moduleIR.bindings.push(binding)
34
+ return binding
35
+ }
36
+
37
+ export function registerDerived(moduleIR, descriptor) {
38
+ const derived = { slot: moduleIR.derived.length, ...descriptor }
39
+ moduleIR.derived.push(derived)
40
+ return derived
41
+ }
42
+
43
+ export function registerKeyedBlock(moduleIR, descriptor) {
44
+ const block = { slot: moduleIR.keyedBlocks.length, ...descriptor }
45
+ moduleIR.keyedBlocks.push(block)
46
+ return block
47
+ }
@@ -80,6 +80,57 @@ export type MetadataContext<Props = Record<string, unknown>> = {
80
80
  props: Props
81
81
  }
82
82
 
83
+ export type ListSourceDescriptor =
84
+ | { state: string; truthy?: unknown; falsy?: unknown }
85
+ | {
86
+ module: string
87
+ handler: string
88
+ states: Record<string, string>
89
+ scope: Record<string, unknown>
90
+ scopeStates: Record<string, string>
91
+ scopeBindings: Record<string, unknown>
92
+ }
93
+
94
+ export type ChildListDescriptor = {
95
+ id: string
96
+ field: string
97
+ key: string | null
98
+ selector?: unknown[]
99
+ }
100
+
101
+ export type ListDescriptor = {
102
+ id: string
103
+ state: string
104
+ key: string | null
105
+ keys: Array<string | number>
106
+ svg?: true
107
+ static?: true
108
+ source?: ListSourceDescriptor
109
+ ownerField?: string
110
+ selector?: unknown[]
111
+ selectorStates?: Record<string, string>
112
+ indexed?: true
113
+ reducer?: true
114
+ children?: ChildListDescriptor[]
115
+ mount?: true
116
+ nested?: true
117
+ effects?: true
118
+ conditions?: true
119
+ conditionHandlers?: true
120
+ textRanges?: true
121
+ attributes?: true
122
+ events?: true
123
+ expressions?: true
124
+ expressionAttributes?: true
125
+ expressionStates?: string[]
126
+ rowStates?: Array<{ id: string; initialValue: unknown }>
127
+ rowConditions?: string[]
128
+ rowRefs?: string[]
129
+ fastRelease?: true
130
+ seed?: Record<string, string | number | boolean | null>
131
+ valueSeed?: Record<string, string | number | boolean | null>
132
+ }
133
+
83
134
  export function renderPage<Props = Record<string, never>>(
84
135
  component: (props: Props) => unknown | Promise<unknown>,
85
136
  metadata?: PageMetadata,
@@ -117,6 +168,6 @@ export function renderPage<Props = Record<string, never>>(
117
168
  scopeBindings?: Record<string, unknown>
118
169
  }>
119
170
  conditions: Array<Record<string, unknown>>
120
- lists: Array<Record<string, unknown>>
171
+ lists: ListDescriptor[]
121
172
  }
122
173
  }>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.8.18",
3
+ "version": "0.8.20",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",