@tanstack/start-plugin-core 1.142.10 → 1.142.12

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.
@@ -22,6 +22,13 @@ type ExportEntry = {
22
22
  };
23
23
  type Kind = 'None' | `Root` | `Builder` | LookupKind;
24
24
  export type LookupKind = 'ServerFn' | 'Middleware' | 'IsomorphicFn' | 'ServerOnlyFn' | 'ClientOnlyFn';
25
+ export declare const KindDetectionPatterns: Record<LookupKind, RegExp>;
26
+ export declare const LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>>;
27
+ /**
28
+ * Detects which LookupKinds are present in the code using string matching.
29
+ * This is a fast pre-scan before AST parsing to limit the work done during compilation.
30
+ */
31
+ export declare function detectKindsInCode(code: string, env: 'client' | 'server'): Set<LookupKind>;
25
32
  export type LookupConfig = {
26
33
  libName: string;
27
34
  rootExport: string;
@@ -38,8 +45,6 @@ export declare class ServerFnCompiler {
38
45
  private moduleCache;
39
46
  private initialized;
40
47
  private validLookupKinds;
41
- private hasDirectCallKinds;
42
- private hasRootAsCandidateKinds;
43
48
  private knownRootImports;
44
49
  constructor(options: {
45
50
  env: 'client' | 'server';
@@ -58,12 +63,13 @@ export declare class ServerFnCompiler {
58
63
  ast: import('@tanstack/router-utils').ParseAstResult;
59
64
  };
60
65
  invalidateModule(id: string): boolean;
61
- compile({ code, id, isProviderFile, }: {
66
+ compile({ code, id, isProviderFile, detectedKinds, }: {
62
67
  code: string;
63
68
  id: string;
64
69
  isProviderFile: boolean;
70
+ /** Pre-detected kinds present in this file. If not provided, all valid kinds are checked. */
71
+ detectedKinds?: Set<LookupKind>;
65
72
  }): Promise<import('@tanstack/router-utils').GeneratorResult | null>;
66
- private collectCandidates;
67
73
  private resolveIdentifierKind;
68
74
  /**
69
75
  * Recursively find an export in a module, following `export * from` chains.
@@ -24,6 +24,38 @@ const LookupSetup = {
24
24
  ServerOnlyFn: { type: "directCall" },
25
25
  ClientOnlyFn: { type: "directCall" }
26
26
  };
27
+ const KindDetectionPatterns = {
28
+ ServerFn: /\.handler\s*\(/,
29
+ Middleware: /createMiddleware/,
30
+ IsomorphicFn: /createIsomorphicFn/,
31
+ ServerOnlyFn: /createServerOnlyFn/,
32
+ ClientOnlyFn: /createClientOnlyFn/
33
+ };
34
+ const LookupKindsPerEnv = {
35
+ client: /* @__PURE__ */ new Set([
36
+ "Middleware",
37
+ "ServerFn",
38
+ "IsomorphicFn",
39
+ "ServerOnlyFn",
40
+ "ClientOnlyFn"
41
+ ]),
42
+ server: /* @__PURE__ */ new Set([
43
+ "ServerFn",
44
+ "IsomorphicFn",
45
+ "ServerOnlyFn",
46
+ "ClientOnlyFn"
47
+ ])
48
+ };
49
+ function detectKindsInCode(code, env) {
50
+ const detected = /* @__PURE__ */ new Set();
51
+ const validForEnv = LookupKindsPerEnv[env];
52
+ for (const [kind, pattern] of Object.entries(KindDetectionPatterns)) {
53
+ if (validForEnv.has(kind) && pattern.test(code)) {
54
+ detected.add(kind);
55
+ }
56
+ }
57
+ return detected;
58
+ }
27
59
  const IdentifierToKinds = /* @__PURE__ */ new Map();
28
60
  for (const [kind, setup] of Object.entries(LookupSetup)) {
29
61
  if (setup.type === "methodChain") {
@@ -37,27 +69,53 @@ for (const [kind, setup] of Object.entries(LookupSetup)) {
37
69
  }
38
70
  }
39
71
  }
72
+ const DirectCallFactoryNames = /* @__PURE__ */ new Set([
73
+ "createServerOnlyFn",
74
+ "createClientOnlyFn",
75
+ "createIsomorphicFn"
76
+ ]);
77
+ function needsDirectCallDetection(kinds) {
78
+ for (const kind of kinds) {
79
+ const setup = LookupSetup[kind];
80
+ if (setup.type === "directCall" || setup.allowRootAsCandidate) {
81
+ return true;
82
+ }
83
+ }
84
+ return false;
85
+ }
86
+ function isNestedDirectCallCandidate(node) {
87
+ let calleeName;
88
+ if (t.isIdentifier(node.callee)) {
89
+ calleeName = node.callee.name;
90
+ } else if (t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.property)) {
91
+ calleeName = node.callee.property.name;
92
+ }
93
+ return calleeName !== void 0 && DirectCallFactoryNames.has(calleeName);
94
+ }
95
+ function isTopLevelDirectCallCandidate(path) {
96
+ const node = path.node;
97
+ const isSimpleCall = t.isIdentifier(node.callee) || t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object) && t.isIdentifier(node.callee.property);
98
+ if (!isSimpleCall) {
99
+ return false;
100
+ }
101
+ const parent = path.parent;
102
+ if (!t.isVariableDeclarator(parent) || parent.init !== node) {
103
+ return false;
104
+ }
105
+ const grandParent = path.parentPath.parent;
106
+ if (!t.isVariableDeclaration(grandParent)) {
107
+ return false;
108
+ }
109
+ return t.isProgram(path.parentPath.parentPath?.parent);
110
+ }
40
111
  class ServerFnCompiler {
41
112
  constructor(options) {
42
113
  this.options = options;
43
114
  this.validLookupKinds = options.lookupKinds;
44
- this.hasDirectCallKinds = false;
45
- this.hasRootAsCandidateKinds = false;
46
- for (const kind of options.lookupKinds) {
47
- const setup = LookupSetup[kind];
48
- if (setup.type === "directCall") {
49
- this.hasDirectCallKinds = true;
50
- } else if (setup.allowRootAsCandidate) {
51
- this.hasRootAsCandidateKinds = true;
52
- }
53
- }
54
115
  }
55
116
  moduleCache = /* @__PURE__ */ new Map();
56
117
  initialized = false;
57
118
  validLookupKinds;
58
- // Precomputed flags for candidate detection (avoid recomputing on each collectCandidates call)
59
- hasDirectCallKinds;
60
- hasRootAsCandidateKinds;
61
119
  // Fast lookup for direct imports from known libraries (e.g., '@tanstack/react-start')
62
120
  // Maps: libName → (exportName → Kind)
63
121
  // This allows O(1) resolution for the common case without async resolveId calls
@@ -207,43 +265,59 @@ class ServerFnCompiler {
207
265
  async compile({
208
266
  code,
209
267
  id,
210
- isProviderFile
268
+ isProviderFile,
269
+ detectedKinds
211
270
  }) {
212
271
  if (!this.initialized) {
213
272
  await this.init();
214
273
  }
215
- const { info, ast } = this.ingestModule({ code, id });
216
- const candidates = this.collectCandidates(info.bindings);
217
- if (candidates.length === 0) {
274
+ const fileKinds = detectedKinds ? new Set([...detectedKinds].filter((k) => this.validLookupKinds.has(k))) : this.validLookupKinds;
275
+ if (fileKinds.size === 0) {
276
+ return null;
277
+ }
278
+ const checkDirectCalls = needsDirectCallDetection(fileKinds);
279
+ const { ast } = this.ingestModule({ code, id });
280
+ const candidatePaths = [];
281
+ const chainCallPaths = /* @__PURE__ */ new Map();
282
+ babel.traverse(ast, {
283
+ CallExpression: (path) => {
284
+ const node = path.node;
285
+ const parent = path.parent;
286
+ if (t.isMemberExpression(parent) && t.isCallExpression(path.parentPath.parent)) {
287
+ chainCallPaths.set(node, path);
288
+ return;
289
+ }
290
+ if (isMethodChainCandidate(node, fileKinds)) {
291
+ candidatePaths.push(path);
292
+ return;
293
+ }
294
+ if (checkDirectCalls) {
295
+ if (isTopLevelDirectCallCandidate(path)) {
296
+ candidatePaths.push(path);
297
+ } else if (isNestedDirectCallCandidate(node)) {
298
+ candidatePaths.push(path);
299
+ }
300
+ }
301
+ }
302
+ });
303
+ if (candidatePaths.length === 0) {
218
304
  return null;
219
305
  }
220
306
  const resolvedCandidates = await Promise.all(
221
- candidates.map(async (candidate) => ({
222
- candidate,
223
- kind: await this.resolveExprKind(candidate, id)
307
+ candidatePaths.map(async (path) => ({
308
+ path,
309
+ kind: await this.resolveExprKind(path.node, id)
224
310
  }))
225
311
  );
226
- const toRewriteMap = /* @__PURE__ */ new Map();
227
- for (const { candidate, kind } of resolvedCandidates) {
228
- if (this.validLookupKinds.has(kind)) {
229
- toRewriteMap.set(candidate, kind);
230
- }
231
- }
232
- if (toRewriteMap.size === 0) {
312
+ const validCandidates = resolvedCandidates.filter(
313
+ ({ kind }) => this.validLookupKinds.has(kind)
314
+ );
315
+ if (validCandidates.length === 0) {
233
316
  return null;
234
317
  }
235
318
  const pathsToRewrite = [];
236
- const callExprPaths = /* @__PURE__ */ new Map();
237
- babel.traverse(ast, {
238
- CallExpression(path) {
239
- callExprPaths.set(path.node, path);
240
- }
241
- });
242
- for (const [node, kind] of toRewriteMap) {
243
- const path = callExprPaths.get(node);
244
- if (!path) {
245
- continue;
246
- }
319
+ for (const { path, kind } of validCandidates) {
320
+ const node = path.node;
247
321
  const methodChain = {
248
322
  middleware: null,
249
323
  inputValidator: null,
@@ -252,6 +326,7 @@ class ServerFnCompiler {
252
326
  client: null
253
327
  };
254
328
  let currentNode = node;
329
+ let currentPath = path;
255
330
  while (true) {
256
331
  const callee = currentNode.callee;
257
332
  if (!t.isMemberExpression(callee)) {
@@ -260,7 +335,6 @@ class ServerFnCompiler {
260
335
  if (t.isIdentifier(callee.property)) {
261
336
  const name = callee.property.name;
262
337
  if (name in methodChain) {
263
- const currentPath = callExprPaths.get(currentNode);
264
338
  const args = currentPath.get("arguments");
265
339
  const firstArgPath = Array.isArray(args) && args.length > 0 ? args[0] ?? null : null;
266
340
  methodChain[name] = {
@@ -273,14 +347,14 @@ class ServerFnCompiler {
273
347
  break;
274
348
  }
275
349
  currentNode = callee.object;
350
+ const nextPath = chainCallPaths.get(currentNode);
351
+ if (!nextPath) {
352
+ break;
353
+ }
354
+ currentPath = nextPath;
276
355
  }
277
356
  pathsToRewrite.push({ path, kind, methodChain });
278
357
  }
279
- if (pathsToRewrite.length !== toRewriteMap.size) {
280
- throw new Error(
281
- `Internal error: could not find all paths to rewrite. please file an issue`
282
- );
283
- }
284
358
  const refIdents = findReferencedIdentifiers(ast);
285
359
  for (const { path, kind, methodChain } of pathsToRewrite) {
286
360
  const candidate = { path, methodChain };
@@ -313,28 +387,6 @@ class ServerFnCompiler {
313
387
  filename: id
314
388
  });
315
389
  }
316
- // collects all candidate CallExpressions at top-level
317
- collectCandidates(bindings) {
318
- const candidates = [];
319
- for (const binding of bindings.values()) {
320
- if (binding.type === "var" && t.isCallExpression(binding.init)) {
321
- const methodChainCandidate = isCandidateCallExpression(
322
- binding.init,
323
- this.validLookupKinds
324
- );
325
- if (methodChainCandidate) {
326
- candidates.push(methodChainCandidate);
327
- continue;
328
- }
329
- if (this.hasDirectCallKinds || this.hasRootAsCandidateKinds) {
330
- if (t.isIdentifier(binding.init.callee) || t.isMemberExpression(binding.init.callee) && t.isIdentifier(binding.init.callee.property)) {
331
- candidates.push(binding.init);
332
- }
333
- }
334
- }
335
- }
336
- return candidates;
337
- }
338
390
  async resolveIdentifierKind(ident, id, visited = /* @__PURE__ */ new Set()) {
339
391
  const info = await this.getModuleInfo(id);
340
392
  const binding = info.bindings.get(ident);
@@ -542,23 +594,25 @@ class ServerFnCompiler {
542
594
  return cached;
543
595
  }
544
596
  }
545
- function isCandidateCallExpression(node, lookupKinds) {
546
- if (!t.isCallExpression(node)) return void 0;
597
+ function isMethodChainCandidate(node, lookupKinds) {
547
598
  const callee = node.callee;
548
599
  if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.property)) {
549
- return void 0;
600
+ return false;
550
601
  }
551
602
  const possibleKinds = IdentifierToKinds.get(callee.property.name);
552
603
  if (possibleKinds) {
553
604
  for (const kind of possibleKinds) {
554
605
  if (lookupKinds.has(kind)) {
555
- return node;
606
+ return true;
556
607
  }
557
608
  }
558
609
  }
559
- return void 0;
610
+ return false;
560
611
  }
561
612
  export {
562
- ServerFnCompiler
613
+ KindDetectionPatterns,
614
+ LookupKindsPerEnv,
615
+ ServerFnCompiler,
616
+ detectKindsInCode
563
617
  };
564
618
  //# sourceMappingURL=compiler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.js","sources":["../../../src/create-server-fn-plugin/compiler.ts"],"sourcesContent":["/* eslint-disable import/no-commonjs */\nimport * as t from '@babel/types'\nimport { generateFromAst, parseAst } from '@tanstack/router-utils'\nimport babel from '@babel/core'\nimport {\n deadCodeElimination,\n findReferencedIdentifiers,\n} from 'babel-dead-code-elimination'\nimport { handleCreateServerFn } from './handleCreateServerFn'\nimport { handleCreateMiddleware } from './handleCreateMiddleware'\nimport { handleCreateIsomorphicFn } from './handleCreateIsomorphicFn'\nimport { handleEnvOnlyFn } from './handleEnvOnly'\nimport type { MethodChainPaths, RewriteCandidate } from './types'\n\ntype Binding =\n | {\n type: 'import'\n source: string\n importedName: string\n resolvedKind?: Kind\n }\n | {\n type: 'var'\n init: t.Expression | null\n resolvedKind?: Kind\n }\n\ntype ExportEntry =\n | { tag: 'Normal'; name: string }\n | { tag: 'Default'; name: string }\n | { tag: 'Namespace'; name: string; targetId: string } // for `export * as ns from './x'`\n\ntype Kind = 'None' | `Root` | `Builder` | LookupKind\n\nexport type LookupKind =\n | 'ServerFn'\n | 'Middleware'\n | 'IsomorphicFn'\n | 'ServerOnlyFn'\n | 'ClientOnlyFn'\n\n// Detection strategy for each kind\ntype MethodChainSetup = {\n type: 'methodChain'\n candidateCallIdentifier: Set<string>\n // If true, a call to the root function (e.g., createIsomorphicFn()) is also a candidate\n // even without chained method calls. This is used for IsomorphicFn which can be\n // called without .client() or .server() (resulting in a no-op function).\n allowRootAsCandidate?: boolean\n}\ntype DirectCallSetup = { type: 'directCall' }\n\nconst LookupSetup: Record<LookupKind, MethodChainSetup | DirectCallSetup> = {\n ServerFn: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['handler']),\n },\n Middleware: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['server', 'client', 'createMiddlewares']),\n },\n IsomorphicFn: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['server', 'client']),\n allowRootAsCandidate: true, // createIsomorphicFn() alone is valid (returns no-op)\n },\n ServerOnlyFn: { type: 'directCall' },\n ClientOnlyFn: { type: 'directCall' },\n}\n\n// Pre-computed map: identifier name -> Set<LookupKind> for fast candidate detection (method chain only)\n// Multiple kinds can share the same identifier (e.g., 'server' and 'client' are used by both Middleware and IsomorphicFn)\nconst IdentifierToKinds = new Map<string, Set<LookupKind>>()\nfor (const [kind, setup] of Object.entries(LookupSetup) as Array<\n [LookupKind, MethodChainSetup | DirectCallSetup]\n>) {\n if (setup.type === 'methodChain') {\n for (const id of setup.candidateCallIdentifier) {\n let kinds = IdentifierToKinds.get(id)\n if (!kinds) {\n kinds = new Set()\n IdentifierToKinds.set(id, kinds)\n }\n kinds.add(kind)\n }\n }\n}\n\nexport type LookupConfig = {\n libName: string\n rootExport: string\n kind: LookupKind | 'Root' // 'Root' for builder pattern, LookupKind for direct call\n}\n\ninterface ModuleInfo {\n id: string\n bindings: Map<string, Binding>\n exports: Map<string, ExportEntry>\n // Track `export * from './module'` declarations for re-export resolution\n reExportAllSources: Array<string>\n}\n\nexport class ServerFnCompiler {\n private moduleCache = new Map<string, ModuleInfo>()\n private initialized = false\n private validLookupKinds: Set<LookupKind>\n // Precomputed flags for candidate detection (avoid recomputing on each collectCandidates call)\n private hasDirectCallKinds: boolean\n private hasRootAsCandidateKinds: boolean\n // Fast lookup for direct imports from known libraries (e.g., '@tanstack/react-start')\n // Maps: libName → (exportName → Kind)\n // This allows O(1) resolution for the common case without async resolveId calls\n private knownRootImports = new Map<string, Map<string, Kind>>()\n constructor(\n private options: {\n env: 'client' | 'server'\n directive: string\n lookupConfigurations: Array<LookupConfig>\n lookupKinds: Set<LookupKind>\n loadModule: (id: string) => Promise<void>\n resolveId: (id: string, importer?: string) => Promise<string | null>\n },\n ) {\n this.validLookupKinds = options.lookupKinds\n\n // Precompute flags for candidate detection\n this.hasDirectCallKinds = false\n this.hasRootAsCandidateKinds = false\n for (const kind of options.lookupKinds) {\n const setup = LookupSetup[kind]\n if (setup.type === 'directCall') {\n this.hasDirectCallKinds = true\n } else if (setup.allowRootAsCandidate) {\n this.hasRootAsCandidateKinds = true\n }\n }\n }\n\n private async init() {\n // Register internal stub package exports for recognition.\n // These don't need module resolution - only the knownRootImports fast path.\n this.knownRootImports.set(\n '@tanstack/start-fn-stubs',\n new Map<string, Kind>([\n ['createIsomorphicFn', 'IsomorphicFn'],\n ['createServerOnlyFn', 'ServerOnlyFn'],\n ['createClientOnlyFn', 'ClientOnlyFn'],\n ]),\n )\n\n await Promise.all(\n this.options.lookupConfigurations.map(async (config) => {\n // Populate the fast lookup map for direct imports (by package name)\n // This allows O(1) recognition of imports from known packages.\n let libExports = this.knownRootImports.get(config.libName)\n if (!libExports) {\n libExports = new Map()\n this.knownRootImports.set(config.libName, libExports)\n }\n libExports.set(config.rootExport, config.kind)\n\n const libId = await this.options.resolveId(config.libName)\n if (!libId) {\n throw new Error(`could not resolve \"${config.libName}\"`)\n }\n let rootModule = this.moduleCache.get(libId)\n if (!rootModule) {\n // insert root binding\n rootModule = {\n bindings: new Map(),\n exports: new Map(),\n id: libId,\n reExportAllSources: [],\n }\n this.moduleCache.set(libId, rootModule)\n }\n\n rootModule.exports.set(config.rootExport, {\n tag: 'Normal',\n name: config.rootExport,\n })\n rootModule.exports.set('*', {\n tag: 'Namespace',\n name: config.rootExport,\n targetId: libId,\n })\n rootModule.bindings.set(config.rootExport, {\n type: 'var',\n init: null, // Not needed since resolvedKind is set\n resolvedKind: config.kind satisfies Kind,\n })\n this.moduleCache.set(libId, rootModule)\n }),\n )\n\n this.initialized = true\n }\n\n public ingestModule({ code, id }: { code: string; id: string }) {\n const ast = parseAst({ code })\n\n const bindings = new Map<string, Binding>()\n const exports = new Map<string, ExportEntry>()\n const reExportAllSources: Array<string> = []\n\n // we are only interested in top-level bindings, hence we don't traverse the AST\n // instead we only iterate over the program body\n for (const node of ast.program.body) {\n if (t.isImportDeclaration(node)) {\n const source = node.source.value\n for (const s of node.specifiers) {\n if (t.isImportSpecifier(s)) {\n const importedName = t.isIdentifier(s.imported)\n ? s.imported.name\n : s.imported.value\n bindings.set(s.local.name, { type: 'import', source, importedName })\n } else if (t.isImportDefaultSpecifier(s)) {\n bindings.set(s.local.name, {\n type: 'import',\n source,\n importedName: 'default',\n })\n } else if (t.isImportNamespaceSpecifier(s)) {\n bindings.set(s.local.name, {\n type: 'import',\n source,\n importedName: '*',\n })\n }\n }\n } else if (t.isVariableDeclaration(node)) {\n for (const decl of node.declarations) {\n if (t.isIdentifier(decl.id)) {\n bindings.set(decl.id.name, {\n type: 'var',\n init: decl.init ?? null,\n })\n }\n }\n } else if (t.isExportNamedDeclaration(node)) {\n // export const foo = ...\n if (node.declaration) {\n if (t.isVariableDeclaration(node.declaration)) {\n for (const d of node.declaration.declarations) {\n if (t.isIdentifier(d.id)) {\n exports.set(d.id.name, { tag: 'Normal', name: d.id.name })\n bindings.set(d.id.name, { type: 'var', init: d.init ?? null })\n }\n }\n }\n }\n for (const sp of node.specifiers) {\n if (t.isExportNamespaceSpecifier(sp)) {\n exports.set(sp.exported.name, {\n tag: 'Namespace',\n name: sp.exported.name,\n targetId: node.source?.value || '',\n })\n }\n // export { local as exported }\n else if (t.isExportSpecifier(sp)) {\n const local = sp.local.name\n const exported = t.isIdentifier(sp.exported)\n ? sp.exported.name\n : sp.exported.value\n exports.set(exported, { tag: 'Normal', name: local })\n\n // When re-exporting from another module (export { foo } from './module'),\n // create an import binding so the server function can be resolved\n if (node.source) {\n bindings.set(local, {\n type: 'import',\n source: node.source.value,\n importedName: local,\n })\n }\n }\n }\n } else if (t.isExportDefaultDeclaration(node)) {\n const d = node.declaration\n if (t.isIdentifier(d)) {\n exports.set('default', { tag: 'Default', name: d.name })\n } else {\n const synth = '__default_export__'\n bindings.set(synth, { type: 'var', init: d as t.Expression })\n exports.set('default', { tag: 'Default', name: synth })\n }\n } else if (t.isExportAllDeclaration(node)) {\n // Handle `export * from './module'` syntax\n // Track the source so we can look up exports from it when needed\n reExportAllSources.push(node.source.value)\n }\n }\n\n const info: ModuleInfo = {\n id,\n bindings,\n exports,\n reExportAllSources,\n }\n this.moduleCache.set(id, info)\n return { info, ast }\n }\n\n public invalidateModule(id: string) {\n return this.moduleCache.delete(id)\n }\n\n public async compile({\n code,\n id,\n isProviderFile,\n }: {\n code: string\n id: string\n isProviderFile: boolean\n }) {\n if (!this.initialized) {\n await this.init()\n }\n const { info, ast } = this.ingestModule({ code, id })\n const candidates = this.collectCandidates(info.bindings)\n if (candidates.length === 0) {\n // this hook will only be invoked if there is `.handler(` | `.server(` | `.client(` in the code,\n // so not discovering a handler candidate is rather unlikely, but maybe possible?\n return null\n }\n\n // let's find out which of the candidates are actually server functions\n // Resolve all candidates in parallel for better performance\n const resolvedCandidates = await Promise.all(\n candidates.map(async (candidate) => ({\n candidate,\n kind: await this.resolveExprKind(candidate, id),\n })),\n )\n\n // Map from candidate/root node -> kind\n // Note: For top-level variable declarations, candidate === root (the outermost CallExpression)\n const toRewriteMap = new Map<t.CallExpression, LookupKind>()\n for (const { candidate, kind } of resolvedCandidates) {\n if (this.validLookupKinds.has(kind as LookupKind)) {\n toRewriteMap.set(candidate, kind as LookupKind)\n }\n }\n if (toRewriteMap.size === 0) {\n return null\n }\n\n // Single-pass traversal to find NodePaths and collect method chains\n const pathsToRewrite: Array<{\n path: babel.NodePath<t.CallExpression>\n kind: LookupKind\n methodChain: MethodChainPaths\n }> = []\n\n // First, collect all CallExpression paths in the AST for O(1) lookup\n const callExprPaths = new Map<\n t.CallExpression,\n babel.NodePath<t.CallExpression>\n >()\n\n babel.traverse(ast, {\n CallExpression(path) {\n callExprPaths.set(path.node, path)\n },\n })\n\n // Now process candidates - we can look up any CallExpression path in O(1)\n for (const [node, kind] of toRewriteMap) {\n const path = callExprPaths.get(node)\n if (!path) {\n continue\n }\n\n // Collect method chain paths by walking DOWN from root through the chain\n const methodChain: MethodChainPaths = {\n middleware: null,\n inputValidator: null,\n handler: null,\n server: null,\n client: null,\n }\n\n // Walk down the call chain using nodes, look up paths from map\n let currentNode: t.CallExpression = node\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const callee = currentNode.callee\n if (!t.isMemberExpression(callee)) {\n break\n }\n\n // Record method chain path if it's a known method\n if (t.isIdentifier(callee.property)) {\n const name = callee.property.name as keyof MethodChainPaths\n if (name in methodChain) {\n const currentPath = callExprPaths.get(currentNode)!\n // Get first argument path\n const args = currentPath.get('arguments')\n const firstArgPath =\n Array.isArray(args) && args.length > 0 ? (args[0] ?? null) : null\n methodChain[name] = {\n callPath: currentPath,\n firstArgPath,\n }\n }\n }\n\n // Move to the inner call (the object of the member expression)\n if (!t.isCallExpression(callee.object)) {\n break\n }\n currentNode = callee.object\n }\n\n pathsToRewrite.push({ path, kind, methodChain })\n }\n\n // Verify we found all candidates (pathsToRewrite should have same size as toRewriteMap had)\n if (pathsToRewrite.length !== toRewriteMap.size) {\n throw new Error(\n `Internal error: could not find all paths to rewrite. please file an issue`,\n )\n }\n\n const refIdents = findReferencedIdentifiers(ast)\n\n for (const { path, kind, methodChain } of pathsToRewrite) {\n const candidate: RewriteCandidate = { path, methodChain }\n if (kind === 'ServerFn') {\n handleCreateServerFn(candidate, {\n env: this.options.env,\n code,\n directive: this.options.directive,\n isProviderFile,\n })\n } else if (kind === 'Middleware') {\n handleCreateMiddleware(candidate, {\n env: this.options.env,\n })\n } else if (kind === 'IsomorphicFn') {\n handleCreateIsomorphicFn(candidate, {\n env: this.options.env,\n })\n } else {\n // ServerOnlyFn or ClientOnlyFn\n handleEnvOnlyFn(candidate, {\n env: this.options.env,\n kind,\n })\n }\n }\n\n deadCodeElimination(ast, refIdents)\n\n return generateFromAst(ast, {\n sourceMaps: true,\n sourceFileName: id,\n filename: id,\n })\n }\n\n // collects all candidate CallExpressions at top-level\n private collectCandidates(bindings: Map<string, Binding>) {\n const candidates: Array<t.CallExpression> = []\n\n for (const binding of bindings.values()) {\n if (binding.type === 'var' && t.isCallExpression(binding.init)) {\n // Pattern 1: Method chain pattern (.handler(), .server(), etc.)\n const methodChainCandidate = isCandidateCallExpression(\n binding.init,\n this.validLookupKinds,\n )\n if (methodChainCandidate) {\n candidates.push(methodChainCandidate)\n continue\n }\n\n // Pattern 2: Direct call pattern\n // Handles:\n // - createServerOnlyFn(), createClientOnlyFn() (direct call kinds)\n // - createIsomorphicFn() (root-as-candidate kinds)\n // - TanStackStart.createServerOnlyFn() (namespace calls)\n if (this.hasDirectCallKinds || this.hasRootAsCandidateKinds) {\n if (\n t.isIdentifier(binding.init.callee) ||\n (t.isMemberExpression(binding.init.callee) &&\n t.isIdentifier(binding.init.callee.property))\n ) {\n // Include as candidate - kind resolution will verify it's actually a known export\n candidates.push(binding.init)\n }\n }\n }\n }\n return candidates\n }\n\n private async resolveIdentifierKind(\n ident: string,\n id: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n const info = await this.getModuleInfo(id)\n\n const binding = info.bindings.get(ident)\n if (!binding) {\n return 'None'\n }\n if (binding.resolvedKind) {\n return binding.resolvedKind\n }\n\n // TODO improve cycle detection? should we throw here instead of returning 'None'?\n // prevent cycles\n const vKey = `${id}:${ident}`\n if (visited.has(vKey)) {\n return 'None'\n }\n visited.add(vKey)\n\n const resolvedKind = await this.resolveBindingKind(binding, id, visited)\n binding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n /**\n * Recursively find an export in a module, following `export * from` chains.\n * Returns the module info and binding if found, or undefined if not found.\n */\n private async findExportInModule(\n moduleInfo: ModuleInfo,\n exportName: string,\n visitedModules = new Set<string>(),\n ): Promise<{ moduleInfo: ModuleInfo; binding: Binding } | undefined> {\n // Prevent infinite loops in circular re-exports\n if (visitedModules.has(moduleInfo.id)) {\n return undefined\n }\n visitedModules.add(moduleInfo.id)\n\n // First check direct exports\n const directExport = moduleInfo.exports.get(exportName)\n if (directExport) {\n const binding = moduleInfo.bindings.get(directExport.name)\n if (binding) {\n return { moduleInfo, binding }\n }\n }\n\n // If not found, recursively check re-export-all sources in parallel\n // Valid code won't have duplicate exports across chains, so first match wins\n if (moduleInfo.reExportAllSources.length > 0) {\n const results = await Promise.all(\n moduleInfo.reExportAllSources.map(async (reExportSource) => {\n const reExportTarget = await this.options.resolveId(\n reExportSource,\n moduleInfo.id,\n )\n if (reExportTarget) {\n const reExportModule = await this.getModuleInfo(reExportTarget)\n return this.findExportInModule(\n reExportModule,\n exportName,\n visitedModules,\n )\n }\n return undefined\n }),\n )\n // Return the first valid result\n for (const result of results) {\n if (result) {\n return result\n }\n }\n }\n\n return undefined\n }\n\n private async resolveBindingKind(\n binding: Binding,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (binding.resolvedKind) {\n return binding.resolvedKind\n }\n if (binding.type === 'import') {\n // Fast path: check if this is a direct import from a known library\n // (e.g., import { createServerFn } from '@tanstack/react-start')\n // This avoids async resolveId calls for the common case\n const knownExports = this.knownRootImports.get(binding.source)\n if (knownExports) {\n const kind = knownExports.get(binding.importedName)\n if (kind) {\n binding.resolvedKind = kind\n return kind\n }\n }\n\n // Slow path: resolve through the module graph\n const target = await this.options.resolveId(binding.source, fileId)\n if (!target) {\n return 'None'\n }\n\n const importedModule = await this.getModuleInfo(target)\n\n // Find the export, recursively searching through export * from chains\n const found = await this.findExportInModule(\n importedModule,\n binding.importedName,\n )\n\n if (!found) {\n return 'None'\n }\n\n const { moduleInfo: foundModule, binding: foundBinding } = found\n\n if (foundBinding.resolvedKind) {\n return foundBinding.resolvedKind\n }\n\n const resolvedKind = await this.resolveBindingKind(\n foundBinding,\n foundModule.id,\n visited,\n )\n foundBinding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n const resolvedKind = await this.resolveExprKind(\n binding.init,\n fileId,\n visited,\n )\n binding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n private async resolveExprKind(\n expr: t.Expression | null,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (!expr) {\n return 'None'\n }\n\n // Unwrap common TypeScript/parenthesized wrappers first for efficiency\n while (\n t.isTSAsExpression(expr) ||\n t.isTSNonNullExpression(expr) ||\n t.isParenthesizedExpression(expr)\n ) {\n expr = expr.expression\n }\n\n let result: Kind = 'None'\n\n if (t.isCallExpression(expr)) {\n if (!t.isExpression(expr.callee)) {\n return 'None'\n }\n const calleeKind = await this.resolveCalleeKind(\n expr.callee,\n fileId,\n visited,\n )\n if (calleeKind === 'Root' || calleeKind === 'Builder') {\n return 'Builder'\n }\n // Use direct Set.has() instead of iterating\n if (this.validLookupKinds.has(calleeKind as LookupKind)) {\n return calleeKind\n }\n } else if (t.isMemberExpression(expr) && t.isIdentifier(expr.property)) {\n result = await this.resolveCalleeKind(expr.object, fileId, visited)\n }\n\n if (result === 'None' && t.isIdentifier(expr)) {\n result = await this.resolveIdentifierKind(expr.name, fileId, visited)\n }\n\n return result\n }\n\n private async resolveCalleeKind(\n callee: t.Expression,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (t.isIdentifier(callee)) {\n return this.resolveIdentifierKind(callee.name, fileId, visited)\n }\n\n if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {\n const prop = callee.property.name\n\n // Check if this property matches any method chain pattern\n const possibleKinds = IdentifierToKinds.get(prop)\n if (possibleKinds) {\n // Resolve base expression ONCE and reuse for all pattern checks\n const base = await this.resolveExprKind(callee.object, fileId, visited)\n\n // Check each possible kind that uses this identifier\n for (const kind of possibleKinds) {\n if (!this.validLookupKinds.has(kind)) continue\n\n if (kind === 'ServerFn') {\n if (base === 'Root' || base === 'Builder') {\n return 'ServerFn'\n }\n } else if (kind === 'Middleware') {\n if (\n base === 'Root' ||\n base === 'Builder' ||\n base === 'Middleware'\n ) {\n return 'Middleware'\n }\n } else if (kind === 'IsomorphicFn') {\n if (\n base === 'Root' ||\n base === 'Builder' ||\n base === 'IsomorphicFn'\n ) {\n return 'IsomorphicFn'\n }\n }\n }\n }\n\n // Check if the object is a namespace import\n if (t.isIdentifier(callee.object)) {\n const info = await this.getModuleInfo(fileId)\n const binding = info.bindings.get(callee.object.name)\n if (\n binding &&\n binding.type === 'import' &&\n binding.importedName === '*'\n ) {\n // resolve the property from the target module\n const targetModuleId = await this.options.resolveId(\n binding.source,\n fileId,\n )\n if (targetModuleId) {\n const targetModule = await this.getModuleInfo(targetModuleId)\n const exportEntry = targetModule.exports.get(callee.property.name)\n if (exportEntry) {\n const exportedBinding = targetModule.bindings.get(\n exportEntry.name,\n )\n if (exportedBinding) {\n return await this.resolveBindingKind(\n exportedBinding,\n targetModule.id,\n visited,\n )\n }\n }\n } else {\n return 'None'\n }\n }\n }\n return this.resolveExprKind(callee.object, fileId, visited)\n }\n\n // handle nested expressions\n return this.resolveExprKind(callee, fileId, visited)\n }\n\n private async getModuleInfo(id: string) {\n let cached = this.moduleCache.get(id)\n if (cached) {\n return cached\n }\n\n await this.options.loadModule(id)\n\n cached = this.moduleCache.get(id)\n if (!cached) {\n throw new Error(`could not load module info for ${id}`)\n }\n return cached\n }\n}\n\nfunction isCandidateCallExpression(\n node: t.Node | null | undefined,\n lookupKinds: Set<LookupKind>,\n): t.CallExpression | undefined {\n if (!t.isCallExpression(node)) return undefined\n\n const callee = node.callee\n if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.property)) {\n return undefined\n }\n\n // Use pre-computed map for O(1) lookup\n // IdentifierToKinds maps identifier -> Set<LookupKind> to handle shared identifiers\n const possibleKinds = IdentifierToKinds.get(callee.property.name)\n if (possibleKinds) {\n // Check if any of the possible kinds are in the valid lookup kinds\n for (const kind of possibleKinds) {\n if (lookupKinds.has(kind)) {\n return node\n }\n }\n }\n\n return undefined\n}\n"],"names":["resolvedKind"],"mappings":"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"}
1
+ {"version":3,"file":"compiler.js","sources":["../../../src/create-server-fn-plugin/compiler.ts"],"sourcesContent":["/* eslint-disable import/no-commonjs */\nimport * as t from '@babel/types'\nimport { generateFromAst, parseAst } from '@tanstack/router-utils'\nimport babel from '@babel/core'\nimport {\n deadCodeElimination,\n findReferencedIdentifiers,\n} from 'babel-dead-code-elimination'\nimport { handleCreateServerFn } from './handleCreateServerFn'\nimport { handleCreateMiddleware } from './handleCreateMiddleware'\nimport { handleCreateIsomorphicFn } from './handleCreateIsomorphicFn'\nimport { handleEnvOnlyFn } from './handleEnvOnly'\nimport type { MethodChainPaths, RewriteCandidate } from './types'\n\ntype Binding =\n | {\n type: 'import'\n source: string\n importedName: string\n resolvedKind?: Kind\n }\n | {\n type: 'var'\n init: t.Expression | null\n resolvedKind?: Kind\n }\n\ntype ExportEntry =\n | { tag: 'Normal'; name: string }\n | { tag: 'Default'; name: string }\n | { tag: 'Namespace'; name: string; targetId: string } // for `export * as ns from './x'`\n\ntype Kind = 'None' | `Root` | `Builder` | LookupKind\n\nexport type LookupKind =\n | 'ServerFn'\n | 'Middleware'\n | 'IsomorphicFn'\n | 'ServerOnlyFn'\n | 'ClientOnlyFn'\n\n// Detection strategy for each kind\ntype MethodChainSetup = {\n type: 'methodChain'\n candidateCallIdentifier: Set<string>\n // If true, a call to the root function (e.g., createIsomorphicFn()) is also a candidate\n // even without chained method calls. This is used for IsomorphicFn which can be\n // called without .client() or .server() (resulting in a no-op function).\n allowRootAsCandidate?: boolean\n}\ntype DirectCallSetup = { type: 'directCall' }\n\nconst LookupSetup: Record<LookupKind, MethodChainSetup | DirectCallSetup> = {\n ServerFn: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['handler']),\n },\n Middleware: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['server', 'client', 'createMiddlewares']),\n },\n IsomorphicFn: {\n type: 'methodChain',\n candidateCallIdentifier: new Set(['server', 'client']),\n allowRootAsCandidate: true, // createIsomorphicFn() alone is valid (returns no-op)\n },\n ServerOnlyFn: { type: 'directCall' },\n ClientOnlyFn: { type: 'directCall' },\n}\n\n// Single source of truth for detecting which kinds are present in code\n// These patterns are used for:\n// 1. Pre-scanning code to determine which kinds to look for (before AST parsing)\n// 2. Deriving the plugin's transform code filter\nexport const KindDetectionPatterns: Record<LookupKind, RegExp> = {\n ServerFn: /\\.handler\\s*\\(/,\n Middleware: /createMiddleware/,\n IsomorphicFn: /createIsomorphicFn/,\n ServerOnlyFn: /createServerOnlyFn/,\n ClientOnlyFn: /createClientOnlyFn/,\n}\n\n// Which kinds are valid for each environment\nexport const LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>> = {\n client: new Set([\n 'Middleware',\n 'ServerFn',\n 'IsomorphicFn',\n 'ServerOnlyFn',\n 'ClientOnlyFn',\n ] as const),\n server: new Set([\n 'ServerFn',\n 'IsomorphicFn',\n 'ServerOnlyFn',\n 'ClientOnlyFn',\n ] as const),\n}\n\n/**\n * Detects which LookupKinds are present in the code using string matching.\n * This is a fast pre-scan before AST parsing to limit the work done during compilation.\n */\nexport function detectKindsInCode(\n code: string,\n env: 'client' | 'server',\n): Set<LookupKind> {\n const detected = new Set<LookupKind>()\n const validForEnv = LookupKindsPerEnv[env]\n\n for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array<\n [LookupKind, RegExp]\n >) {\n if (validForEnv.has(kind) && pattern.test(code)) {\n detected.add(kind)\n }\n }\n\n return detected\n}\n\n// Pre-computed map: identifier name -> Set<LookupKind> for fast candidate detection (method chain only)\n// Multiple kinds can share the same identifier (e.g., 'server' and 'client' are used by both Middleware and IsomorphicFn)\nconst IdentifierToKinds = new Map<string, Set<LookupKind>>()\nfor (const [kind, setup] of Object.entries(LookupSetup) as Array<\n [LookupKind, MethodChainSetup | DirectCallSetup]\n>) {\n if (setup.type === 'methodChain') {\n for (const id of setup.candidateCallIdentifier) {\n let kinds = IdentifierToKinds.get(id)\n if (!kinds) {\n kinds = new Set()\n IdentifierToKinds.set(id, kinds)\n }\n kinds.add(kind)\n }\n }\n}\n\n// Known factory function names for direct call and root-as-candidate patterns\n// These are the names that, when called directly, create a new function.\n// Used to filter nested candidates - we only want to include actual factory calls,\n// not invocations of already-created functions (e.g., `myServerFn()` should NOT be a candidate)\nconst DirectCallFactoryNames = new Set([\n 'createServerOnlyFn',\n 'createClientOnlyFn',\n 'createIsomorphicFn',\n])\n\nexport type LookupConfig = {\n libName: string\n rootExport: string\n kind: LookupKind | 'Root' // 'Root' for builder pattern, LookupKind for direct call\n}\n\ninterface ModuleInfo {\n id: string\n bindings: Map<string, Binding>\n exports: Map<string, ExportEntry>\n // Track `export * from './module'` declarations for re-export resolution\n reExportAllSources: Array<string>\n}\n\n/**\n * Computes whether any file kinds need direct-call candidate detection.\n * This includes both directCall types (ServerOnlyFn, ClientOnlyFn) and\n * allowRootAsCandidate types (IsomorphicFn).\n */\nfunction needsDirectCallDetection(kinds: Set<LookupKind>): boolean {\n for (const kind of kinds) {\n const setup = LookupSetup[kind]\n if (setup.type === 'directCall' || setup.allowRootAsCandidate) {\n return true\n }\n }\n return false\n}\n\n/**\n * Checks if a CallExpression is a direct-call candidate for NESTED detection.\n * Returns true if the callee is a known factory function name.\n * This is stricter than top-level detection because we need to filter out\n * invocations of existing server functions (e.g., `myServerFn()`).\n */\nfunction isNestedDirectCallCandidate(node: t.CallExpression): boolean {\n let calleeName: string | undefined\n if (t.isIdentifier(node.callee)) {\n calleeName = node.callee.name\n } else if (\n t.isMemberExpression(node.callee) &&\n t.isIdentifier(node.callee.property)\n ) {\n calleeName = node.callee.property.name\n }\n return calleeName !== undefined && DirectCallFactoryNames.has(calleeName)\n}\n\n/**\n * Checks if a CallExpression path is a top-level direct-call candidate.\n * Top-level means the call is the init of a VariableDeclarator at program level.\n * We accept any simple identifier call or namespace call at top level\n * (e.g., `isomorphicFn()`, `TanStackStart.createServerOnlyFn()`) and let\n * resolution verify it. This handles renamed imports.\n */\nfunction isTopLevelDirectCallCandidate(\n path: babel.NodePath<t.CallExpression>,\n): boolean {\n const node = path.node\n\n // Must be a simple identifier call or namespace call\n const isSimpleCall =\n t.isIdentifier(node.callee) ||\n (t.isMemberExpression(node.callee) &&\n t.isIdentifier(node.callee.object) &&\n t.isIdentifier(node.callee.property))\n\n if (!isSimpleCall) {\n return false\n }\n\n // Must be top-level: VariableDeclarator -> VariableDeclaration -> Program\n const parent = path.parent\n if (!t.isVariableDeclarator(parent) || parent.init !== node) {\n return false\n }\n const grandParent = path.parentPath.parent\n if (!t.isVariableDeclaration(grandParent)) {\n return false\n }\n return t.isProgram(path.parentPath.parentPath?.parent)\n}\n\nexport class ServerFnCompiler {\n private moduleCache = new Map<string, ModuleInfo>()\n private initialized = false\n private validLookupKinds: Set<LookupKind>\n // Fast lookup for direct imports from known libraries (e.g., '@tanstack/react-start')\n // Maps: libName → (exportName → Kind)\n // This allows O(1) resolution for the common case without async resolveId calls\n private knownRootImports = new Map<string, Map<string, Kind>>()\n constructor(\n private options: {\n env: 'client' | 'server'\n directive: string\n lookupConfigurations: Array<LookupConfig>\n lookupKinds: Set<LookupKind>\n loadModule: (id: string) => Promise<void>\n resolveId: (id: string, importer?: string) => Promise<string | null>\n },\n ) {\n this.validLookupKinds = options.lookupKinds\n }\n\n private async init() {\n // Register internal stub package exports for recognition.\n // These don't need module resolution - only the knownRootImports fast path.\n this.knownRootImports.set(\n '@tanstack/start-fn-stubs',\n new Map<string, Kind>([\n ['createIsomorphicFn', 'IsomorphicFn'],\n ['createServerOnlyFn', 'ServerOnlyFn'],\n ['createClientOnlyFn', 'ClientOnlyFn'],\n ]),\n )\n\n await Promise.all(\n this.options.lookupConfigurations.map(async (config) => {\n // Populate the fast lookup map for direct imports (by package name)\n // This allows O(1) recognition of imports from known packages.\n let libExports = this.knownRootImports.get(config.libName)\n if (!libExports) {\n libExports = new Map()\n this.knownRootImports.set(config.libName, libExports)\n }\n libExports.set(config.rootExport, config.kind)\n\n const libId = await this.options.resolveId(config.libName)\n if (!libId) {\n throw new Error(`could not resolve \"${config.libName}\"`)\n }\n let rootModule = this.moduleCache.get(libId)\n if (!rootModule) {\n // insert root binding\n rootModule = {\n bindings: new Map(),\n exports: new Map(),\n id: libId,\n reExportAllSources: [],\n }\n this.moduleCache.set(libId, rootModule)\n }\n\n rootModule.exports.set(config.rootExport, {\n tag: 'Normal',\n name: config.rootExport,\n })\n rootModule.exports.set('*', {\n tag: 'Namespace',\n name: config.rootExport,\n targetId: libId,\n })\n rootModule.bindings.set(config.rootExport, {\n type: 'var',\n init: null, // Not needed since resolvedKind is set\n resolvedKind: config.kind satisfies Kind,\n })\n this.moduleCache.set(libId, rootModule)\n }),\n )\n\n this.initialized = true\n }\n\n public ingestModule({ code, id }: { code: string; id: string }) {\n const ast = parseAst({ code })\n\n const bindings = new Map<string, Binding>()\n const exports = new Map<string, ExportEntry>()\n const reExportAllSources: Array<string> = []\n\n // we are only interested in top-level bindings, hence we don't traverse the AST\n // instead we only iterate over the program body\n for (const node of ast.program.body) {\n if (t.isImportDeclaration(node)) {\n const source = node.source.value\n for (const s of node.specifiers) {\n if (t.isImportSpecifier(s)) {\n const importedName = t.isIdentifier(s.imported)\n ? s.imported.name\n : s.imported.value\n bindings.set(s.local.name, { type: 'import', source, importedName })\n } else if (t.isImportDefaultSpecifier(s)) {\n bindings.set(s.local.name, {\n type: 'import',\n source,\n importedName: 'default',\n })\n } else if (t.isImportNamespaceSpecifier(s)) {\n bindings.set(s.local.name, {\n type: 'import',\n source,\n importedName: '*',\n })\n }\n }\n } else if (t.isVariableDeclaration(node)) {\n for (const decl of node.declarations) {\n if (t.isIdentifier(decl.id)) {\n bindings.set(decl.id.name, {\n type: 'var',\n init: decl.init ?? null,\n })\n }\n }\n } else if (t.isExportNamedDeclaration(node)) {\n // export const foo = ...\n if (node.declaration) {\n if (t.isVariableDeclaration(node.declaration)) {\n for (const d of node.declaration.declarations) {\n if (t.isIdentifier(d.id)) {\n exports.set(d.id.name, { tag: 'Normal', name: d.id.name })\n bindings.set(d.id.name, { type: 'var', init: d.init ?? null })\n }\n }\n }\n }\n for (const sp of node.specifiers) {\n if (t.isExportNamespaceSpecifier(sp)) {\n exports.set(sp.exported.name, {\n tag: 'Namespace',\n name: sp.exported.name,\n targetId: node.source?.value || '',\n })\n }\n // export { local as exported }\n else if (t.isExportSpecifier(sp)) {\n const local = sp.local.name\n const exported = t.isIdentifier(sp.exported)\n ? sp.exported.name\n : sp.exported.value\n exports.set(exported, { tag: 'Normal', name: local })\n\n // When re-exporting from another module (export { foo } from './module'),\n // create an import binding so the server function can be resolved\n if (node.source) {\n bindings.set(local, {\n type: 'import',\n source: node.source.value,\n importedName: local,\n })\n }\n }\n }\n } else if (t.isExportDefaultDeclaration(node)) {\n const d = node.declaration\n if (t.isIdentifier(d)) {\n exports.set('default', { tag: 'Default', name: d.name })\n } else {\n const synth = '__default_export__'\n bindings.set(synth, { type: 'var', init: d as t.Expression })\n exports.set('default', { tag: 'Default', name: synth })\n }\n } else if (t.isExportAllDeclaration(node)) {\n // Handle `export * from './module'` syntax\n // Track the source so we can look up exports from it when needed\n reExportAllSources.push(node.source.value)\n }\n }\n\n const info: ModuleInfo = {\n id,\n bindings,\n exports,\n reExportAllSources,\n }\n this.moduleCache.set(id, info)\n return { info, ast }\n }\n\n public invalidateModule(id: string) {\n return this.moduleCache.delete(id)\n }\n\n public async compile({\n code,\n id,\n isProviderFile,\n detectedKinds,\n }: {\n code: string\n id: string\n isProviderFile: boolean\n /** Pre-detected kinds present in this file. If not provided, all valid kinds are checked. */\n detectedKinds?: Set<LookupKind>\n }) {\n if (!this.initialized) {\n await this.init()\n }\n\n // Use detected kinds if provided, otherwise fall back to all valid kinds for this env\n const fileKinds = detectedKinds\n ? new Set([...detectedKinds].filter((k) => this.validLookupKinds.has(k)))\n : this.validLookupKinds\n\n // Early exit if no kinds to process\n if (fileKinds.size === 0) {\n return null\n }\n\n const checkDirectCalls = needsDirectCallDetection(fileKinds)\n\n const { ast } = this.ingestModule({ code, id })\n\n // Single-pass traversal to:\n // 1. Collect candidate paths (only candidates, not all CallExpressions)\n // 2. Build a map for looking up paths of nested calls in method chains\n const candidatePaths: Array<babel.NodePath<t.CallExpression>> = []\n // Map for nested chain lookup - only populated for CallExpressions that are\n // part of a method chain (callee.object is a CallExpression)\n const chainCallPaths = new Map<\n t.CallExpression,\n babel.NodePath<t.CallExpression>\n >()\n\n babel.traverse(ast, {\n CallExpression: (path) => {\n const node = path.node\n const parent = path.parent\n\n // Check if this call is part of a larger chain (inner call)\n // If so, store it for method chain lookup but don't treat as candidate\n if (\n t.isMemberExpression(parent) &&\n t.isCallExpression(path.parentPath.parent)\n ) {\n // This is an inner call in a chain - store for later lookup\n chainCallPaths.set(node, path)\n return\n }\n\n // Pattern 1: Method chain pattern (.handler(), .server(), .client(), etc.)\n if (isMethodChainCandidate(node, fileKinds)) {\n candidatePaths.push(path)\n return\n }\n\n // Pattern 2: Direct call pattern\n if (checkDirectCalls) {\n if (isTopLevelDirectCallCandidate(path)) {\n candidatePaths.push(path)\n } else if (isNestedDirectCallCandidate(node)) {\n candidatePaths.push(path)\n }\n }\n },\n })\n\n if (candidatePaths.length === 0) {\n return null\n }\n\n // Resolve all candidates in parallel to determine their kinds\n const resolvedCandidates = await Promise.all(\n candidatePaths.map(async (path) => ({\n path,\n kind: await this.resolveExprKind(path.node, id),\n })),\n )\n\n // Filter to valid candidates\n const validCandidates = resolvedCandidates.filter(({ kind }) =>\n this.validLookupKinds.has(kind as LookupKind),\n ) as Array<{ path: babel.NodePath<t.CallExpression>; kind: LookupKind }>\n\n if (validCandidates.length === 0) {\n return null\n }\n\n // Process valid candidates to collect method chains\n const pathsToRewrite: Array<{\n path: babel.NodePath<t.CallExpression>\n kind: LookupKind\n methodChain: MethodChainPaths\n }> = []\n\n for (const { path, kind } of validCandidates) {\n const node = path.node\n\n // Collect method chain paths by walking DOWN from root through the chain\n const methodChain: MethodChainPaths = {\n middleware: null,\n inputValidator: null,\n handler: null,\n server: null,\n client: null,\n }\n\n // Walk down the call chain using nodes, look up paths from map\n let currentNode: t.CallExpression = node\n let currentPath: babel.NodePath<t.CallExpression> = path\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const callee = currentNode.callee\n if (!t.isMemberExpression(callee)) {\n break\n }\n\n // Record method chain path if it's a known method\n if (t.isIdentifier(callee.property)) {\n const name = callee.property.name as keyof MethodChainPaths\n if (name in methodChain) {\n // Get first argument path\n const args = currentPath.get('arguments')\n const firstArgPath =\n Array.isArray(args) && args.length > 0 ? (args[0] ?? null) : null\n methodChain[name] = {\n callPath: currentPath,\n firstArgPath,\n }\n }\n }\n\n // Move to the inner call (the object of the member expression)\n if (!t.isCallExpression(callee.object)) {\n break\n }\n currentNode = callee.object\n // Look up path from chain map, or use candidate path if not found\n const nextPath = chainCallPaths.get(currentNode)\n if (!nextPath) {\n break\n }\n currentPath = nextPath\n }\n\n pathsToRewrite.push({ path, kind, methodChain })\n }\n\n const refIdents = findReferencedIdentifiers(ast)\n\n for (const { path, kind, methodChain } of pathsToRewrite) {\n const candidate: RewriteCandidate = { path, methodChain }\n if (kind === 'ServerFn') {\n handleCreateServerFn(candidate, {\n env: this.options.env,\n code,\n directive: this.options.directive,\n isProviderFile,\n })\n } else if (kind === 'Middleware') {\n handleCreateMiddleware(candidate, {\n env: this.options.env,\n })\n } else if (kind === 'IsomorphicFn') {\n handleCreateIsomorphicFn(candidate, {\n env: this.options.env,\n })\n } else {\n // ServerOnlyFn or ClientOnlyFn\n handleEnvOnlyFn(candidate, {\n env: this.options.env,\n kind,\n })\n }\n }\n\n deadCodeElimination(ast, refIdents)\n\n return generateFromAst(ast, {\n sourceMaps: true,\n sourceFileName: id,\n filename: id,\n })\n }\n\n private async resolveIdentifierKind(\n ident: string,\n id: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n const info = await this.getModuleInfo(id)\n\n const binding = info.bindings.get(ident)\n if (!binding) {\n return 'None'\n }\n if (binding.resolvedKind) {\n return binding.resolvedKind\n }\n\n // TODO improve cycle detection? should we throw here instead of returning 'None'?\n // prevent cycles\n const vKey = `${id}:${ident}`\n if (visited.has(vKey)) {\n return 'None'\n }\n visited.add(vKey)\n\n const resolvedKind = await this.resolveBindingKind(binding, id, visited)\n binding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n /**\n * Recursively find an export in a module, following `export * from` chains.\n * Returns the module info and binding if found, or undefined if not found.\n */\n private async findExportInModule(\n moduleInfo: ModuleInfo,\n exportName: string,\n visitedModules = new Set<string>(),\n ): Promise<{ moduleInfo: ModuleInfo; binding: Binding } | undefined> {\n // Prevent infinite loops in circular re-exports\n if (visitedModules.has(moduleInfo.id)) {\n return undefined\n }\n visitedModules.add(moduleInfo.id)\n\n // First check direct exports\n const directExport = moduleInfo.exports.get(exportName)\n if (directExport) {\n const binding = moduleInfo.bindings.get(directExport.name)\n if (binding) {\n return { moduleInfo, binding }\n }\n }\n\n // If not found, recursively check re-export-all sources in parallel\n // Valid code won't have duplicate exports across chains, so first match wins\n if (moduleInfo.reExportAllSources.length > 0) {\n const results = await Promise.all(\n moduleInfo.reExportAllSources.map(async (reExportSource) => {\n const reExportTarget = await this.options.resolveId(\n reExportSource,\n moduleInfo.id,\n )\n if (reExportTarget) {\n const reExportModule = await this.getModuleInfo(reExportTarget)\n return this.findExportInModule(\n reExportModule,\n exportName,\n visitedModules,\n )\n }\n return undefined\n }),\n )\n // Return the first valid result\n for (const result of results) {\n if (result) {\n return result\n }\n }\n }\n\n return undefined\n }\n\n private async resolveBindingKind(\n binding: Binding,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (binding.resolvedKind) {\n return binding.resolvedKind\n }\n if (binding.type === 'import') {\n // Fast path: check if this is a direct import from a known library\n // (e.g., import { createServerFn } from '@tanstack/react-start')\n // This avoids async resolveId calls for the common case\n const knownExports = this.knownRootImports.get(binding.source)\n if (knownExports) {\n const kind = knownExports.get(binding.importedName)\n if (kind) {\n binding.resolvedKind = kind\n return kind\n }\n }\n\n // Slow path: resolve through the module graph\n const target = await this.options.resolveId(binding.source, fileId)\n if (!target) {\n return 'None'\n }\n\n const importedModule = await this.getModuleInfo(target)\n\n // Find the export, recursively searching through export * from chains\n const found = await this.findExportInModule(\n importedModule,\n binding.importedName,\n )\n\n if (!found) {\n return 'None'\n }\n\n const { moduleInfo: foundModule, binding: foundBinding } = found\n\n if (foundBinding.resolvedKind) {\n return foundBinding.resolvedKind\n }\n\n const resolvedKind = await this.resolveBindingKind(\n foundBinding,\n foundModule.id,\n visited,\n )\n foundBinding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n const resolvedKind = await this.resolveExprKind(\n binding.init,\n fileId,\n visited,\n )\n binding.resolvedKind = resolvedKind\n return resolvedKind\n }\n\n private async resolveExprKind(\n expr: t.Expression | null,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (!expr) {\n return 'None'\n }\n\n // Unwrap common TypeScript/parenthesized wrappers first for efficiency\n while (\n t.isTSAsExpression(expr) ||\n t.isTSNonNullExpression(expr) ||\n t.isParenthesizedExpression(expr)\n ) {\n expr = expr.expression\n }\n\n let result: Kind = 'None'\n\n if (t.isCallExpression(expr)) {\n if (!t.isExpression(expr.callee)) {\n return 'None'\n }\n const calleeKind = await this.resolveCalleeKind(\n expr.callee,\n fileId,\n visited,\n )\n if (calleeKind === 'Root' || calleeKind === 'Builder') {\n return 'Builder'\n }\n // Use direct Set.has() instead of iterating\n if (this.validLookupKinds.has(calleeKind as LookupKind)) {\n return calleeKind\n }\n } else if (t.isMemberExpression(expr) && t.isIdentifier(expr.property)) {\n result = await this.resolveCalleeKind(expr.object, fileId, visited)\n }\n\n if (result === 'None' && t.isIdentifier(expr)) {\n result = await this.resolveIdentifierKind(expr.name, fileId, visited)\n }\n\n return result\n }\n\n private async resolveCalleeKind(\n callee: t.Expression,\n fileId: string,\n visited = new Set<string>(),\n ): Promise<Kind> {\n if (t.isIdentifier(callee)) {\n return this.resolveIdentifierKind(callee.name, fileId, visited)\n }\n\n if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {\n const prop = callee.property.name\n\n // Check if this property matches any method chain pattern\n const possibleKinds = IdentifierToKinds.get(prop)\n if (possibleKinds) {\n // Resolve base expression ONCE and reuse for all pattern checks\n const base = await this.resolveExprKind(callee.object, fileId, visited)\n\n // Check each possible kind that uses this identifier\n for (const kind of possibleKinds) {\n if (!this.validLookupKinds.has(kind)) continue\n\n if (kind === 'ServerFn') {\n if (base === 'Root' || base === 'Builder') {\n return 'ServerFn'\n }\n } else if (kind === 'Middleware') {\n if (\n base === 'Root' ||\n base === 'Builder' ||\n base === 'Middleware'\n ) {\n return 'Middleware'\n }\n } else if (kind === 'IsomorphicFn') {\n if (\n base === 'Root' ||\n base === 'Builder' ||\n base === 'IsomorphicFn'\n ) {\n return 'IsomorphicFn'\n }\n }\n }\n }\n\n // Check if the object is a namespace import\n if (t.isIdentifier(callee.object)) {\n const info = await this.getModuleInfo(fileId)\n const binding = info.bindings.get(callee.object.name)\n if (\n binding &&\n binding.type === 'import' &&\n binding.importedName === '*'\n ) {\n // resolve the property from the target module\n const targetModuleId = await this.options.resolveId(\n binding.source,\n fileId,\n )\n if (targetModuleId) {\n const targetModule = await this.getModuleInfo(targetModuleId)\n const exportEntry = targetModule.exports.get(callee.property.name)\n if (exportEntry) {\n const exportedBinding = targetModule.bindings.get(\n exportEntry.name,\n )\n if (exportedBinding) {\n return await this.resolveBindingKind(\n exportedBinding,\n targetModule.id,\n visited,\n )\n }\n }\n } else {\n return 'None'\n }\n }\n }\n return this.resolveExprKind(callee.object, fileId, visited)\n }\n\n // handle nested expressions\n return this.resolveExprKind(callee, fileId, visited)\n }\n\n private async getModuleInfo(id: string) {\n let cached = this.moduleCache.get(id)\n if (cached) {\n return cached\n }\n\n await this.options.loadModule(id)\n\n cached = this.moduleCache.get(id)\n if (!cached) {\n throw new Error(`could not load module info for ${id}`)\n }\n return cached\n }\n}\n\n/**\n * Checks if a CallExpression has a method chain pattern that matches any of the lookup kinds.\n * E.g., `.handler()`, `.server()`, `.client()`, `.createMiddlewares()`\n */\nfunction isMethodChainCandidate(\n node: t.CallExpression,\n lookupKinds: Set<LookupKind>,\n): boolean {\n const callee = node.callee\n if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.property)) {\n return false\n }\n\n // Use pre-computed map for O(1) lookup\n // IdentifierToKinds maps identifier -> Set<LookupKind> to handle shared identifiers\n const possibleKinds = IdentifierToKinds.get(callee.property.name)\n if (possibleKinds) {\n // Check if any of the possible kinds are in the valid lookup kinds\n for (const kind of possibleKinds) {\n if (lookupKinds.has(kind)) {\n return true\n }\n }\n }\n\n return false\n}\n"],"names":["resolvedKind"],"mappings":"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"}
@@ -1,24 +1,22 @@
1
1
  import { TRANSFORM_ID_REGEX } from "../constants.js";
2
- import { ServerFnCompiler } from "./compiler.js";
2
+ import { ServerFnCompiler, LookupKindsPerEnv, detectKindsInCode, KindDetectionPatterns } from "./compiler.js";
3
3
  function cleanId(id) {
4
+ if (id.startsWith("\0")) {
5
+ id = id.slice(1);
6
+ }
4
7
  const queryIndex = id.indexOf("?");
5
8
  return queryIndex === -1 ? id : id.substring(0, queryIndex);
6
9
  }
7
- const LookupKindsPerEnv = {
8
- client: /* @__PURE__ */ new Set([
9
- "Middleware",
10
- "ServerFn",
11
- "IsomorphicFn",
12
- "ServerOnlyFn",
13
- "ClientOnlyFn"
14
- ]),
15
- server: /* @__PURE__ */ new Set([
16
- "ServerFn",
17
- "IsomorphicFn",
18
- "ServerOnlyFn",
19
- "ClientOnlyFn"
20
- ])
21
- };
10
+ function getTransformCodeFilterForEnv(env) {
11
+ const validKinds = LookupKindsPerEnv[env];
12
+ const patterns = [];
13
+ for (const [kind, pattern] of Object.entries(KindDetectionPatterns)) {
14
+ if (validKinds.has(kind)) {
15
+ patterns.push(pattern);
16
+ }
17
+ }
18
+ return patterns;
19
+ }
22
20
  const getLookupConfigurationsForEnv = (env, framework) => {
23
21
  const commonConfigs = [
24
22
  {
@@ -64,17 +62,11 @@ const SERVER_FN_LOOKUP = "server-fn-module-lookup";
64
62
  function buildDirectiveSplitParam(directive) {
65
63
  return `tsr-directive-${directive.replace(/[^a-zA-Z0-9]/g, "-")}`;
66
64
  }
67
- const commonTransformCodeFilter = [
68
- /\.\s*handler\(/,
69
- /createIsomorphicFn/,
70
- /createServerOnlyFn/,
71
- /createClientOnlyFn/
72
- ];
73
65
  function createServerFnPlugin(opts) {
74
66
  const compilers = {};
75
67
  const directiveSplitParam = buildDirectiveSplitParam(opts.directive);
76
68
  function perEnvServerFnPlugin(environment) {
77
- const transformCodeFilter = environment.type === "client" ? [...commonTransformCodeFilter, /createMiddleware\s*\(/] : commonTransformCodeFilter;
69
+ const transformCodeFilter = getTransformCodeFilterForEnv(environment.type);
78
70
  return {
79
71
  name: `tanstack-start-core::server-fn:${environment.name}`,
80
72
  enforce: "pre",
@@ -132,8 +124,14 @@ function createServerFnPlugin(opts) {
132
124
  compilers[this.environment.name] = compiler;
133
125
  }
134
126
  const isProviderFile = id.includes(directiveSplitParam);
127
+ const detectedKinds = detectKindsInCode(code, environment.type);
135
128
  id = cleanId(id);
136
- const result = await compiler.compile({ id, code, isProviderFile });
129
+ const result = await compiler.compile({
130
+ id,
131
+ code,
132
+ isProviderFile,
133
+ detectedKinds
134
+ });
137
135
  return result;
138
136
  }
139
137
  },
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["../../../src/create-server-fn-plugin/plugin.ts"],"sourcesContent":["import { TRANSFORM_ID_REGEX } from '../constants'\nimport { ServerFnCompiler } from './compiler'\nimport type { CompileStartFrameworkOptions } from '../types'\nimport type { LookupConfig, LookupKind } from './compiler'\nimport type { PluginOption } from 'vite'\n\nfunction cleanId(id: string): string {\n const queryIndex = id.indexOf('?')\n return queryIndex === -1 ? id : id.substring(0, queryIndex)\n}\n\nconst LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>> = {\n client: new Set([\n 'Middleware',\n 'ServerFn',\n 'IsomorphicFn',\n 'ServerOnlyFn',\n 'ClientOnlyFn',\n ] as const),\n server: new Set([\n 'ServerFn',\n 'IsomorphicFn',\n 'ServerOnlyFn',\n 'ClientOnlyFn',\n ] as const),\n}\n\nconst getLookupConfigurationsForEnv = (\n env: 'client' | 'server',\n framework: CompileStartFrameworkOptions,\n): Array<LookupConfig> => {\n // Common configs for all environments\n const commonConfigs: Array<LookupConfig> = [\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createServerFn',\n kind: 'Root',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createIsomorphicFn',\n kind: 'IsomorphicFn',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createServerOnlyFn',\n kind: 'ServerOnlyFn',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createClientOnlyFn',\n kind: 'ClientOnlyFn',\n },\n ]\n\n if (env === 'client') {\n return [\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createMiddleware',\n kind: 'Root',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createStart',\n kind: 'Root',\n },\n ...commonConfigs,\n ]\n } else {\n return commonConfigs\n }\n}\nconst SERVER_FN_LOOKUP = 'server-fn-module-lookup'\n\nfunction buildDirectiveSplitParam(directive: string) {\n return `tsr-directive-${directive.replace(/[^a-zA-Z0-9]/g, '-')}`\n}\n\nconst commonTransformCodeFilter = [\n /\\.\\s*handler\\(/,\n /createIsomorphicFn/,\n /createServerOnlyFn/,\n /createClientOnlyFn/,\n]\nexport function createServerFnPlugin(opts: {\n framework: CompileStartFrameworkOptions\n directive: string\n environments: Array<{ name: string; type: 'client' | 'server' }>\n}): PluginOption {\n const compilers: Record<string /* envName */, ServerFnCompiler> = {}\n const directiveSplitParam = buildDirectiveSplitParam(opts.directive)\n\n function perEnvServerFnPlugin(environment: {\n name: string\n type: 'client' | 'server'\n }): PluginOption {\n // Code filter patterns for transform functions:\n // - `.handler(` for createServerFn\n // - `createMiddleware(` for middleware (client only)\n // - `createIsomorphicFn` for isomorphic functions\n // - `createServerOnlyFn` for server-only functions\n // - `createClientOnlyFn` for client-only functions\n const transformCodeFilter =\n environment.type === 'client'\n ? [...commonTransformCodeFilter, /createMiddleware\\s*\\(/]\n : commonTransformCodeFilter\n\n return {\n name: `tanstack-start-core::server-fn:${environment.name}`,\n enforce: 'pre',\n applyToEnvironment(env) {\n return env.name === environment.name\n },\n transform: {\n filter: {\n id: {\n exclude: new RegExp(`${SERVER_FN_LOOKUP}$`),\n include: TRANSFORM_ID_REGEX,\n },\n code: {\n include: transformCodeFilter,\n },\n },\n async handler(code, id) {\n let compiler = compilers[this.environment.name]\n if (!compiler) {\n compiler = new ServerFnCompiler({\n env: environment.type,\n directive: opts.directive,\n lookupKinds: LookupKindsPerEnv[environment.type],\n lookupConfigurations: getLookupConfigurationsForEnv(\n environment.type,\n opts.framework,\n ),\n loadModule: async (id: string) => {\n if (this.environment.mode === 'build') {\n const loaded = await this.load({ id })\n if (!loaded.code) {\n throw new Error(`could not load module ${id}`)\n }\n compiler!.ingestModule({ code: loaded.code, id })\n } else if (this.environment.mode === 'dev') {\n /**\n * in dev, vite does not return code from `ctx.load()`\n * so instead, we need to take a different approach\n * we must force vite to load the module and run it through the vite plugin pipeline\n * we can do this by using the `fetchModule` method\n * the `captureServerFnModuleLookupPlugin` captures the module code via its transform hook and invokes analyzeModuleAST\n */\n await this.environment.fetchModule(\n id + '?' + SERVER_FN_LOOKUP,\n )\n } else {\n throw new Error(\n `could not load module ${id}: unknown environment mode ${this.environment.mode}`,\n )\n }\n },\n resolveId: async (source: string, importer?: string) => {\n const r = await this.resolve(source, importer)\n if (r) {\n if (!r.external) {\n return cleanId(r.id)\n }\n }\n return null\n },\n })\n compilers[this.environment.name] = compiler\n }\n\n const isProviderFile = id.includes(directiveSplitParam)\n\n id = cleanId(id)\n const result = await compiler.compile({ id, code, isProviderFile })\n return result\n },\n },\n\n hotUpdate(ctx) {\n const compiler = compilers[this.environment.name]\n\n ctx.modules.forEach((m) => {\n if (m.id) {\n const deleted = compiler?.invalidateModule(m.id)\n if (deleted) {\n m.importers.forEach((importer) => {\n if (importer.id) {\n compiler?.invalidateModule(importer.id)\n }\n })\n }\n }\n })\n },\n }\n }\n\n return [\n ...opts.environments.map(perEnvServerFnPlugin),\n {\n name: 'tanstack-start-core:capture-server-fn-module-lookup',\n // we only need this plugin in dev mode\n apply: 'serve',\n applyToEnvironment(env) {\n return !!opts.environments.find((e) => e.name === env.name)\n },\n transform: {\n filter: {\n id: new RegExp(`${SERVER_FN_LOOKUP}$`),\n },\n handler(code, id) {\n const compiler = compilers[this.environment.name]\n compiler?.ingestModule({ code, id: cleanId(id) })\n },\n },\n },\n ]\n}\n"],"names":["id"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sources":["../../../src/create-server-fn-plugin/plugin.ts"],"sourcesContent":["import { TRANSFORM_ID_REGEX } from '../constants'\nimport {\n KindDetectionPatterns,\n LookupKindsPerEnv,\n ServerFnCompiler,\n detectKindsInCode,\n} from './compiler'\nimport type { CompileStartFrameworkOptions } from '../types'\nimport type { LookupConfig, LookupKind } from './compiler'\nimport type { PluginOption } from 'vite'\n\nfunction cleanId(id: string): string {\n // Remove null byte prefix used by Vite/Rollup for virtual modules\n if (id.startsWith('\\0')) {\n id = id.slice(1)\n }\n const queryIndex = id.indexOf('?')\n return queryIndex === -1 ? id : id.substring(0, queryIndex)\n}\n\n// Derive transform code filter from KindDetectionPatterns (single source of truth)\nfunction getTransformCodeFilterForEnv(env: 'client' | 'server'): Array<RegExp> {\n const validKinds = LookupKindsPerEnv[env]\n const patterns: Array<RegExp> = []\n for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array<\n [LookupKind, RegExp]\n >) {\n if (validKinds.has(kind)) {\n patterns.push(pattern)\n }\n }\n return patterns\n}\n\nconst getLookupConfigurationsForEnv = (\n env: 'client' | 'server',\n framework: CompileStartFrameworkOptions,\n): Array<LookupConfig> => {\n // Common configs for all environments\n const commonConfigs: Array<LookupConfig> = [\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createServerFn',\n kind: 'Root',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createIsomorphicFn',\n kind: 'IsomorphicFn',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createServerOnlyFn',\n kind: 'ServerOnlyFn',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createClientOnlyFn',\n kind: 'ClientOnlyFn',\n },\n ]\n\n if (env === 'client') {\n return [\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createMiddleware',\n kind: 'Root',\n },\n {\n libName: `@tanstack/${framework}-start`,\n rootExport: 'createStart',\n kind: 'Root',\n },\n ...commonConfigs,\n ]\n } else {\n return commonConfigs\n }\n}\nconst SERVER_FN_LOOKUP = 'server-fn-module-lookup'\n\nfunction buildDirectiveSplitParam(directive: string) {\n return `tsr-directive-${directive.replace(/[^a-zA-Z0-9]/g, '-')}`\n}\n\nexport function createServerFnPlugin(opts: {\n framework: CompileStartFrameworkOptions\n directive: string\n environments: Array<{ name: string; type: 'client' | 'server' }>\n}): PluginOption {\n const compilers: Record<string /* envName */, ServerFnCompiler> = {}\n const directiveSplitParam = buildDirectiveSplitParam(opts.directive)\n\n function perEnvServerFnPlugin(environment: {\n name: string\n type: 'client' | 'server'\n }): PluginOption {\n // Derive transform code filter from KindDetectionPatterns (single source of truth)\n const transformCodeFilter = getTransformCodeFilterForEnv(environment.type)\n\n return {\n name: `tanstack-start-core::server-fn:${environment.name}`,\n enforce: 'pre',\n applyToEnvironment(env) {\n return env.name === environment.name\n },\n transform: {\n filter: {\n id: {\n exclude: new RegExp(`${SERVER_FN_LOOKUP}$`),\n include: TRANSFORM_ID_REGEX,\n },\n code: {\n include: transformCodeFilter,\n },\n },\n async handler(code, id) {\n let compiler = compilers[this.environment.name]\n if (!compiler) {\n compiler = new ServerFnCompiler({\n env: environment.type,\n directive: opts.directive,\n lookupKinds: LookupKindsPerEnv[environment.type],\n lookupConfigurations: getLookupConfigurationsForEnv(\n environment.type,\n opts.framework,\n ),\n loadModule: async (id: string) => {\n if (this.environment.mode === 'build') {\n const loaded = await this.load({ id })\n if (!loaded.code) {\n throw new Error(`could not load module ${id}`)\n }\n compiler!.ingestModule({ code: loaded.code, id })\n } else if (this.environment.mode === 'dev') {\n /**\n * in dev, vite does not return code from `ctx.load()`\n * so instead, we need to take a different approach\n * we must force vite to load the module and run it through the vite plugin pipeline\n * we can do this by using the `fetchModule` method\n * the `captureServerFnModuleLookupPlugin` captures the module code via its transform hook and invokes analyzeModuleAST\n */\n await this.environment.fetchModule(\n id + '?' + SERVER_FN_LOOKUP,\n )\n } else {\n throw new Error(\n `could not load module ${id}: unknown environment mode ${this.environment.mode}`,\n )\n }\n },\n resolveId: async (source: string, importer?: string) => {\n const r = await this.resolve(source, importer)\n if (r) {\n if (!r.external) {\n return cleanId(r.id)\n }\n }\n return null\n },\n })\n compilers[this.environment.name] = compiler\n }\n\n const isProviderFile = id.includes(directiveSplitParam)\n\n // Detect which kinds are present in this file before parsing\n const detectedKinds = detectKindsInCode(code, environment.type)\n\n id = cleanId(id)\n const result = await compiler.compile({\n id,\n code,\n isProviderFile,\n detectedKinds,\n })\n return result\n },\n },\n\n hotUpdate(ctx) {\n const compiler = compilers[this.environment.name]\n\n ctx.modules.forEach((m) => {\n if (m.id) {\n const deleted = compiler?.invalidateModule(m.id)\n if (deleted) {\n m.importers.forEach((importer) => {\n if (importer.id) {\n compiler?.invalidateModule(importer.id)\n }\n })\n }\n }\n })\n },\n }\n }\n\n return [\n ...opts.environments.map(perEnvServerFnPlugin),\n {\n name: 'tanstack-start-core:capture-server-fn-module-lookup',\n // we only need this plugin in dev mode\n apply: 'serve',\n applyToEnvironment(env) {\n return !!opts.environments.find((e) => e.name === env.name)\n },\n transform: {\n filter: {\n id: new RegExp(`${SERVER_FN_LOOKUP}$`),\n },\n handler(code, id) {\n const compiler = compilers[this.environment.name]\n compiler?.ingestModule({ code, id: cleanId(id) })\n },\n },\n },\n ]\n}\n"],"names":["id"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/start-plugin-core",
3
- "version": "1.142.10",
3
+ "version": "1.142.12",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -59,13 +59,13 @@
59
59
  "vitefu": "^1.1.1",
60
60
  "xmlbuilder2": "^4.0.0",
61
61
  "zod": "^3.24.2",
62
- "@tanstack/router-core": "1.142.8",
63
- "@tanstack/router-plugin": "1.142.10",
62
+ "@tanstack/router-core": "1.142.11",
63
+ "@tanstack/router-generator": "1.142.11",
64
64
  "@tanstack/router-utils": "1.141.0",
65
65
  "@tanstack/server-functions-plugin": "1.142.1",
66
- "@tanstack/router-generator": "1.142.10",
67
- "@tanstack/start-server-core": "1.142.9",
68
- "@tanstack/start-client-core": "1.142.9"
66
+ "@tanstack/router-plugin": "1.142.11",
67
+ "@tanstack/start-client-core": "1.142.11",
68
+ "@tanstack/start-server-core": "1.142.11"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/babel__code-frame": "^7.0.6",
@@ -68,6 +68,57 @@ const LookupSetup: Record<LookupKind, MethodChainSetup | DirectCallSetup> = {
68
68
  ClientOnlyFn: { type: 'directCall' },
69
69
  }
70
70
 
71
+ // Single source of truth for detecting which kinds are present in code
72
+ // These patterns are used for:
73
+ // 1. Pre-scanning code to determine which kinds to look for (before AST parsing)
74
+ // 2. Deriving the plugin's transform code filter
75
+ export const KindDetectionPatterns: Record<LookupKind, RegExp> = {
76
+ ServerFn: /\.handler\s*\(/,
77
+ Middleware: /createMiddleware/,
78
+ IsomorphicFn: /createIsomorphicFn/,
79
+ ServerOnlyFn: /createServerOnlyFn/,
80
+ ClientOnlyFn: /createClientOnlyFn/,
81
+ }
82
+
83
+ // Which kinds are valid for each environment
84
+ export const LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>> = {
85
+ client: new Set([
86
+ 'Middleware',
87
+ 'ServerFn',
88
+ 'IsomorphicFn',
89
+ 'ServerOnlyFn',
90
+ 'ClientOnlyFn',
91
+ ] as const),
92
+ server: new Set([
93
+ 'ServerFn',
94
+ 'IsomorphicFn',
95
+ 'ServerOnlyFn',
96
+ 'ClientOnlyFn',
97
+ ] as const),
98
+ }
99
+
100
+ /**
101
+ * Detects which LookupKinds are present in the code using string matching.
102
+ * This is a fast pre-scan before AST parsing to limit the work done during compilation.
103
+ */
104
+ export function detectKindsInCode(
105
+ code: string,
106
+ env: 'client' | 'server',
107
+ ): Set<LookupKind> {
108
+ const detected = new Set<LookupKind>()
109
+ const validForEnv = LookupKindsPerEnv[env]
110
+
111
+ for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array<
112
+ [LookupKind, RegExp]
113
+ >) {
114
+ if (validForEnv.has(kind) && pattern.test(code)) {
115
+ detected.add(kind)
116
+ }
117
+ }
118
+
119
+ return detected
120
+ }
121
+
71
122
  // Pre-computed map: identifier name -> Set<LookupKind> for fast candidate detection (method chain only)
72
123
  // Multiple kinds can share the same identifier (e.g., 'server' and 'client' are used by both Middleware and IsomorphicFn)
73
124
  const IdentifierToKinds = new Map<string, Set<LookupKind>>()
@@ -86,6 +137,16 @@ for (const [kind, setup] of Object.entries(LookupSetup) as Array<
86
137
  }
87
138
  }
88
139
 
140
+ // Known factory function names for direct call and root-as-candidate patterns
141
+ // These are the names that, when called directly, create a new function.
142
+ // Used to filter nested candidates - we only want to include actual factory calls,
143
+ // not invocations of already-created functions (e.g., `myServerFn()` should NOT be a candidate)
144
+ const DirectCallFactoryNames = new Set([
145
+ 'createServerOnlyFn',
146
+ 'createClientOnlyFn',
147
+ 'createIsomorphicFn',
148
+ ])
149
+
89
150
  export type LookupConfig = {
90
151
  libName: string
91
152
  rootExport: string
@@ -100,13 +161,79 @@ interface ModuleInfo {
100
161
  reExportAllSources: Array<string>
101
162
  }
102
163
 
164
+ /**
165
+ * Computes whether any file kinds need direct-call candidate detection.
166
+ * This includes both directCall types (ServerOnlyFn, ClientOnlyFn) and
167
+ * allowRootAsCandidate types (IsomorphicFn).
168
+ */
169
+ function needsDirectCallDetection(kinds: Set<LookupKind>): boolean {
170
+ for (const kind of kinds) {
171
+ const setup = LookupSetup[kind]
172
+ if (setup.type === 'directCall' || setup.allowRootAsCandidate) {
173
+ return true
174
+ }
175
+ }
176
+ return false
177
+ }
178
+
179
+ /**
180
+ * Checks if a CallExpression is a direct-call candidate for NESTED detection.
181
+ * Returns true if the callee is a known factory function name.
182
+ * This is stricter than top-level detection because we need to filter out
183
+ * invocations of existing server functions (e.g., `myServerFn()`).
184
+ */
185
+ function isNestedDirectCallCandidate(node: t.CallExpression): boolean {
186
+ let calleeName: string | undefined
187
+ if (t.isIdentifier(node.callee)) {
188
+ calleeName = node.callee.name
189
+ } else if (
190
+ t.isMemberExpression(node.callee) &&
191
+ t.isIdentifier(node.callee.property)
192
+ ) {
193
+ calleeName = node.callee.property.name
194
+ }
195
+ return calleeName !== undefined && DirectCallFactoryNames.has(calleeName)
196
+ }
197
+
198
+ /**
199
+ * Checks if a CallExpression path is a top-level direct-call candidate.
200
+ * Top-level means the call is the init of a VariableDeclarator at program level.
201
+ * We accept any simple identifier call or namespace call at top level
202
+ * (e.g., `isomorphicFn()`, `TanStackStart.createServerOnlyFn()`) and let
203
+ * resolution verify it. This handles renamed imports.
204
+ */
205
+ function isTopLevelDirectCallCandidate(
206
+ path: babel.NodePath<t.CallExpression>,
207
+ ): boolean {
208
+ const node = path.node
209
+
210
+ // Must be a simple identifier call or namespace call
211
+ const isSimpleCall =
212
+ t.isIdentifier(node.callee) ||
213
+ (t.isMemberExpression(node.callee) &&
214
+ t.isIdentifier(node.callee.object) &&
215
+ t.isIdentifier(node.callee.property))
216
+
217
+ if (!isSimpleCall) {
218
+ return false
219
+ }
220
+
221
+ // Must be top-level: VariableDeclarator -> VariableDeclaration -> Program
222
+ const parent = path.parent
223
+ if (!t.isVariableDeclarator(parent) || parent.init !== node) {
224
+ return false
225
+ }
226
+ const grandParent = path.parentPath.parent
227
+ if (!t.isVariableDeclaration(grandParent)) {
228
+ return false
229
+ }
230
+ return t.isProgram(path.parentPath.parentPath?.parent)
231
+ }
232
+
103
233
  export class ServerFnCompiler {
104
234
  private moduleCache = new Map<string, ModuleInfo>()
105
235
  private initialized = false
106
236
  private validLookupKinds: Set<LookupKind>
107
- // Precomputed flags for candidate detection (avoid recomputing on each collectCandidates call)
108
- private hasDirectCallKinds: boolean
109
- private hasRootAsCandidateKinds: boolean
110
237
  // Fast lookup for direct imports from known libraries (e.g., '@tanstack/react-start')
111
238
  // Maps: libName → (exportName → Kind)
112
239
  // This allows O(1) resolution for the common case without async resolveId calls
@@ -122,18 +249,6 @@ export class ServerFnCompiler {
122
249
  },
123
250
  ) {
124
251
  this.validLookupKinds = options.lookupKinds
125
-
126
- // Precompute flags for candidate detection
127
- this.hasDirectCallKinds = false
128
- this.hasRootAsCandidateKinds = false
129
- for (const kind of options.lookupKinds) {
130
- const setup = LookupSetup[kind]
131
- if (setup.type === 'directCall') {
132
- this.hasDirectCallKinds = true
133
- } else if (setup.allowRootAsCandidate) {
134
- this.hasRootAsCandidateKinds = true
135
- }
136
- }
137
252
  }
138
253
 
139
254
  private async init() {
@@ -310,68 +425,106 @@ export class ServerFnCompiler {
310
425
  code,
311
426
  id,
312
427
  isProviderFile,
428
+ detectedKinds,
313
429
  }: {
314
430
  code: string
315
431
  id: string
316
432
  isProviderFile: boolean
433
+ /** Pre-detected kinds present in this file. If not provided, all valid kinds are checked. */
434
+ detectedKinds?: Set<LookupKind>
317
435
  }) {
318
436
  if (!this.initialized) {
319
437
  await this.init()
320
438
  }
321
- const { info, ast } = this.ingestModule({ code, id })
322
- const candidates = this.collectCandidates(info.bindings)
323
- if (candidates.length === 0) {
324
- // this hook will only be invoked if there is `.handler(` | `.server(` | `.client(` in the code,
325
- // so not discovering a handler candidate is rather unlikely, but maybe possible?
439
+
440
+ // Use detected kinds if provided, otherwise fall back to all valid kinds for this env
441
+ const fileKinds = detectedKinds
442
+ ? new Set([...detectedKinds].filter((k) => this.validLookupKinds.has(k)))
443
+ : this.validLookupKinds
444
+
445
+ // Early exit if no kinds to process
446
+ if (fileKinds.size === 0) {
326
447
  return null
327
448
  }
328
449
 
329
- // let's find out which of the candidates are actually server functions
330
- // Resolve all candidates in parallel for better performance
450
+ const checkDirectCalls = needsDirectCallDetection(fileKinds)
451
+
452
+ const { ast } = this.ingestModule({ code, id })
453
+
454
+ // Single-pass traversal to:
455
+ // 1. Collect candidate paths (only candidates, not all CallExpressions)
456
+ // 2. Build a map for looking up paths of nested calls in method chains
457
+ const candidatePaths: Array<babel.NodePath<t.CallExpression>> = []
458
+ // Map for nested chain lookup - only populated for CallExpressions that are
459
+ // part of a method chain (callee.object is a CallExpression)
460
+ const chainCallPaths = new Map<
461
+ t.CallExpression,
462
+ babel.NodePath<t.CallExpression>
463
+ >()
464
+
465
+ babel.traverse(ast, {
466
+ CallExpression: (path) => {
467
+ const node = path.node
468
+ const parent = path.parent
469
+
470
+ // Check if this call is part of a larger chain (inner call)
471
+ // If so, store it for method chain lookup but don't treat as candidate
472
+ if (
473
+ t.isMemberExpression(parent) &&
474
+ t.isCallExpression(path.parentPath.parent)
475
+ ) {
476
+ // This is an inner call in a chain - store for later lookup
477
+ chainCallPaths.set(node, path)
478
+ return
479
+ }
480
+
481
+ // Pattern 1: Method chain pattern (.handler(), .server(), .client(), etc.)
482
+ if (isMethodChainCandidate(node, fileKinds)) {
483
+ candidatePaths.push(path)
484
+ return
485
+ }
486
+
487
+ // Pattern 2: Direct call pattern
488
+ if (checkDirectCalls) {
489
+ if (isTopLevelDirectCallCandidate(path)) {
490
+ candidatePaths.push(path)
491
+ } else if (isNestedDirectCallCandidate(node)) {
492
+ candidatePaths.push(path)
493
+ }
494
+ }
495
+ },
496
+ })
497
+
498
+ if (candidatePaths.length === 0) {
499
+ return null
500
+ }
501
+
502
+ // Resolve all candidates in parallel to determine their kinds
331
503
  const resolvedCandidates = await Promise.all(
332
- candidates.map(async (candidate) => ({
333
- candidate,
334
- kind: await this.resolveExprKind(candidate, id),
504
+ candidatePaths.map(async (path) => ({
505
+ path,
506
+ kind: await this.resolveExprKind(path.node, id),
335
507
  })),
336
508
  )
337
509
 
338
- // Map from candidate/root node -> kind
339
- // Note: For top-level variable declarations, candidate === root (the outermost CallExpression)
340
- const toRewriteMap = new Map<t.CallExpression, LookupKind>()
341
- for (const { candidate, kind } of resolvedCandidates) {
342
- if (this.validLookupKinds.has(kind as LookupKind)) {
343
- toRewriteMap.set(candidate, kind as LookupKind)
344
- }
345
- }
346
- if (toRewriteMap.size === 0) {
510
+ // Filter to valid candidates
511
+ const validCandidates = resolvedCandidates.filter(({ kind }) =>
512
+ this.validLookupKinds.has(kind as LookupKind),
513
+ ) as Array<{ path: babel.NodePath<t.CallExpression>; kind: LookupKind }>
514
+
515
+ if (validCandidates.length === 0) {
347
516
  return null
348
517
  }
349
518
 
350
- // Single-pass traversal to find NodePaths and collect method chains
519
+ // Process valid candidates to collect method chains
351
520
  const pathsToRewrite: Array<{
352
521
  path: babel.NodePath<t.CallExpression>
353
522
  kind: LookupKind
354
523
  methodChain: MethodChainPaths
355
524
  }> = []
356
525
 
357
- // First, collect all CallExpression paths in the AST for O(1) lookup
358
- const callExprPaths = new Map<
359
- t.CallExpression,
360
- babel.NodePath<t.CallExpression>
361
- >()
362
-
363
- babel.traverse(ast, {
364
- CallExpression(path) {
365
- callExprPaths.set(path.node, path)
366
- },
367
- })
368
-
369
- // Now process candidates - we can look up any CallExpression path in O(1)
370
- for (const [node, kind] of toRewriteMap) {
371
- const path = callExprPaths.get(node)
372
- if (!path) {
373
- continue
374
- }
526
+ for (const { path, kind } of validCandidates) {
527
+ const node = path.node
375
528
 
376
529
  // Collect method chain paths by walking DOWN from root through the chain
377
530
  const methodChain: MethodChainPaths = {
@@ -384,6 +537,8 @@ export class ServerFnCompiler {
384
537
 
385
538
  // Walk down the call chain using nodes, look up paths from map
386
539
  let currentNode: t.CallExpression = node
540
+ let currentPath: babel.NodePath<t.CallExpression> = path
541
+
387
542
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
388
543
  while (true) {
389
544
  const callee = currentNode.callee
@@ -395,7 +550,6 @@ export class ServerFnCompiler {
395
550
  if (t.isIdentifier(callee.property)) {
396
551
  const name = callee.property.name as keyof MethodChainPaths
397
552
  if (name in methodChain) {
398
- const currentPath = callExprPaths.get(currentNode)!
399
553
  // Get first argument path
400
554
  const args = currentPath.get('arguments')
401
555
  const firstArgPath =
@@ -412,18 +566,17 @@ export class ServerFnCompiler {
412
566
  break
413
567
  }
414
568
  currentNode = callee.object
569
+ // Look up path from chain map, or use candidate path if not found
570
+ const nextPath = chainCallPaths.get(currentNode)
571
+ if (!nextPath) {
572
+ break
573
+ }
574
+ currentPath = nextPath
415
575
  }
416
576
 
417
577
  pathsToRewrite.push({ path, kind, methodChain })
418
578
  }
419
579
 
420
- // Verify we found all candidates (pathsToRewrite should have same size as toRewriteMap had)
421
- if (pathsToRewrite.length !== toRewriteMap.size) {
422
- throw new Error(
423
- `Internal error: could not find all paths to rewrite. please file an issue`,
424
- )
425
- }
426
-
427
580
  const refIdents = findReferencedIdentifiers(ast)
428
581
 
429
582
  for (const { path, kind, methodChain } of pathsToRewrite) {
@@ -461,42 +614,6 @@ export class ServerFnCompiler {
461
614
  })
462
615
  }
463
616
 
464
- // collects all candidate CallExpressions at top-level
465
- private collectCandidates(bindings: Map<string, Binding>) {
466
- const candidates: Array<t.CallExpression> = []
467
-
468
- for (const binding of bindings.values()) {
469
- if (binding.type === 'var' && t.isCallExpression(binding.init)) {
470
- // Pattern 1: Method chain pattern (.handler(), .server(), etc.)
471
- const methodChainCandidate = isCandidateCallExpression(
472
- binding.init,
473
- this.validLookupKinds,
474
- )
475
- if (methodChainCandidate) {
476
- candidates.push(methodChainCandidate)
477
- continue
478
- }
479
-
480
- // Pattern 2: Direct call pattern
481
- // Handles:
482
- // - createServerOnlyFn(), createClientOnlyFn() (direct call kinds)
483
- // - createIsomorphicFn() (root-as-candidate kinds)
484
- // - TanStackStart.createServerOnlyFn() (namespace calls)
485
- if (this.hasDirectCallKinds || this.hasRootAsCandidateKinds) {
486
- if (
487
- t.isIdentifier(binding.init.callee) ||
488
- (t.isMemberExpression(binding.init.callee) &&
489
- t.isIdentifier(binding.init.callee.property))
490
- ) {
491
- // Include as candidate - kind resolution will verify it's actually a known export
492
- candidates.push(binding.init)
493
- }
494
- }
495
- }
496
- }
497
- return candidates
498
- }
499
-
500
617
  private async resolveIdentifierKind(
501
618
  ident: string,
502
619
  id: string,
@@ -793,15 +910,17 @@ export class ServerFnCompiler {
793
910
  }
794
911
  }
795
912
 
796
- function isCandidateCallExpression(
797
- node: t.Node | null | undefined,
913
+ /**
914
+ * Checks if a CallExpression has a method chain pattern that matches any of the lookup kinds.
915
+ * E.g., `.handler()`, `.server()`, `.client()`, `.createMiddlewares()`
916
+ */
917
+ function isMethodChainCandidate(
918
+ node: t.CallExpression,
798
919
  lookupKinds: Set<LookupKind>,
799
- ): t.CallExpression | undefined {
800
- if (!t.isCallExpression(node)) return undefined
801
-
920
+ ): boolean {
802
921
  const callee = node.callee
803
922
  if (!t.isMemberExpression(callee) || !t.isIdentifier(callee.property)) {
804
- return undefined
923
+ return false
805
924
  }
806
925
 
807
926
  // Use pre-computed map for O(1) lookup
@@ -811,10 +930,10 @@ function isCandidateCallExpression(
811
930
  // Check if any of the possible kinds are in the valid lookup kinds
812
931
  for (const kind of possibleKinds) {
813
932
  if (lookupKinds.has(kind)) {
814
- return node
933
+ return true
815
934
  }
816
935
  }
817
936
  }
818
937
 
819
- return undefined
938
+ return false
820
939
  }
@@ -1,28 +1,35 @@
1
1
  import { TRANSFORM_ID_REGEX } from '../constants'
2
- import { ServerFnCompiler } from './compiler'
2
+ import {
3
+ KindDetectionPatterns,
4
+ LookupKindsPerEnv,
5
+ ServerFnCompiler,
6
+ detectKindsInCode,
7
+ } from './compiler'
3
8
  import type { CompileStartFrameworkOptions } from '../types'
4
9
  import type { LookupConfig, LookupKind } from './compiler'
5
10
  import type { PluginOption } from 'vite'
6
11
 
7
12
  function cleanId(id: string): string {
13
+ // Remove null byte prefix used by Vite/Rollup for virtual modules
14
+ if (id.startsWith('\0')) {
15
+ id = id.slice(1)
16
+ }
8
17
  const queryIndex = id.indexOf('?')
9
18
  return queryIndex === -1 ? id : id.substring(0, queryIndex)
10
19
  }
11
20
 
12
- const LookupKindsPerEnv: Record<'client' | 'server', Set<LookupKind>> = {
13
- client: new Set([
14
- 'Middleware',
15
- 'ServerFn',
16
- 'IsomorphicFn',
17
- 'ServerOnlyFn',
18
- 'ClientOnlyFn',
19
- ] as const),
20
- server: new Set([
21
- 'ServerFn',
22
- 'IsomorphicFn',
23
- 'ServerOnlyFn',
24
- 'ClientOnlyFn',
25
- ] as const),
21
+ // Derive transform code filter from KindDetectionPatterns (single source of truth)
22
+ function getTransformCodeFilterForEnv(env: 'client' | 'server'): Array<RegExp> {
23
+ const validKinds = LookupKindsPerEnv[env]
24
+ const patterns: Array<RegExp> = []
25
+ for (const [kind, pattern] of Object.entries(KindDetectionPatterns) as Array<
26
+ [LookupKind, RegExp]
27
+ >) {
28
+ if (validKinds.has(kind)) {
29
+ patterns.push(pattern)
30
+ }
31
+ }
32
+ return patterns
26
33
  }
27
34
 
28
35
  const getLookupConfigurationsForEnv = (
@@ -77,12 +84,6 @@ function buildDirectiveSplitParam(directive: string) {
77
84
  return `tsr-directive-${directive.replace(/[^a-zA-Z0-9]/g, '-')}`
78
85
  }
79
86
 
80
- const commonTransformCodeFilter = [
81
- /\.\s*handler\(/,
82
- /createIsomorphicFn/,
83
- /createServerOnlyFn/,
84
- /createClientOnlyFn/,
85
- ]
86
87
  export function createServerFnPlugin(opts: {
87
88
  framework: CompileStartFrameworkOptions
88
89
  directive: string
@@ -95,16 +96,8 @@ export function createServerFnPlugin(opts: {
95
96
  name: string
96
97
  type: 'client' | 'server'
97
98
  }): PluginOption {
98
- // Code filter patterns for transform functions:
99
- // - `.handler(` for createServerFn
100
- // - `createMiddleware(` for middleware (client only)
101
- // - `createIsomorphicFn` for isomorphic functions
102
- // - `createServerOnlyFn` for server-only functions
103
- // - `createClientOnlyFn` for client-only functions
104
- const transformCodeFilter =
105
- environment.type === 'client'
106
- ? [...commonTransformCodeFilter, /createMiddleware\s*\(/]
107
- : commonTransformCodeFilter
99
+ // Derive transform code filter from KindDetectionPatterns (single source of truth)
100
+ const transformCodeFilter = getTransformCodeFilterForEnv(environment.type)
108
101
 
109
102
  return {
110
103
  name: `tanstack-start-core::server-fn:${environment.name}`,
@@ -172,8 +165,16 @@ export function createServerFnPlugin(opts: {
172
165
 
173
166
  const isProviderFile = id.includes(directiveSplitParam)
174
167
 
168
+ // Detect which kinds are present in this file before parsing
169
+ const detectedKinds = detectKindsInCode(code, environment.type)
170
+
175
171
  id = cleanId(id)
176
- const result = await compiler.compile({ id, code, isProviderFile })
172
+ const result = await compiler.compile({
173
+ id,
174
+ code,
175
+ isProviderFile,
176
+ detectedKinds,
177
+ })
177
178
  return result
178
179
  },
179
180
  },