@spfn/core 0.3.0-beta.11 → 0.3.0-beta.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.
@@ -293,6 +293,15 @@ interface GenerationStats {
293
293
  * Generates a route map file containing routeName → {method, path} mappings.
294
294
  * This allows RPC proxy to resolve routes without importing the full router.
295
295
  *
296
+ * The router is loaded and walked, not parsed from source. A source parser sees
297
+ * the spelling rather than the router: `defineRouter({ createUser })` written on
298
+ * one line, an aliased key `create: createUser`, and a nested router assembled by
299
+ * a second `defineRouter(` in the same file are all ordinary ways to write a
300
+ * router, and all of them used to leave routes out of the map. The RPC client
301
+ * addresses a route by name, so a dropped name is a route that typechecks and
302
+ * 404s. Walking the loaded router registers exactly what `registerRoutes`
303
+ * registers, whatever the source looks like.
304
+ *
296
305
  * @example
297
306
  * ```typescript
298
307
  * // .spfnrc.ts
@@ -326,10 +335,19 @@ interface RouteMapGeneratorConfig {
326
335
  */
327
336
  outputPath?: string;
328
337
  /**
329
- * Additional route directories to scan (for package routers)
338
+ * Extra file patterns to watch, for routes outside src/server/routes
339
+ *
340
+ * @deprecated Ignored. The map is read from the loaded router, which reaches
341
+ * every route through its own imports wherever they live, so there is no
342
+ * longer a set of directories to scan. Still accepted so an existing
343
+ * `.spfnrc.ts` keeps working; the watch patterns it produced are unchanged.
330
344
  */
331
345
  additionalRouteDirs?: string[];
332
346
  }
347
+ /** Thrown when the router cannot produce a route map at all. */
348
+ declare class RouteMapGeneratorError extends Error {
349
+ constructor(message: string);
350
+ }
333
351
 
334
352
  /**
335
353
  * Contract Generator
@@ -392,27 +410,58 @@ declare class ContractGeneratorError extends Error {
392
410
  /**
393
411
  * Router registration guard
394
412
  *
395
- * A contract has to describe what production serves. When a route is registered
396
- * only under a feature flag or an environment check, the generated contract
397
- * describes whichever way the generator happened to run — and the gate then
398
- * compares a promise nobody made.
413
+ * A route registered only behind a feature flag or an environment check makes
414
+ * generated output depend on how the generator happened to run. The contract
415
+ * then describes a promise nobody made, and the route map leaves out a name
416
+ * `registerRoutes` registers in production — so `api.admin.call()` typechecks
417
+ * and 404s wherever the flag differs.
399
418
  *
400
- * Object spread is the one way a key can be conditionally present in the object
401
- * `defineRouter()` receives, so that is what this reads. A spread of a plain
402
- * identifier (`...baseRoutes`) is unconditional and passes; anything computed
403
- * inside the spread does not.
419
+ * What it reads is the router the generator was pointed at: the export the
420
+ * loader found the router under, and the routers that export mounts by name
421
+ * from the same file, since a nested router's routes land in the same flat map.
422
+ * A `defineRouter(` the file writes but the app router never reaches — a second
423
+ * router a test harness mounts, say — registers nothing in production and is
424
+ * not scanned.
425
+ *
426
+ * Reading is done with TypeScript's parser rather than a regular expression
427
+ * over the text. A spread is a syntax node there, so a JSDoc that spells
428
+ * `defineRouter({ ... })` in prose, a brace inside a route path, and a comment
429
+ * holding an old router are not router source. Only a spread whose expression
430
+ * reads a condition is refused — a ternary, `&&`, `||`, `??`. A call is
431
+ * allowed: `...metadataRoutes(config, resource)` is how `@spfn/mcp` composes
432
+ * its router, and loading yields exactly the routes it returns.
433
+ *
434
+ * Past it, by construction: a conditional spread inside an imported route
435
+ * module, a router built by a factory, a condition hoisted to a variable
436
+ * (`const extra = flag ? { admin } : {}` spread afterwards), and a
437
+ * `.packages()` list assembled conditionally. `NODE_ENV` is pinned before the
438
+ * router loads, which covers those only when the shell left it unset: the
439
+ * generator then reads the router production gets. `spfn build` pins
440
+ * `production` itself, so the deployed map is right. `spfn dev` sets
441
+ * `development` before it spawns the watcher, which inherits it — a
442
+ * `NODE_ENV === 'development'` condition hoisted to a variable therefore writes
443
+ * a dev-only route into the map the watcher regenerates, and `spfn build` takes
444
+ * it back out. The generated map's header names the `NODE_ENV` it was written
445
+ * under, so that difference shows up in the diff.
404
446
  */
405
- /** Thrown when the router registers contracted routes conditionally. */
447
+ /** Thrown when the router registers routes conditionally. */
406
448
  declare class ConditionalRegistrationError extends Error {
407
449
  constructor(message: string);
408
450
  }
451
+ interface RegistrationScan {
452
+ /** Path named in the error message. */
453
+ routerPath: string;
454
+ /** Contents of the router file. */
455
+ source: string;
456
+ /** The export the loader found the router under: `appRouter`, `default`, … */
457
+ exportName: string;
458
+ /** What is being generated, named in the message: `route map`, `contract`. */
459
+ subject: string;
460
+ }
409
461
  /**
410
462
  * Refuse a router whose route set depends on a condition.
411
- *
412
- * @param routerPath - path named in the error message
413
- * @param source - contents of the router file
414
463
  */
415
- declare function assertUnconditionalRegistration(routerPath: string, source: string): void;
464
+ declare function assertUnconditionalRegistration(scan: RegistrationScan): void;
416
465
 
417
466
  /**
418
467
  * Built-in Generators Export
@@ -447,4 +496,4 @@ declare function assertUnconditionalRegistration(routerPath: string, source: str
447
496
  */
448
497
  declare const generators: Record<string, unknown>;
449
498
 
450
- export { type ClientGenerationOptions, type CodegenConfig, CodegenOrchestrator, ConditionalRegistrationError, type ContractGeneratorConfig, ContractGeneratorError, type GenerationStats, type Generator, type GeneratorConfig, type GeneratorOptions, type GeneratorTrigger, type OrchestratorOptions, type ResourceRoutes, type RouteContractMapping, type RouteMapGeneratorConfig, assertUnconditionalRegistration, createGeneratorsFromConfig, defineConfig, defineGenerator, generators, loadCodegenConfig };
499
+ export { type ClientGenerationOptions, type CodegenConfig, CodegenOrchestrator, ConditionalRegistrationError, type ContractGeneratorConfig, ContractGeneratorError, type GenerationStats, type Generator, type GeneratorConfig, type GeneratorOptions, type GeneratorTrigger, type OrchestratorOptions, type RegistrationScan, type ResourceRoutes, type RouteContractMapping, type RouteMapGeneratorConfig, RouteMapGeneratorError, assertUnconditionalRegistration, createGeneratorsFromConfig, defineConfig, defineGenerator, generators, loadCodegenConfig };