@tanstack/start-plugin-core 1.132.0-alpha.0 → 1.132.0-alpha.10

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 (67) hide show
  1. package/dist/esm/create-server-fn-plugin/compiler.d.ts +61 -0
  2. package/dist/esm/create-server-fn-plugin/compiler.js +336 -0
  3. package/dist/esm/create-server-fn-plugin/compiler.js.map +1 -0
  4. package/dist/esm/create-server-fn-plugin/handleCreateServerFn.d.ts +6 -0
  5. package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js +85 -0
  6. package/dist/esm/create-server-fn-plugin/handleCreateServerFn.js.map +1 -0
  7. package/dist/esm/create-server-fn-plugin/plugin.d.ts +3 -0
  8. package/dist/esm/create-server-fn-plugin/plugin.js +113 -0
  9. package/dist/esm/create-server-fn-plugin/plugin.js.map +1 -0
  10. package/dist/esm/index.d.ts +1 -0
  11. package/dist/esm/index.js +2 -0
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/output-directory.js +5 -2
  14. package/dist/esm/output-directory.js.map +1 -1
  15. package/dist/esm/plugin.d.ts +1 -1
  16. package/dist/esm/plugin.js +23 -14
  17. package/dist/esm/plugin.js.map +1 -1
  18. package/dist/esm/schema.d.ts +20 -28
  19. package/dist/esm/schema.js +10 -14
  20. package/dist/esm/schema.js.map +1 -1
  21. package/dist/esm/start-compiler-plugin/compilers.d.ts +15 -0
  22. package/dist/esm/start-compiler-plugin/compilers.js +131 -0
  23. package/dist/esm/start-compiler-plugin/compilers.js.map +1 -0
  24. package/dist/esm/start-compiler-plugin/constants.d.ts +1 -0
  25. package/dist/esm/start-compiler-plugin/constants.js +13 -0
  26. package/dist/esm/start-compiler-plugin/constants.js.map +1 -0
  27. package/dist/esm/start-compiler-plugin/envOnly.d.ts +5 -0
  28. package/dist/esm/start-compiler-plugin/envOnly.js +41 -0
  29. package/dist/esm/start-compiler-plugin/envOnly.js.map +1 -0
  30. package/dist/esm/start-compiler-plugin/isomorphicFn.d.ts +4 -0
  31. package/dist/esm/start-compiler-plugin/isomorphicFn.js +49 -0
  32. package/dist/esm/start-compiler-plugin/isomorphicFn.js.map +1 -0
  33. package/dist/esm/start-compiler-plugin/middleware.d.ts +4 -0
  34. package/dist/esm/start-compiler-plugin/middleware.js +51 -0
  35. package/dist/esm/start-compiler-plugin/middleware.js.map +1 -0
  36. package/dist/esm/{start-compiler-plugin.d.ts → start-compiler-plugin/plugin.d.ts} +1 -8
  37. package/dist/esm/start-compiler-plugin/plugin.js +96 -0
  38. package/dist/esm/start-compiler-plugin/plugin.js.map +1 -0
  39. package/dist/esm/start-compiler-plugin/serverFileRoute.d.ts +4 -0
  40. package/dist/esm/start-compiler-plugin/serverFileRoute.js +38 -0
  41. package/dist/esm/start-compiler-plugin/serverFileRoute.js.map +1 -0
  42. package/dist/esm/start-compiler-plugin/utils.d.ts +13 -0
  43. package/dist/esm/start-compiler-plugin/utils.js +30 -0
  44. package/dist/esm/start-compiler-plugin/utils.js.map +1 -0
  45. package/package.json +8 -8
  46. package/src/create-server-fn-plugin/compiler.ts +456 -0
  47. package/src/create-server-fn-plugin/handleCreateServerFn.ts +153 -0
  48. package/src/create-server-fn-plugin/plugin.ts +138 -0
  49. package/src/index.ts +2 -0
  50. package/src/output-directory.ts +13 -6
  51. package/src/plugin.ts +24 -21
  52. package/src/schema.ts +10 -16
  53. package/src/start-compiler-plugin/compilers.ts +195 -0
  54. package/src/start-compiler-plugin/constants.ts +9 -0
  55. package/src/start-compiler-plugin/envOnly.ts +58 -0
  56. package/src/start-compiler-plugin/isomorphicFn.ts +78 -0
  57. package/src/start-compiler-plugin/middleware.ts +79 -0
  58. package/src/start-compiler-plugin/plugin.ts +122 -0
  59. package/src/start-compiler-plugin/serverFileRoute.ts +59 -0
  60. package/src/start-compiler-plugin/utils.ts +41 -0
  61. package/dist/esm/compilers.d.ts +0 -21
  62. package/dist/esm/compilers.js +0 -395
  63. package/dist/esm/compilers.js.map +0 -1
  64. package/dist/esm/start-compiler-plugin.js +0 -78
  65. package/dist/esm/start-compiler-plugin.js.map +0 -1
  66. package/src/compilers.ts +0 -659
  67. package/src/start-compiler-plugin.ts +0 -115
@@ -0,0 +1,456 @@
1
+ /* eslint-disable import/no-commonjs */
2
+ import * as t from '@babel/types'
3
+ import { generateFromAst, parseAst } from '@tanstack/router-utils'
4
+ import babel from '@babel/core'
5
+ import {
6
+ deadCodeElimination,
7
+ findReferencedIdentifiers,
8
+ } from 'babel-dead-code-elimination'
9
+ import { handleCreateServerFn } from './handleCreateServerFn'
10
+
11
+ type Binding =
12
+ | {
13
+ type: 'import'
14
+ source: string
15
+ importedName: string
16
+ resolvedKind?: Kind
17
+ }
18
+ | {
19
+ type: 'var'
20
+ init: t.Expression | null
21
+ resolvedKind?: Kind
22
+ }
23
+
24
+ type ExportEntry =
25
+ | { tag: 'Normal'; name: string }
26
+ | { tag: 'Default'; name: string }
27
+ | { tag: 'Namespace'; name: string; targetId: string } // for `export * as ns from './x'`
28
+
29
+ type Kind = 'None' | 'Root' | 'Builder' | 'ServerFn'
30
+
31
+ interface ModuleInfo {
32
+ id: string
33
+ code: string
34
+ ast: ReturnType<typeof parseAst>
35
+ bindings: Map<string, Binding>
36
+ exports: Map<string, ExportEntry>
37
+ }
38
+
39
+ export class ServerFnCompiler {
40
+ private moduleCache = new Map<string, ModuleInfo>()
41
+ private resolvedLibId!: string
42
+ private initialized = false
43
+ constructor(
44
+ private options: {
45
+ env: 'client' | 'server'
46
+ libName: string
47
+ rootExport: string
48
+ loadModule: (id: string) => Promise<void>
49
+ resolveId: (id: string, importer?: string) => Promise<string | null>
50
+ },
51
+ ) {}
52
+
53
+ private async init(id: string) {
54
+ const libId = await this.options.resolveId(this.options.libName, id)
55
+ if (!libId) {
56
+ throw new Error(`could not resolve "${this.options.libName}"`)
57
+ }
58
+ // insert root binding
59
+ const rootModule = {
60
+ ast: null as any,
61
+ bindings: new Map(),
62
+ exports: new Map(),
63
+ code: '',
64
+ id: libId,
65
+ }
66
+ rootModule.exports.set(this.options.rootExport, {
67
+ tag: 'Normal',
68
+ name: this.options.rootExport,
69
+ })
70
+ rootModule.bindings.set(this.options.rootExport, {
71
+ type: 'var',
72
+ init: t.identifier(this.options.rootExport),
73
+ resolvedKind: 'Root',
74
+ })
75
+ this.moduleCache.set(libId, rootModule)
76
+ this.initialized = true
77
+ this.resolvedLibId = libId
78
+ }
79
+
80
+ public ingestModule({ code, id }: { code: string; id: string }) {
81
+ const ast = parseAst({ code })
82
+
83
+ const bindings = new Map<string, Binding>()
84
+ const exports = new Map<string, ExportEntry>()
85
+
86
+ // we are only interested in top-level bindings, hence we don't traverse the AST
87
+ // instead we only iterate over the program body
88
+ for (const node of ast.program.body) {
89
+ if (t.isImportDeclaration(node)) {
90
+ const source = node.source.value
91
+ for (const s of node.specifiers) {
92
+ if (t.isImportSpecifier(s)) {
93
+ const importedName = t.isIdentifier(s.imported)
94
+ ? s.imported.name
95
+ : s.imported.value
96
+ bindings.set(s.local.name, { type: 'import', source, importedName })
97
+ } else if (t.isImportDefaultSpecifier(s)) {
98
+ bindings.set(s.local.name, {
99
+ type: 'import',
100
+ source,
101
+ importedName: 'default',
102
+ })
103
+ } else if (t.isImportNamespaceSpecifier(s)) {
104
+ bindings.set(s.local.name, {
105
+ type: 'import',
106
+ source,
107
+ importedName: '*',
108
+ })
109
+ }
110
+ }
111
+ } else if (t.isVariableDeclaration(node)) {
112
+ for (const decl of node.declarations) {
113
+ if (t.isIdentifier(decl.id)) {
114
+ bindings.set(decl.id.name, {
115
+ type: 'var',
116
+ init: decl.init ?? null,
117
+ })
118
+ }
119
+ }
120
+ } else if (t.isExportNamedDeclaration(node)) {
121
+ // export const foo = ...
122
+ if (node.declaration) {
123
+ if (t.isVariableDeclaration(node.declaration)) {
124
+ for (const d of node.declaration.declarations) {
125
+ if (t.isIdentifier(d.id)) {
126
+ exports.set(d.id.name, { tag: 'Normal', name: d.id.name })
127
+ bindings.set(d.id.name, { type: 'var', init: d.init ?? null })
128
+ }
129
+ }
130
+ }
131
+ }
132
+ for (const sp of node.specifiers) {
133
+ if (t.isExportNamespaceSpecifier(sp)) {
134
+ exports.set(sp.exported.name, {
135
+ tag: 'Namespace',
136
+ name: sp.exported.name,
137
+ targetId: node.source?.value || '',
138
+ })
139
+ }
140
+ // export { local as exported }
141
+ else if (t.isExportSpecifier(sp)) {
142
+ const local = sp.local.name
143
+ const exported = t.isIdentifier(sp.exported)
144
+ ? sp.exported.name
145
+ : sp.exported.value
146
+ exports.set(exported, { tag: 'Normal', name: local })
147
+ }
148
+ }
149
+ } else if (t.isExportDefaultDeclaration(node)) {
150
+ const d = node.declaration
151
+ if (t.isIdentifier(d)) {
152
+ exports.set('default', { tag: 'Default', name: d.name })
153
+ } else {
154
+ const synth = '__default_export__'
155
+ bindings.set(synth, { type: 'var', init: d as t.Expression })
156
+ exports.set('default', { tag: 'Default', name: synth })
157
+ }
158
+ }
159
+ }
160
+
161
+ const info: ModuleInfo = { code, id, ast, bindings, exports }
162
+ this.moduleCache.set(id, info)
163
+ return info
164
+ }
165
+
166
+ public invalidateModule(id: string) {
167
+ return this.moduleCache.delete(id)
168
+ }
169
+
170
+ public async compile({ code, id }: { code: string; id: string }) {
171
+ if (!this.initialized) {
172
+ await this.init(id)
173
+ }
174
+ const { bindings, ast } = this.ingestModule({ code, id })
175
+ const candidates = this.collectHandlerCandidates(bindings)
176
+ if (candidates.length === 0) {
177
+ // this hook will only be invoked if there is `.handler(` in the code,
178
+ // so not discovering a handler candidate is rather unlikely, but maybe possible?
179
+ return null
180
+ }
181
+
182
+ // let's find out which of the candidates are actually server functions
183
+ const toRewrite: Array<t.CallExpression> = []
184
+ for (const handler of candidates) {
185
+ const kind = await this.resolveExprKind(handler, id)
186
+ if (kind === 'ServerFn') {
187
+ toRewrite.push(handler)
188
+ }
189
+ }
190
+ if (toRewrite.length === 0) {
191
+ return null
192
+ }
193
+ const pathsToRewrite: Array<babel.NodePath<t.CallExpression>> = []
194
+ babel.traverse(ast, {
195
+ CallExpression(path) {
196
+ const found = toRewrite.findIndex((h) => path.node === h)
197
+ if (found !== -1) {
198
+ pathsToRewrite.push(path)
199
+ // delete from toRewrite
200
+ toRewrite.splice(found, 1)
201
+ }
202
+ },
203
+ })
204
+
205
+ if (toRewrite.length > 0) {
206
+ throw new Error(
207
+ `Internal error: could not find all paths to rewrite. please file an issue`,
208
+ )
209
+ }
210
+
211
+ const refIdents = findReferencedIdentifiers(ast)
212
+
213
+ pathsToRewrite.map((p) =>
214
+ handleCreateServerFn(p, { env: this.options.env, code }),
215
+ )
216
+
217
+ deadCodeElimination(ast, refIdents)
218
+
219
+ return generateFromAst(ast, {
220
+ sourceMaps: true,
221
+ sourceFileName: id,
222
+ filename: id,
223
+ })
224
+ }
225
+
226
+ // collects all `.handler(...)` CallExpressions at top-level
227
+ private collectHandlerCandidates(bindings: Map<string, Binding>) {
228
+ const candidates: Array<t.CallExpression> = []
229
+
230
+ for (const binding of bindings.values()) {
231
+ if (binding.type === 'var') {
232
+ const handler = isHandlerCall(binding.init)
233
+ if (handler) {
234
+ candidates.push(handler)
235
+ }
236
+ }
237
+ }
238
+ return candidates
239
+ }
240
+
241
+ private async resolveIdentifierKind(
242
+ ident: string,
243
+ id: string,
244
+ visited = new Set<string>(),
245
+ ): Promise<Kind> {
246
+ const info = await this.getModuleInfo(id)
247
+
248
+ const binding = info.bindings.get(ident)
249
+ if (!binding) {
250
+ return 'None'
251
+ }
252
+ if (binding.resolvedKind) {
253
+ return binding.resolvedKind
254
+ }
255
+
256
+ // TODO improve cycle detection? should we throw here instead of returning 'None'?
257
+ // prevent cycles
258
+ const vKey = `${id}:${ident}`
259
+ if (visited.has(vKey)) {
260
+ return 'None'
261
+ }
262
+ visited.add(vKey)
263
+
264
+ const resolvedKind = await this.resolveBindingKind(binding, id, visited)
265
+ binding.resolvedKind = resolvedKind
266
+ return resolvedKind
267
+ }
268
+
269
+ private async resolveBindingKind(
270
+ binding: Binding,
271
+ fileId: string,
272
+ visited = new Set<string>(),
273
+ ): Promise<Kind> {
274
+ if (binding.resolvedKind) {
275
+ return binding.resolvedKind
276
+ }
277
+ if (binding.type === 'import') {
278
+ const target = await this.options.resolveId(binding.source, fileId)
279
+ if (!target) {
280
+ return 'None'
281
+ }
282
+
283
+ if (binding.importedName === '*') {
284
+ throw new Error(
285
+ `should never get here, namespace imports are handled in resolveCalleeKind`,
286
+ )
287
+ }
288
+
289
+ const importedModule = await this.getModuleInfo(target)
290
+
291
+ const moduleExport = importedModule.exports.get(binding.importedName)
292
+ if (!moduleExport) {
293
+ return 'None'
294
+ }
295
+ const importedBinding = importedModule.bindings.get(moduleExport.name)
296
+ if (!importedBinding) {
297
+ return 'None'
298
+ }
299
+ if (importedBinding.resolvedKind) {
300
+ return importedBinding.resolvedKind
301
+ }
302
+
303
+ const resolvedKind = await this.resolveBindingKind(
304
+ importedBinding,
305
+ importedModule.id,
306
+ visited,
307
+ )
308
+ importedBinding.resolvedKind = resolvedKind
309
+ return resolvedKind
310
+ }
311
+
312
+ const resolvedKind = await this.resolveExprKind(
313
+ binding.init,
314
+ fileId,
315
+ visited,
316
+ )
317
+ binding.resolvedKind = resolvedKind
318
+ return resolvedKind
319
+ }
320
+
321
+ private async resolveExprKind(
322
+ expr: t.Expression | null,
323
+ fileId: string,
324
+ visited = new Set<string>(),
325
+ ): Promise<Kind> {
326
+ if (!expr) {
327
+ return 'None'
328
+ }
329
+
330
+ let result: Kind = 'None'
331
+
332
+ if (t.isCallExpression(expr)) {
333
+ if (!t.isExpression(expr.callee)) {
334
+ return 'None'
335
+ }
336
+ const calleeKind = await this.resolveCalleeKind(
337
+ expr.callee,
338
+ fileId,
339
+ visited,
340
+ )
341
+ if (calleeKind === 'Root' || calleeKind === 'Builder') {
342
+ return 'Builder'
343
+ }
344
+ if (calleeKind === 'ServerFn') {
345
+ return 'ServerFn'
346
+ }
347
+ } else if (t.isMemberExpression(expr) && t.isIdentifier(expr.property)) {
348
+ result = await this.resolveCalleeKind(expr.object, fileId, visited)
349
+ }
350
+
351
+ if (result === 'None' && t.isIdentifier(expr)) {
352
+ result = await this.resolveIdentifierKind(expr.name, fileId, visited)
353
+ }
354
+
355
+ if (result === 'None' && t.isTSAsExpression(expr)) {
356
+ result = await this.resolveExprKind(expr.expression, fileId, visited)
357
+ }
358
+ if (result === 'None' && t.isTSNonNullExpression(expr)) {
359
+ result = await this.resolveExprKind(expr.expression, fileId, visited)
360
+ }
361
+ if (result === 'None' && t.isParenthesizedExpression(expr)) {
362
+ result = await this.resolveExprKind(expr.expression, fileId, visited)
363
+ }
364
+
365
+ return result
366
+ }
367
+
368
+ private async resolveCalleeKind(
369
+ callee: t.Expression,
370
+ fileId: string,
371
+ visited = new Set<string>(),
372
+ ): Promise<Kind> {
373
+ if (t.isIdentifier(callee)) {
374
+ return this.resolveIdentifierKind(callee.name, fileId, visited)
375
+ }
376
+
377
+ if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
378
+ const prop = callee.property.name
379
+
380
+ if (prop === 'handler') {
381
+ const base = await this.resolveExprKind(callee.object, fileId, visited)
382
+ if (base === 'Root' || base === 'Builder') {
383
+ return 'ServerFn'
384
+ }
385
+ return 'None'
386
+ }
387
+ // Check if the object is a namespace import
388
+ if (t.isIdentifier(callee.object)) {
389
+ const info = await this.getModuleInfo(fileId)
390
+ const binding = info.bindings.get(callee.object.name)
391
+ if (
392
+ binding &&
393
+ binding.type === 'import' &&
394
+ binding.importedName === '*'
395
+ ) {
396
+ // resolve the property from the target module
397
+ const targetModuleId = await this.options.resolveId(
398
+ binding.source,
399
+ fileId,
400
+ )
401
+ if (targetModuleId) {
402
+ const targetModule = await this.getModuleInfo(targetModuleId)
403
+ const exportEntry = targetModule.exports.get(callee.property.name)
404
+ if (exportEntry) {
405
+ const exportedBinding = targetModule.bindings.get(
406
+ exportEntry.name,
407
+ )
408
+ if (exportedBinding) {
409
+ return await this.resolveBindingKind(
410
+ exportedBinding,
411
+ targetModule.id,
412
+ visited,
413
+ )
414
+ }
415
+ }
416
+ }
417
+ }
418
+ }
419
+ return this.resolveExprKind(callee.object, fileId, visited)
420
+ }
421
+
422
+ // handle nested expressions
423
+ return this.resolveExprKind(callee, fileId, visited)
424
+ }
425
+
426
+ private async getModuleInfo(id: string) {
427
+ let cached = this.moduleCache.get(id)
428
+ if (cached) {
429
+ return cached
430
+ }
431
+
432
+ await this.options.loadModule(id)
433
+
434
+ cached = this.moduleCache.get(id)
435
+ if (!cached) {
436
+ throw new Error(`could not load module info for ${id}`)
437
+ }
438
+ return cached
439
+ }
440
+ }
441
+
442
+ function isHandlerCall(
443
+ node: t.Node | null | undefined,
444
+ ): undefined | t.CallExpression {
445
+ if (!t.isCallExpression(node)) return undefined
446
+
447
+ const callee = node.callee
448
+ if (
449
+ !t.isMemberExpression(callee) ||
450
+ !t.isIdentifier(callee.property, { name: 'handler' })
451
+ ) {
452
+ return undefined
453
+ }
454
+
455
+ return node
456
+ }
@@ -0,0 +1,153 @@
1
+ import * as t from '@babel/types'
2
+ import {
3
+ codeFrameError,
4
+ getRootCallExpression,
5
+ } from '../start-compiler-plugin/utils'
6
+ import type * as babel from '@babel/core'
7
+
8
+ export function handleCreateServerFn(
9
+ path: babel.NodePath<t.CallExpression>,
10
+ opts: {
11
+ env: 'client' | 'server'
12
+ code: string
13
+ },
14
+ ) {
15
+ // Traverse the member expression and find the call expressions for
16
+ // the validator, handler, and middleware methods. Check to make sure they
17
+ // are children of the createServerFn call expression.
18
+
19
+ const validMethods = ['middleware', 'validator', 'handler'] as const
20
+ type ValidMethods = (typeof validMethods)[number]
21
+ const callExpressionPaths: Record<
22
+ ValidMethods,
23
+ babel.NodePath<t.CallExpression> | null
24
+ > = {
25
+ middleware: null,
26
+ validator: null,
27
+ handler: null,
28
+ }
29
+
30
+ const rootCallExpression = getRootCallExpression(path)
31
+
32
+ // if (debug)
33
+ // console.info(
34
+ // 'Handling createServerFn call expression:',
35
+ // rootCallExpression.toString(),
36
+ // )
37
+
38
+ // Check if the call is assigned to a variable
39
+ if (!rootCallExpression.parentPath.isVariableDeclarator()) {
40
+ throw new Error('createServerFn must be assigned to a variable!')
41
+ }
42
+
43
+ // Get the identifier name of the variable
44
+ const variableDeclarator = rootCallExpression.parentPath.node
45
+ const existingVariableName = (variableDeclarator.id as t.Identifier).name
46
+
47
+ rootCallExpression.traverse({
48
+ MemberExpression(memberExpressionPath) {
49
+ if (t.isIdentifier(memberExpressionPath.node.property)) {
50
+ const name = memberExpressionPath.node.property.name as ValidMethods
51
+
52
+ if (
53
+ validMethods.includes(name) &&
54
+ memberExpressionPath.parentPath.isCallExpression()
55
+ ) {
56
+ callExpressionPaths[name] = memberExpressionPath.parentPath
57
+ }
58
+ }
59
+ },
60
+ })
61
+
62
+ if (callExpressionPaths.validator) {
63
+ const innerInputExpression = callExpressionPaths.validator.node.arguments[0]
64
+
65
+ if (!innerInputExpression) {
66
+ throw new Error(
67
+ 'createServerFn().validator() must be called with a validator!',
68
+ )
69
+ }
70
+
71
+ // If we're on the client, remove the validator call expression
72
+ if (opts.env === 'client') {
73
+ if (t.isMemberExpression(callExpressionPaths.validator.node.callee)) {
74
+ callExpressionPaths.validator.replaceWith(
75
+ callExpressionPaths.validator.node.callee.object,
76
+ )
77
+ }
78
+ }
79
+ }
80
+
81
+ // First, we need to move the handler function to a nested function call
82
+ // that is applied to the arguments passed to the server function.
83
+
84
+ const handlerFnPath = callExpressionPaths.handler?.get(
85
+ 'arguments.0',
86
+ ) as babel.NodePath<any>
87
+
88
+ if (!callExpressionPaths.handler || !handlerFnPath.node) {
89
+ throw codeFrameError(
90
+ opts.code,
91
+ path.node.callee.loc!,
92
+ `createServerFn must be called with a "handler" property!`,
93
+ )
94
+ }
95
+
96
+ const handlerFn = handlerFnPath.node
97
+
98
+ // So, the way we do this is we give the handler function a way
99
+ // to access the serverFn ctx on the server via function scope.
100
+ // The 'use server' extracted function will be called with the
101
+ // payload from the client, then use the scoped serverFn ctx
102
+ // to execute the handler function.
103
+ // This way, we can do things like data and middleware validation
104
+ // in the __execute function without having to AST transform the
105
+ // handler function too much itself.
106
+
107
+ // .handler((optsOut, ctx) => {
108
+ // return ((optsIn) => {
109
+ // 'use server'
110
+ // ctx.__execute(handlerFn, optsIn)
111
+ // })(optsOut)
112
+ // })
113
+
114
+ // If the handler function is an identifier and we're on the client, we need to
115
+ // remove the bound function from the file.
116
+ // If we're on the server, you can leave it, since it will get referenced
117
+ // as a second argument.
118
+
119
+ if (t.isIdentifier(handlerFn)) {
120
+ if (opts.env === 'client') {
121
+ // Find the binding for the handler function
122
+ const binding = handlerFnPath.scope.getBinding(handlerFn.name)
123
+ // Remove it
124
+ if (binding) {
125
+ binding.path.remove()
126
+ }
127
+ }
128
+ // If the env is server, just leave it alone
129
+ }
130
+
131
+ handlerFnPath.replaceWith(
132
+ t.arrowFunctionExpression(
133
+ [t.identifier('opts'), t.identifier('signal')],
134
+ t.blockStatement(
135
+ // Everything in here is server-only, since the client
136
+ // will strip out anything in the 'use server' directive.
137
+ [
138
+ t.returnStatement(
139
+ t.callExpression(
140
+ t.identifier(`${existingVariableName}.__executeServer`),
141
+ [t.identifier('opts'), t.identifier('signal')],
142
+ ),
143
+ ),
144
+ ],
145
+ [t.directive(t.directiveLiteral('use server'))],
146
+ ),
147
+ ),
148
+ )
149
+
150
+ if (opts.env === 'server') {
151
+ callExpressionPaths.handler.node.arguments.push(handlerFn)
152
+ }
153
+ }