@spfn/core 0.2.0-beta.6 → 0.2.0-beta.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +181 -1281
  3. package/dist/authz/index.d.ts +34 -0
  4. package/dist/authz/index.js +415 -0
  5. package/dist/authz/index.js.map +1 -0
  6. package/dist/{boss-DI1r4kTS.d.ts → boss-gXhgctn6.d.ts} +40 -0
  7. package/dist/cache/index.js +42 -30
  8. package/dist/cache/index.js.map +1 -1
  9. package/dist/codegen/index.d.ts +55 -8
  10. package/dist/codegen/index.js +183 -7
  11. package/dist/codegen/index.js.map +1 -1
  12. package/dist/config/index.d.ts +585 -6
  13. package/dist/config/index.js +116 -5
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/db/index.d.ts +270 -4
  16. package/dist/db/index.js +404 -60
  17. package/dist/db/index.js.map +1 -1
  18. package/dist/define-middleware-DuXD8Hvu.d.ts +167 -0
  19. package/dist/env/index.d.ts +26 -2
  20. package/dist/env/index.js +15 -5
  21. package/dist/env/index.js.map +1 -1
  22. package/dist/env/loader.d.ts +26 -19
  23. package/dist/env/loader.js +32 -25
  24. package/dist/env/loader.js.map +1 -1
  25. package/dist/errors/index.d.ts +10 -0
  26. package/dist/errors/index.js +20 -2
  27. package/dist/errors/index.js.map +1 -1
  28. package/dist/event/index.d.ts +33 -3
  29. package/dist/event/index.js +24 -3
  30. package/dist/event/index.js.map +1 -1
  31. package/dist/event/sse/client.d.ts +42 -3
  32. package/dist/event/sse/client.js +128 -45
  33. package/dist/event/sse/client.js.map +1 -1
  34. package/dist/event/sse/index.d.ts +12 -5
  35. package/dist/event/sse/index.js +271 -32
  36. package/dist/event/sse/index.js.map +1 -1
  37. package/dist/event/ws/client.d.ts +59 -0
  38. package/dist/event/ws/client.js +273 -0
  39. package/dist/event/ws/client.js.map +1 -0
  40. package/dist/event/ws/index.d.ts +94 -0
  41. package/dist/event/ws/index.js +272 -0
  42. package/dist/event/ws/index.js.map +1 -0
  43. package/dist/job/index.d.ts +2 -2
  44. package/dist/job/index.js +155 -42
  45. package/dist/job/index.js.map +1 -1
  46. package/dist/logger/index.d.ts +5 -0
  47. package/dist/logger/index.js +14 -0
  48. package/dist/logger/index.js.map +1 -1
  49. package/dist/middleware/index.d.ts +243 -2
  50. package/dist/middleware/index.js +1323 -13
  51. package/dist/middleware/index.js.map +1 -1
  52. package/dist/nextjs/index.d.ts +2 -2
  53. package/dist/nextjs/index.js +77 -31
  54. package/dist/nextjs/index.js.map +1 -1
  55. package/dist/nextjs/server.d.ts +53 -23
  56. package/dist/nextjs/server.js +197 -66
  57. package/dist/nextjs/server.js.map +1 -1
  58. package/dist/route/index.d.ts +138 -146
  59. package/dist/route/index.js +238 -22
  60. package/dist/route/index.js.map +1 -1
  61. package/dist/security/index.d.ts +83 -0
  62. package/dist/security/index.js +173 -0
  63. package/dist/security/index.js.map +1 -0
  64. package/dist/server/index.d.ts +450 -17
  65. package/dist/server/index.js +1756 -277
  66. package/dist/server/index.js.map +1 -1
  67. package/dist/{router-Di7ENoah.d.ts → token-manager-jKD_EsSE.d.ts} +121 -1
  68. package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
  69. package/dist/types-BFB72jbM.d.ts +282 -0
  70. package/dist/types-DVjf37yO.d.ts +205 -0
  71. package/docs/file-upload.md +717 -0
  72. package/package.json +235 -208
  73. package/dist/types-B-e_f2dQ.d.ts +0 -121
@@ -1,12 +1,19 @@
1
1
  import { logger } from '@spfn/core/logger';
2
+ import { env } from '@spfn/core/config';
2
3
 
3
4
  // src/cache/cache-factory.ts
5
+ function resilienceOptions() {
6
+ return {
7
+ maxRetriesPerRequest: env.CACHE_MAX_RETRIES_PER_REQUEST,
8
+ enableOfflineQueue: env.CACHE_ENABLE_OFFLINE_QUEUE
9
+ };
10
+ }
4
11
  var cacheLogger = logger.child("@spfn/core:cache");
5
12
  function hasCacheConfig() {
6
13
  return !!(process.env.CACHE_URL || process.env.CACHE_WRITE_URL || process.env.CACHE_READ_URL || process.env.CACHE_SENTINEL_HOSTS || process.env.CACHE_CLUSTER_NODES);
7
14
  }
8
15
  function createClient(RedisClient, url) {
9
- const options = {};
16
+ const options = { ...resilienceOptions() };
10
17
  if (url.startsWith("rediss://")) {
11
18
  options.tls = {
12
19
  rejectUnauthorized: process.env.CACHE_TLS_REJECT_UNAUTHORIZED !== "false"
@@ -46,6 +53,7 @@ async function createCacheFromEnv() {
46
53
  return { host: hostname, port: Number(port) || 26379 };
47
54
  });
48
55
  const options = {
56
+ ...resilienceOptions(),
49
57
  sentinels,
50
58
  name: masterName,
51
59
  password
@@ -61,6 +69,7 @@ async function createCacheFromEnv() {
61
69
  });
62
70
  const clusterOptions = {
63
71
  redisOptions: {
72
+ ...resilienceOptions(),
64
73
  password
65
74
  }
66
75
  };
@@ -107,26 +116,29 @@ async function createSingleCacheFromEnv() {
107
116
  return write;
108
117
  }
109
118
  var cacheLogger2 = logger.child("@spfn/core:cache");
110
- var writeInstance;
111
- var readInstance;
112
- var isDisabled = false;
119
+ var CACHE_KEY = Symbol.for("@spfn/core:cache");
120
+ var state = globalThis[CACHE_KEY] ??= {
121
+ write: void 0,
122
+ read: void 0,
123
+ disabled: false
124
+ };
113
125
  function getCache() {
114
- return writeInstance;
126
+ return state.write;
115
127
  }
116
128
  function getCacheRead() {
117
- return readInstance ?? writeInstance;
129
+ return state.read ?? state.write;
118
130
  }
119
131
  function isCacheDisabled() {
120
- return isDisabled;
132
+ return state.disabled;
121
133
  }
122
134
  function setCache(write, read) {
123
- writeInstance = write;
124
- readInstance = read ?? write;
125
- isDisabled = !write;
135
+ state.write = write;
136
+ state.read = read ?? write;
137
+ state.disabled = !write;
126
138
  }
127
139
  async function initCache() {
128
- if (writeInstance) {
129
- return { write: writeInstance, read: readInstance, disabled: isDisabled };
140
+ if (state.write) {
141
+ return { write: state.write, read: state.read, disabled: state.disabled };
130
142
  }
131
143
  const { write, read } = await createCacheFromEnv();
132
144
  if (write) {
@@ -135,15 +147,15 @@ async function initCache() {
135
147
  if (read && read !== write) {
136
148
  await read.ping();
137
149
  }
138
- writeInstance = write;
139
- readInstance = read;
140
- isDisabled = false;
150
+ state.write = write;
151
+ state.read = read;
152
+ state.disabled = false;
141
153
  const hasReplica = read && read !== write;
142
154
  cacheLogger2.info(
143
155
  hasReplica ? "Cache connected (Master-Replica)" : "Cache connected",
144
156
  { mode: "enabled" }
145
157
  );
146
- return { write: writeInstance, read: readInstance, disabled: false };
158
+ return { write: state.write, read: state.read, disabled: false };
147
159
  } catch (error) {
148
160
  cacheLogger2.error(
149
161
  "Cache connection failed - running in disabled mode",
@@ -157,46 +169,46 @@ async function initCache() {
157
169
  }
158
170
  } catch {
159
171
  }
160
- isDisabled = true;
172
+ state.disabled = true;
161
173
  return { write: void 0, read: void 0, disabled: true };
162
174
  }
163
175
  }
164
- isDisabled = true;
176
+ state.disabled = true;
165
177
  cacheLogger2.info("Cache disabled - no configuration or library not installed", { mode: "disabled" });
166
178
  return { write: void 0, read: void 0, disabled: true };
167
179
  }
168
180
  async function closeCache() {
169
- if (isDisabled) {
181
+ if (state.disabled) {
170
182
  cacheLogger2.debug("Cache already disabled, nothing to close");
171
183
  return;
172
184
  }
173
185
  const closePromises = [];
174
- if (writeInstance) {
186
+ if (state.write) {
175
187
  closePromises.push(
176
- writeInstance.quit().catch((err) => {
188
+ state.write.quit().catch((err) => {
177
189
  cacheLogger2.error("Error closing cache write instance", err);
178
190
  })
179
191
  );
180
192
  }
181
- if (readInstance && readInstance !== writeInstance) {
193
+ if (state.read && state.read !== state.write) {
182
194
  closePromises.push(
183
- readInstance.quit().catch((err) => {
195
+ state.read.quit().catch((err) => {
184
196
  cacheLogger2.error("Error closing cache read instance", err);
185
197
  })
186
198
  );
187
199
  }
188
200
  await Promise.all(closePromises);
189
- writeInstance = void 0;
190
- readInstance = void 0;
191
- isDisabled = true;
201
+ state.write = void 0;
202
+ state.read = void 0;
203
+ state.disabled = true;
192
204
  cacheLogger2.info("Cache connections closed", { mode: "disabled" });
193
205
  }
194
206
  function getCacheInfo() {
195
207
  return {
196
- hasWrite: !!writeInstance,
197
- hasRead: !!readInstance,
198
- isReplica: !!(readInstance && readInstance !== writeInstance),
199
- disabled: isDisabled
208
+ hasWrite: !!state.write,
209
+ hasRead: !!state.read,
210
+ isReplica: !!(state.read && state.read !== state.write),
211
+ disabled: state.disabled
200
212
  };
201
213
  }
202
214
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cache/cache-factory.ts","../../src/cache/cache-manager.ts"],"names":["cacheLogger","logger"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Cache factory with automatic environment variable detection\n */\n\nimport type { Redis, Cluster, RedisOptions, ClusterOptions } from 'ioredis';\nimport { logger } from '@spfn/core/logger';\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\nexport interface CacheClients {\n /** Primary cache for writes (or both read/write if no replica) */\n write?: Redis | Cluster;\n /** Replica cache for reads (optional, falls back to write) */\n read?: Redis | Cluster;\n}\n\n/**\n * Check if any cache configuration exists in environment\n */\nfunction hasCacheConfig(): boolean\n{\n return !!(\n process.env.CACHE_URL ||\n process.env.CACHE_WRITE_URL ||\n process.env.CACHE_READ_URL ||\n process.env.CACHE_SENTINEL_HOSTS ||\n process.env.CACHE_CLUSTER_NODES\n );\n}\n\n/**\n * Create cache client with TLS support\n */\nfunction createClient(\n RedisClient: new (url: string, options?: RedisOptions) => Redis,\n url: string\n): Redis\n{\n const options: RedisOptions = {};\n\n // TLS support for secure connections\n if (url.startsWith('rediss://'))\n {\n options.tls = {\n rejectUnauthorized: process.env.CACHE_TLS_REJECT_UNAUTHORIZED !== 'false',\n };\n }\n\n return new RedisClient(url, options);\n}\n\n/**\n * Create cache client(s) from environment variables\n *\n * Supported patterns (priority order):\n * 1. Single instance: CACHE_URL\n * 2. Master-Replica: CACHE_WRITE_URL + CACHE_READ_URL\n * 3. Sentinel: CACHE_SENTINEL_HOSTS + CACHE_MASTER_NAME\n * 4. Cluster: CACHE_CLUSTER_NODES\n *\n * @returns Cache client(s) or undefined if no configuration found\n *\n * @example\n * ```bash\n * # Single (most common)\n * CACHE_URL=redis://localhost:6379\n * CACHE_URL=rediss://secure.cache.com:6380 # TLS\n *\n * # Master-Replica\n * CACHE_WRITE_URL=redis://master:6379\n * CACHE_READ_URL=redis://replica:6379\n *\n * # Sentinel\n * CACHE_SENTINEL_HOSTS=sentinel1:26379,sentinel2:26379\n * CACHE_MASTER_NAME=mymaster\n * CACHE_PASSWORD=secret\n *\n * # Cluster\n * CACHE_CLUSTER_NODES=node1:6379,node2:6379,node3:6379\n * CACHE_PASSWORD=secret\n * ```\n */\nexport async function createCacheFromEnv(): Promise<CacheClients>\n{\n // Quick exit if no cache config\n if (!hasCacheConfig())\n {\n cacheLogger.info('No cache configuration found - running without cache');\n return { write: undefined, read: undefined };\n }\n\n try\n {\n // Dynamic import to avoid bundling if not used\n const ioredis = await import('ioredis');\n const RedisClient = ioredis.default;\n\n // Get environment variables\n const singleUrl = process.env.CACHE_URL;\n const writeUrl = process.env.CACHE_WRITE_URL;\n const readUrl = process.env.CACHE_READ_URL;\n const clusterNodes = process.env.CACHE_CLUSTER_NODES;\n const sentinelHosts = process.env.CACHE_SENTINEL_HOSTS;\n const masterName = process.env.CACHE_MASTER_NAME;\n const password = process.env.CACHE_PASSWORD;\n\n // 1. Single instance (most common - highest priority)\n if (singleUrl && !writeUrl && !readUrl && !clusterNodes)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created single cache instance', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n return { write: client, read: client };\n }\n\n // 2. Master-Replica pattern (both URLs required)\n if (writeUrl && readUrl)\n {\n const write = createClient(RedisClient, writeUrl);\n const read = createClient(RedisClient, readUrl);\n cacheLogger.debug('Created master-replica cache instances');\n return { write, read };\n }\n\n // 3. Sentinel pattern\n if (sentinelHosts && masterName)\n {\n const sentinels = sentinelHosts.split(',').map((host) =>\n {\n const [hostname, port] = host.trim().split(':');\n return { host: hostname, port: Number(port) || 26379 };\n });\n\n const options: RedisOptions = {\n sentinels,\n name: masterName,\n password,\n };\n\n const client = new RedisClient(options);\n cacheLogger.debug('Created sentinel cache instance', { masterName, sentinels: sentinels.length });\n return { write: client, read: client };\n }\n\n // 4. Cluster pattern\n if (clusterNodes)\n {\n const nodes = clusterNodes.split(',').map((node) =>\n {\n const [host, port] = node.trim().split(':');\n return { host, port: Number(port) || 6379 };\n });\n\n const clusterOptions: ClusterOptions = {\n redisOptions: {\n password,\n },\n };\n\n const cluster = new RedisClient.Cluster(nodes, clusterOptions);\n cacheLogger.debug('Created cluster cache instance', { nodes: nodes.length });\n return { write: cluster, read: cluster };\n }\n\n // 5. Fallback: Single URL with other configs present\n if (singleUrl)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created cache instance (fallback)', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n return { write: client, read: client };\n }\n\n // No valid configuration\n cacheLogger.info('No valid cache configuration found - running without cache');\n return { write: undefined, read: undefined };\n }\n catch (error)\n {\n if (error instanceof Error)\n {\n // Check if it's a missing dependency error\n if (error.message.includes('Cannot find module'))\n {\n cacheLogger.warn(\n 'Cache client library not installed',\n error,\n {\n suggestion: 'Install ioredis to enable cache: pnpm install ioredis',\n mode: 'disabled'\n }\n );\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n error,\n { mode: 'disabled' }\n );\n }\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n { error: String(error), mode: 'disabled' }\n );\n }\n return { write: undefined, read: undefined };\n }\n}\n\n/**\n * Create single cache client (backward compatibility)\n * Only returns write instance\n */\nexport async function createSingleCacheFromEnv(): Promise<Redis | Cluster | undefined>\n{\n const { write } = await createCacheFromEnv();\n return write;\n}","/**\n * Global cache instance manager\n * Provides singleton access to cache (Valkey/Redis) across all modules\n * Supports Master-Replica pattern with separate read/write instances\n *\n * When cache is unavailable, falls back to disabled mode gracefully\n */\n\nimport type { Redis, Cluster } from 'ioredis';\n\nimport { createCacheFromEnv } from './cache-factory';\nimport { logger } from '@spfn/core/logger';\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\nlet writeInstance: Redis | Cluster | undefined;\nlet readInstance: Redis | Cluster | undefined;\nlet isDisabled = false;\n\n/**\n * Get global cache write instance\n *\n * @returns Cache write instance or undefined if disabled/not initialized\n *\n * @example\n * ```typescript\n * import { getCache } from '@spfn/core/cache';\n *\n * const cache = getCache();\n * if (cache) {\n * await cache.set('key', 'value');\n * } else {\n * // Cache disabled - handle gracefully\n * console.log('Cache unavailable, skipping...');\n * }\n * ```\n */\nexport function getCache(): Redis | Cluster | undefined\n{\n return writeInstance;\n}\n\n/**\n * Get global cache read instance (falls back to write if no replica)\n *\n * @returns Cache read instance or write instance as fallback, undefined if disabled\n *\n * @example\n * ```typescript\n * import { getCacheRead } from '@spfn/core/cache';\n *\n * const cache = getCacheRead();\n * if (cache) {\n * const value = await cache.get('key');\n * }\n * ```\n */\nexport function getCacheRead(): Redis | Cluster | undefined\n{\n return readInstance ?? writeInstance;\n}\n\n/**\n * Check if cache is disabled (connection failed or not configured)\n *\n * @example\n * ```typescript\n * import { isCacheDisabled } from '@spfn/core/cache';\n *\n * if (isCacheDisabled()) {\n * // Use alternative strategy (e.g., in-memory cache, database)\n * return await fetchFromDatabase();\n * }\n * ```\n */\nexport function isCacheDisabled(): boolean\n{\n return isDisabled;\n}\n\n/**\n * Set global cache instances (for testing or manual configuration)\n *\n * @param write - Cache write instance\n * @param read - Cache read instance (optional, defaults to write)\n *\n * @example\n * ```typescript\n * import { setCache } from '@spfn/core/cache';\n * import Redis from 'ioredis';\n *\n * const write = new Redis('redis://master:6379');\n * const read = new Redis('redis://replica:6379');\n * setCache(write, read);\n * ```\n */\nexport function setCache(\n write: Redis | Cluster | undefined,\n read?: Redis | Cluster | undefined\n): void\n{\n writeInstance = write;\n readInstance = read ?? write;\n isDisabled = !write;\n}\n\n/**\n * Initialize cache from environment variables\n * Automatically called by startServer()\n *\n * Supported environment variables (priority order):\n * - VALKEY_URL / CACHE_URL (single instance)\n * - VALKEY_WRITE_URL + VALKEY_READ_URL (master-replica)\n * - VALKEY_SENTINEL_HOSTS + VALKEY_MASTER_NAME (sentinel)\n * - VALKEY_CLUSTER_NODES (cluster)\n * - VALKEY_TLS_REJECT_UNAUTHORIZED (TLS config)\n * - Legacy: REDIS_* (backward compatibility)\n *\n * @returns Object with write and read instances, or undefined if disabled\n *\n * @example\n * ```typescript\n * import { initCache } from '@spfn/core/cache';\n *\n * // Manual initialization (not needed if using startServer)\n * const { write, read, disabled } = await initCache();\n * if (!disabled) {\n * console.log('Cache available');\n * }\n * ```\n */\nexport async function initCache(): Promise<{\n write?: Redis | Cluster;\n read?: Redis | Cluster;\n disabled: boolean;\n}>\n{\n // Already initialized\n if (writeInstance)\n {\n return { write: writeInstance, read: readInstance, disabled: isDisabled };\n }\n\n // Auto-detect from environment\n const { write, read } = await createCacheFromEnv();\n\n if (write)\n {\n try\n {\n // Test connection\n await write.ping();\n\n // Test read instance if different\n if (read && read !== write)\n {\n await read.ping();\n }\n\n writeInstance = write;\n readInstance = read;\n isDisabled = false;\n\n const hasReplica = read && read !== write;\n cacheLogger.info(\n hasReplica\n ? 'Cache connected (Master-Replica)'\n : 'Cache connected',\n { mode: 'enabled' }\n );\n\n return { write: writeInstance, read: readInstance, disabled: false };\n }\n catch (error)\n {\n cacheLogger.error(\n 'Cache connection failed - running in disabled mode',\n error instanceof Error ? error : new Error(String(error)),\n { mode: 'disabled' }\n );\n\n // Clean up failed connections\n try\n {\n await write.quit();\n if (read && read !== write)\n {\n await read.quit();\n }\n }\n catch\n {\n // Ignore cleanup errors\n }\n\n isDisabled = true;\n return { write: undefined, read: undefined, disabled: true };\n }\n }\n\n // No configuration or library not installed\n isDisabled = true;\n cacheLogger.info('Cache disabled - no configuration or library not installed', { mode: 'disabled' });\n return { write: undefined, read: undefined, disabled: true };\n}\n\n/**\n * Close all cache connections and cleanup\n *\n * @example\n * ```typescript\n * import { closeCache } from '@spfn/core/cache';\n *\n * // During graceful shutdown\n * await closeCache();\n * ```\n */\nexport async function closeCache(): Promise<void>\n{\n if (isDisabled)\n {\n cacheLogger.debug('Cache already disabled, nothing to close');\n return;\n }\n\n const closePromises: Promise<unknown>[] = [];\n\n if (writeInstance)\n {\n closePromises.push(\n writeInstance.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache write instance', err);\n })\n );\n }\n\n if (readInstance && readInstance !== writeInstance)\n {\n closePromises.push(\n readInstance.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache read instance', err);\n })\n );\n }\n\n await Promise.all(closePromises);\n\n writeInstance = undefined;\n readInstance = undefined;\n isDisabled = true;\n\n cacheLogger.info('Cache connections closed', { mode: 'disabled' });\n}\n\n/**\n * Get cache connection info (for debugging)\n *\n * @example\n * ```typescript\n * import { getCacheInfo } from '@spfn/core/cache';\n *\n * const info = getCacheInfo();\n * console.log(info);\n * // {\n * // hasWrite: true,\n * // hasRead: true,\n * // isReplica: true,\n * // disabled: false\n * // }\n * ```\n */\nexport function getCacheInfo(): {\n hasWrite: boolean;\n hasRead: boolean;\n isReplica: boolean;\n disabled: boolean;\n}\n{\n return {\n hasWrite: !!writeInstance,\n hasRead: !!readInstance,\n isReplica: !!(readInstance && readInstance !== writeInstance),\n disabled: isDisabled,\n };\n}"]}
1
+ {"version":3,"sources":["../../src/cache/cache-factory.ts","../../src/cache/cache-manager.ts"],"names":["cacheLogger","logger"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Cache factory with automatic environment variable detection\n */\n\nimport type { Redis, Cluster, RedisOptions, ClusterOptions } from 'ioredis';\nimport { logger } from '@spfn/core/logger';\nimport { env } from '@spfn/core/config';\n\n/**\n * Connection-resilience options applied to every cache client.\n *\n * ioredis defaults to an unbounded offline command queue and 20 retries per\n * request, so a post-startup cache outage can buffer every command and hang\n * awaits. Bound retries (CACHE_MAX_RETRIES_PER_REQUEST, default 3) so commands\n * fail fast instead of hanging; CACHE_ENABLE_OFFLINE_QUEUE (default true) can be\n * set false for strict fail-fast (reject immediately while disconnected).\n */\nfunction resilienceOptions(): Pick<RedisOptions, 'maxRetriesPerRequest' | 'enableOfflineQueue'>\n{\n return {\n maxRetriesPerRequest: env.CACHE_MAX_RETRIES_PER_REQUEST,\n enableOfflineQueue: env.CACHE_ENABLE_OFFLINE_QUEUE,\n };\n}\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\nexport interface CacheClients {\n /** Primary cache for writes (or both read/write if no replica) */\n write?: Redis | Cluster;\n /** Replica cache for reads (optional, falls back to write) */\n read?: Redis | Cluster;\n}\n\n/**\n * Check if any cache configuration exists in environment\n */\nfunction hasCacheConfig(): boolean\n{\n return !!(\n process.env.CACHE_URL ||\n process.env.CACHE_WRITE_URL ||\n process.env.CACHE_READ_URL ||\n process.env.CACHE_SENTINEL_HOSTS ||\n process.env.CACHE_CLUSTER_NODES\n );\n}\n\n/**\n * Create cache client with TLS support\n */\nfunction createClient(\n RedisClient: new (url: string, options?: RedisOptions) => Redis,\n url: string,\n): Redis\n{\n const options: RedisOptions = { ...resilienceOptions() };\n\n // TLS support for secure connections\n if (url.startsWith('rediss://'))\n {\n options.tls = {\n rejectUnauthorized: process.env.CACHE_TLS_REJECT_UNAUTHORIZED !== 'false',\n };\n }\n\n return new RedisClient(url, options);\n}\n\n/**\n * Create cache client(s) from environment variables\n *\n * Supported patterns (priority order):\n * 1. Single instance: CACHE_URL\n * 2. Master-Replica: CACHE_WRITE_URL + CACHE_READ_URL\n * 3. Sentinel: CACHE_SENTINEL_HOSTS + CACHE_MASTER_NAME\n * 4. Cluster: CACHE_CLUSTER_NODES\n *\n * @returns Cache client(s) or undefined if no configuration found\n *\n * @example\n * ```bash\n * # Single (most common)\n * CACHE_URL=redis://localhost:6379\n * CACHE_URL=rediss://secure.cache.com:6380 # TLS\n *\n * # Master-Replica\n * CACHE_WRITE_URL=redis://master:6379\n * CACHE_READ_URL=redis://replica:6379\n *\n * # Sentinel\n * CACHE_SENTINEL_HOSTS=sentinel1:26379,sentinel2:26379\n * CACHE_MASTER_NAME=mymaster\n * CACHE_PASSWORD=secret\n *\n * # Cluster\n * CACHE_CLUSTER_NODES=node1:6379,node2:6379,node3:6379\n * CACHE_PASSWORD=secret\n * ```\n */\nexport async function createCacheFromEnv(): Promise<CacheClients>\n{\n // Quick exit if no cache config\n if (!hasCacheConfig())\n {\n cacheLogger.info('No cache configuration found - running without cache');\n\n return { write: undefined, read: undefined };\n }\n\n try\n {\n // Dynamic import to avoid bundling if not used\n const ioredis = await import('ioredis');\n const RedisClient = ioredis.default;\n\n // Get environment variables\n const singleUrl = process.env.CACHE_URL;\n const writeUrl = process.env.CACHE_WRITE_URL;\n const readUrl = process.env.CACHE_READ_URL;\n const clusterNodes = process.env.CACHE_CLUSTER_NODES;\n const sentinelHosts = process.env.CACHE_SENTINEL_HOSTS;\n const masterName = process.env.CACHE_MASTER_NAME;\n const password = process.env.CACHE_PASSWORD;\n\n // 1. Single instance (most common - highest priority)\n if (singleUrl && !writeUrl && !readUrl && !clusterNodes)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created single cache instance', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n\n return { write: client, read: client };\n }\n\n // 2. Master-Replica pattern (both URLs required)\n if (writeUrl && readUrl)\n {\n const write = createClient(RedisClient, writeUrl);\n const read = createClient(RedisClient, readUrl);\n cacheLogger.debug('Created master-replica cache instances');\n\n return { write, read };\n }\n\n // 3. Sentinel pattern\n if (sentinelHosts && masterName)\n {\n const sentinels = sentinelHosts.split(',').map((host) =>\n {\n const [hostname, port] = host.trim().split(':');\n\n return { host: hostname, port: Number(port) || 26379 };\n });\n\n const options: RedisOptions = {\n ...resilienceOptions(),\n sentinels,\n name: masterName,\n password,\n };\n\n const client = new RedisClient(options);\n cacheLogger.debug('Created sentinel cache instance', { masterName, sentinels: sentinels.length });\n\n return { write: client, read: client };\n }\n\n // 4. Cluster pattern\n if (clusterNodes)\n {\n const nodes = clusterNodes.split(',').map((node) =>\n {\n const [host, port] = node.trim().split(':');\n\n return { host, port: Number(port) || 6379 };\n });\n\n const clusterOptions: ClusterOptions = {\n redisOptions: {\n ...resilienceOptions(),\n password,\n },\n };\n\n const cluster = new RedisClient.Cluster(nodes, clusterOptions);\n cacheLogger.debug('Created cluster cache instance', { nodes: nodes.length });\n\n return { write: cluster, read: cluster };\n }\n\n // 5. Fallback: Single URL with other configs present\n if (singleUrl)\n {\n const client = createClient(RedisClient, singleUrl);\n cacheLogger.debug('Created cache instance (fallback)', { url: singleUrl.replace(/:[^:@]+@/, ':***@') });\n\n return { write: client, read: client };\n }\n\n // No valid configuration\n cacheLogger.info('No valid cache configuration found - running without cache');\n\n return { write: undefined, read: undefined };\n }\n catch (error)\n {\n if (error instanceof Error)\n {\n // Check if it's a missing dependency error\n if (error.message.includes('Cannot find module'))\n {\n cacheLogger.warn(\n 'Cache client library not installed',\n error,\n {\n suggestion: 'Install ioredis to enable cache: pnpm install ioredis',\n mode: 'disabled',\n },\n );\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n error,\n { mode: 'disabled' },\n );\n }\n }\n else\n {\n cacheLogger.warn(\n 'Failed to create cache client',\n { error: String(error), mode: 'disabled' },\n );\n }\n\n return { write: undefined, read: undefined };\n }\n}\n\n/**\n * Create single cache client (backward compatibility)\n * Only returns write instance\n */\nexport async function createSingleCacheFromEnv(): Promise<Redis | Cluster | undefined>\n{\n const { write } = await createCacheFromEnv();\n\n return write;\n}\n","/**\n * Global cache instance manager\n * Provides singleton access to cache (Valkey/Redis) across all modules\n * Supports Master-Replica pattern with separate read/write instances\n *\n * When cache is unavailable, falls back to disabled mode gracefully\n */\n\nimport type { Redis, Cluster } from 'ioredis';\n\nimport { createCacheFromEnv } from './cache-factory';\nimport { logger } from '@spfn/core/logger';\n\nconst cacheLogger = logger.child('@spfn/core:cache');\n\n// ── globalThis singleton ──────────────────────────────────────────\n// CJS/ESM dual-package hazard: 모듈이 두 번 로드되어도\n// Symbol.for() + globalThis로 동일 상태를 공유한다.\ninterface CacheState\n{\n write: Redis | Cluster | undefined;\n read: Redis | Cluster | undefined;\n disabled: boolean;\n}\n\nconst CACHE_KEY = Symbol.for('@spfn/core:cache');\n\nconst state: CacheState = ((globalThis as any)[CACHE_KEY] ??= {\n write: undefined,\n read: undefined,\n disabled: false,\n});\n\n/**\n * Get global cache write instance\n *\n * @returns Cache write instance or undefined if disabled/not initialized\n *\n * @example\n * ```typescript\n * import { getCache } from '@spfn/core/cache';\n *\n * const cache = getCache();\n * if (cache) {\n * await cache.set('key', 'value');\n * } else {\n * // Cache disabled - handle gracefully\n * console.log('Cache unavailable, skipping...');\n * }\n * ```\n */\nexport function getCache(): Redis | Cluster | undefined\n{\n return state.write;\n}\n\n/**\n * Get global cache read instance (falls back to write if no replica)\n *\n * @returns Cache read instance or write instance as fallback, undefined if disabled\n *\n * @example\n * ```typescript\n * import { getCacheRead } from '@spfn/core/cache';\n *\n * const cache = getCacheRead();\n * if (cache) {\n * const value = await cache.get('key');\n * }\n * ```\n */\nexport function getCacheRead(): Redis | Cluster | undefined\n{\n return state.read ?? state.write;\n}\n\n/**\n * Check if cache is disabled (connection failed or not configured)\n *\n * @example\n * ```typescript\n * import { isCacheDisabled } from '@spfn/core/cache';\n *\n * if (isCacheDisabled()) {\n * // Use alternative strategy (e.g., in-memory cache, database)\n * return await fetchFromDatabase();\n * }\n * ```\n */\nexport function isCacheDisabled(): boolean\n{\n return state.disabled;\n}\n\n/**\n * Set global cache instances (for testing or manual configuration)\n *\n * @param write - Cache write instance\n * @param read - Cache read instance (optional, defaults to write)\n *\n * @example\n * ```typescript\n * import { setCache } from '@spfn/core/cache';\n * import Redis from 'ioredis';\n *\n * const write = new Redis('redis://master:6379');\n * const read = new Redis('redis://replica:6379');\n * setCache(write, read);\n * ```\n */\nexport function setCache(\n write: Redis | Cluster | undefined,\n read?: Redis | Cluster | undefined,\n): void\n{\n state.write = write;\n state.read = read ?? write;\n state.disabled = !write;\n}\n\n/**\n * Initialize cache from environment variables\n * Automatically called by startServer()\n *\n * Supported environment variables (priority order):\n * - VALKEY_URL / CACHE_URL (single instance)\n * - VALKEY_WRITE_URL + VALKEY_READ_URL (master-replica)\n * - VALKEY_SENTINEL_HOSTS + VALKEY_MASTER_NAME (sentinel)\n * - VALKEY_CLUSTER_NODES (cluster)\n * - VALKEY_TLS_REJECT_UNAUTHORIZED (TLS config)\n * - Legacy: REDIS_* (backward compatibility)\n *\n * @returns Object with write and read instances, or undefined if disabled\n *\n * @example\n * ```typescript\n * import { initCache } from '@spfn/core/cache';\n *\n * // Manual initialization (not needed if using startServer)\n * const { write, read, disabled } = await initCache();\n * if (!disabled) {\n * console.log('Cache available');\n * }\n * ```\n */\nexport async function initCache(): Promise<{\n write?: Redis | Cluster;\n read?: Redis | Cluster;\n disabled: boolean;\n}>\n{\n // Already initialized\n if (state.write)\n {\n return { write: state.write, read: state.read, disabled: state.disabled };\n }\n\n // Auto-detect from environment\n const { write, read } = await createCacheFromEnv();\n\n if (write)\n {\n try\n {\n // Test connection\n await write.ping();\n\n // Test read instance if different\n if (read && read !== write)\n {\n await read.ping();\n }\n\n state.write = write;\n state.read = read;\n state.disabled = false;\n\n const hasReplica = read && read !== write;\n cacheLogger.info(\n hasReplica\n ? 'Cache connected (Master-Replica)'\n : 'Cache connected',\n { mode: 'enabled' },\n );\n\n return { write: state.write, read: state.read, disabled: false };\n }\n catch (error)\n {\n cacheLogger.error(\n 'Cache connection failed - running in disabled mode',\n error instanceof Error ? error : new Error(String(error)),\n { mode: 'disabled' },\n );\n\n // Clean up failed connections\n try\n {\n await write.quit();\n if (read && read !== write)\n {\n await read.quit();\n }\n }\n catch\n {\n // Ignore cleanup errors\n }\n\n state.disabled = true;\n\n return { write: undefined, read: undefined, disabled: true };\n }\n }\n\n // No configuration or library not installed\n state.disabled = true;\n cacheLogger.info('Cache disabled - no configuration or library not installed', { mode: 'disabled' });\n\n return { write: undefined, read: undefined, disabled: true };\n}\n\n/**\n * Close all cache connections and cleanup\n *\n * @example\n * ```typescript\n * import { closeCache } from '@spfn/core/cache';\n *\n * // During graceful shutdown\n * await closeCache();\n * ```\n */\nexport async function closeCache(): Promise<void>\n{\n if (state.disabled)\n {\n cacheLogger.debug('Cache already disabled, nothing to close');\n\n return;\n }\n\n const closePromises: Promise<unknown>[] = [];\n\n if (state.write)\n {\n closePromises.push(\n state.write.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache write instance', err);\n }),\n );\n }\n\n if (state.read && state.read !== state.write)\n {\n closePromises.push(\n state.read.quit().catch((err: Error) =>\n {\n cacheLogger.error('Error closing cache read instance', err);\n }),\n );\n }\n\n await Promise.all(closePromises);\n\n state.write = undefined;\n state.read = undefined;\n state.disabled = true;\n\n cacheLogger.info('Cache connections closed', { mode: 'disabled' });\n}\n\n/**\n * Get cache connection info (for debugging)\n *\n * @example\n * ```typescript\n * import { getCacheInfo } from '@spfn/core/cache';\n *\n * const info = getCacheInfo();\n * console.log(info);\n * // {\n * // hasWrite: true,\n * // hasRead: true,\n * // isReplica: true,\n * // disabled: false\n * // }\n * ```\n */\nexport function getCacheInfo(): {\n hasWrite: boolean;\n hasRead: boolean;\n isReplica: boolean;\n disabled: boolean;\n}\n{\n return {\n hasWrite: !!state.write,\n hasRead: !!state.read,\n isReplica: !!(state.read && state.read !== state.write),\n disabled: state.disabled,\n };\n}\n"]}
@@ -270,28 +270,75 @@ interface GenerationStats {
270
270
  }
271
271
 
272
272
  /**
273
- * Built-in Generators Export
273
+ * Route Map Generator
274
+ *
275
+ * Generates a route map file containing routeName → {method, path} mappings.
276
+ * This allows RPC proxy to resolve routes without importing the full router.
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * // .spfnrc.ts
281
+ * import { defineConfig, defineGenerator } from '@spfn/core/codegen';
274
282
  *
275
- * Provides a registry of all built-in generators.
276
- * Custom generators can be added via .spfnrc.ts configuration.
283
+ * export default defineConfig({
284
+ * generators: [
285
+ * defineGenerator({
286
+ * name: '@spfn/core:route-map',
287
+ * routerPath: './src/server/router.ts',
288
+ * outputPath: './src/generated/route-map.ts',
289
+ * })
290
+ * ]
291
+ * });
292
+ * ```
293
+ */
294
+
295
+ interface RouteMapGeneratorConfig {
296
+ /**
297
+ * Generator name (required for package-based loading)
298
+ */
299
+ name: '@spfn/core:route-map';
300
+ /**
301
+ * Path to the router file (relative to project root)
302
+ * @example './src/server/router.ts'
303
+ */
304
+ routerPath: string;
305
+ /**
306
+ * Output path for generated route map (relative to project root)
307
+ * @default './src/generated/route-map.ts'
308
+ */
309
+ outputPath?: string;
310
+ /**
311
+ * Additional route directories to scan (for package routers)
312
+ */
313
+ additionalRouteDirs?: string[];
314
+ }
315
+
316
+ /**
317
+ * Built-in Generators Export
277
318
  *
278
319
  * @example
279
320
  * ```typescript
280
321
  * // .spfnrc.ts
281
322
  * import { defineConfig, defineGenerator } from '@spfn/core/codegen';
323
+ * import type { RouteMapGeneratorConfig } from '@spfn/core/codegen';
282
324
  *
283
325
  * export default defineConfig({
284
326
  * generators: [
285
- * defineGenerator({ path: './my-generator.ts' })
327
+ * defineGenerator<RouteMapGeneratorConfig>({
328
+ * name: '@spfn/core:route-map',
329
+ * routerPath: './src/server/router.ts',
330
+ * outputPath: './src/generated/route-map.ts',
331
+ * })
286
332
  * ]
287
333
  * });
288
334
  * ```
289
335
  */
336
+
290
337
  /**
291
- * Registry of available generators
292
- *
293
- * Used by package-based generator loading (e.g., "@spfn/core:my-generator")
338
+ * @internal
339
+ * Registry of available generators for package-based loading.
340
+ * DO NOT use directly - use defineGenerator({ name: '@spfn/core:route-map', ... }) instead.
294
341
  */
295
342
  declare const generators: Record<string, unknown>;
296
343
 
297
- export { type ClientGenerationOptions, type CodegenConfig, CodegenOrchestrator, type GenerationStats, type Generator, type GeneratorConfig, type GeneratorOptions, type GeneratorTrigger, type OrchestratorOptions, type ResourceRoutes, type RouteContractMapping, createGeneratorsFromConfig, defineConfig, defineGenerator, generators, loadCodegenConfig };
344
+ export { type ClientGenerationOptions, type CodegenConfig, CodegenOrchestrator, type GenerationStats, type Generator, type GeneratorConfig, type GeneratorOptions, type GeneratorTrigger, type OrchestratorOptions, type ResourceRoutes, type RouteContractMapping, type RouteMapGeneratorConfig, createGeneratorsFromConfig, defineConfig, defineGenerator, generators, loadCodegenConfig };
@@ -1,8 +1,8 @@
1
1
  import { watch } from 'chokidar';
2
- import { join, relative } from 'path';
2
+ import { join, relative, sep, dirname, resolve } from 'path';
3
3
  import mm from 'micromatch';
4
4
  import { logger } from '@spfn/core/logger';
5
- import { existsSync, readFileSync } from 'fs';
5
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
6
6
  import { createJiti } from 'jiti';
7
7
 
8
8
  // src/codegen/core/orchestrator.ts
@@ -111,8 +111,10 @@ var CodegenOrchestrator = class {
111
111
  });
112
112
  }
113
113
  this.watcher = watch(watchDirs, {
114
- ignored: /(^|[\/\\])\../,
115
- // ignore dotfiles
114
+ // Ignore dotfiles by path segments relative to cwd — matching the absolute
115
+ // path would ignore everything when the checkout itself lives under a dot
116
+ // directory (e.g. .claude/worktrees/<name>), leaving zero watched files
117
+ ignored: (watchedPath) => relative(this.cwd, watchedPath).split(sep).some((segment) => segment.startsWith(".") && segment !== "." && segment !== ".."),
116
118
  persistent: true,
117
119
  ignoreInitial: true,
118
120
  awaitWriteFinish: {
@@ -172,8 +174,8 @@ var CodegenOrchestrator = class {
172
174
  }
173
175
  };
174
176
  this.watcher.on("add", (path) => handleChange(path, "add")).on("change", (path) => handleChange(path, "change")).on("unlink", (path) => handleChange(path, "unlink"));
175
- return new Promise((resolve, reject) => {
176
- this.watcherClosePromise = { resolve, reject };
177
+ return new Promise((resolve2, reject) => {
178
+ this.watcherClosePromise = { resolve: resolve2, reject };
177
179
  });
178
180
  }
179
181
  };
@@ -276,6 +278,11 @@ async function createGeneratorsFromConfig(config, cwd) {
276
278
  }
277
279
  for (const generatorConfig of config.generators) {
278
280
  try {
281
+ if ("generate" in generatorConfig && typeof generatorConfig.generate === "function") {
282
+ generators2.push(generatorConfig);
283
+ configLogger.info(`Generator instance added: ${generatorConfig.name}`);
284
+ continue;
285
+ }
279
286
  if ("path" in generatorConfig) {
280
287
  const generatorPath = generatorConfig.path.startsWith(".") ? join(cwd, generatorConfig.path) : generatorConfig.path;
281
288
  configLogger.info(`Loading custom generator: ${generatorPath}`);
@@ -321,9 +328,178 @@ async function createGeneratorsFromConfig(config, cwd) {
321
328
  }
322
329
  return generators2;
323
330
  }
331
+ var genLogger = logger.child("@spfn/core:route-map-generator");
332
+ function parseRouteFile(filePath) {
333
+ const routes = [];
334
+ try {
335
+ const content = readFileSync(filePath, "utf-8");
336
+ const routePattern = /export\s+const\s+(\w+)\s*=\s*route\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/gi;
337
+ let match;
338
+ while ((match = routePattern.exec(content)) !== null) {
339
+ const [, name, method, path] = match;
340
+ routes.push({
341
+ name,
342
+ method: method.toUpperCase(),
343
+ path,
344
+ file: filePath
345
+ });
346
+ }
347
+ } catch (error) {
348
+ genLogger.warn(`Failed to parse route file: ${filePath}`, error);
349
+ }
350
+ return routes;
351
+ }
352
+ function parseRouterFile(routerPath) {
353
+ const importPaths = [];
354
+ const routeNames = [];
355
+ try {
356
+ const content = readFileSync(routerPath, "utf-8");
357
+ const importPattern = /import\s+\{[^}]+\}\s+from\s+['"`](\.[^'"`]+)['"`]/g;
358
+ let match;
359
+ while ((match = importPattern.exec(content)) !== null) {
360
+ const importPath = match[1];
361
+ if (importPath.startsWith(".")) {
362
+ importPaths.push(importPath);
363
+ }
364
+ }
365
+ const defineRouterStart = content.indexOf("defineRouter(");
366
+ if (defineRouterStart !== -1) {
367
+ const braceStart = content.indexOf("{", defineRouterStart);
368
+ if (braceStart !== -1) {
369
+ let depth = 1;
370
+ let braceEnd = braceStart + 1;
371
+ while (depth > 0 && braceEnd < content.length) {
372
+ if (content[braceEnd] === "{") depth++;
373
+ else if (content[braceEnd] === "}") depth--;
374
+ braceEnd++;
375
+ }
376
+ const routerContent = content.slice(braceStart + 1, braceEnd - 1);
377
+ const withoutComments = routerContent.replace(/\/\/[^\n]*/g, "");
378
+ const namePattern = /^\s*(\w+)\s*[,\n]/gm;
379
+ while ((match = namePattern.exec(withoutComments)) !== null) {
380
+ routeNames.push(match[1]);
381
+ }
382
+ }
383
+ }
384
+ } catch (error) {
385
+ genLogger.warn(`Failed to parse router file: ${routerPath}`, error);
386
+ }
387
+ return { importPaths, routeNames };
388
+ }
389
+ function generateRouteMapContent(routes) {
390
+ const lines = [
391
+ "/**",
392
+ " * Route Map (Auto-generated)",
393
+ " *",
394
+ " * DO NOT EDIT - This file is generated by @spfn/core:route-map generator",
395
+ " */",
396
+ "",
397
+ "import type { HttpMethod } from '@spfn/core/route';",
398
+ "",
399
+ "export interface RouteInfo",
400
+ "{",
401
+ " method: HttpMethod;",
402
+ " path: string;",
403
+ "}",
404
+ "",
405
+ "export const routeMap: Record<string, RouteInfo> = {"
406
+ ];
407
+ for (const route of routes) {
408
+ lines.push(` ${route.name}: { method: '${route.method}', path: '${route.path}' },`);
409
+ }
410
+ lines.push("};");
411
+ lines.push("");
412
+ lines.push("export type RouteMap = typeof routeMap;");
413
+ lines.push("");
414
+ lines.push("export type RouteName = keyof RouteMap;");
415
+ lines.push("");
416
+ return lines.join("\n");
417
+ }
418
+ function createRouteMapGenerator(config) {
419
+ const {
420
+ routerPath,
421
+ outputPath = "./src/generated/route-map.ts",
422
+ additionalRouteDirs = []
423
+ } = config;
424
+ if (!routerPath) {
425
+ throw new Error(
426
+ `[@spfn/core:route-map] Missing required "routerPath" option.
427
+
428
+ Usage:
429
+ defineGenerator<RouteMapGeneratorConfig>({
430
+ name: '@spfn/core:route-map',
431
+ routerPath: './src/server/router.ts',
432
+ })`
433
+ );
434
+ }
435
+ return {
436
+ name: "@spfn/core:route-map",
437
+ watchPatterns: [
438
+ routerPath,
439
+ // Watch route directories derived from router imports
440
+ "src/server/routes/**/*.ts",
441
+ ...additionalRouteDirs.map((dir) => `${dir}/**/*.ts`)
442
+ ],
443
+ runOn: ["watch", "build", "start", "manual"],
444
+ async generate(options) {
445
+ const { cwd, debug } = options;
446
+ const absoluteRouterPath = join(cwd, routerPath);
447
+ const absoluteOutputPath = join(cwd, outputPath);
448
+ if (!existsSync(absoluteRouterPath)) {
449
+ genLogger.warn(`Router file not found: ${absoluteRouterPath}`);
450
+ return;
451
+ }
452
+ if (debug) {
453
+ genLogger.info("Parsing router file", { path: absoluteRouterPath });
454
+ }
455
+ const { importPaths, routeNames } = parseRouterFile(absoluteRouterPath);
456
+ if (debug) {
457
+ genLogger.info("Found route imports", { count: importPaths.length, names: routeNames });
458
+ }
459
+ const routerDir = dirname(absoluteRouterPath);
460
+ const allRoutes = [];
461
+ for (const importPath of importPaths) {
462
+ let resolvedPath = resolve(routerDir, importPath);
463
+ if (!resolvedPath.endsWith(".ts")) {
464
+ const withTs = resolvedPath + ".ts";
465
+ if (existsSync(withTs)) {
466
+ resolvedPath = withTs;
467
+ } else {
468
+ const indexPath = join(resolvedPath, "index.ts");
469
+ if (existsSync(indexPath)) {
470
+ resolvedPath = indexPath;
471
+ } else {
472
+ resolvedPath = withTs;
473
+ }
474
+ }
475
+ }
476
+ if (existsSync(resolvedPath)) {
477
+ const routes = parseRouteFile(resolvedPath);
478
+ allRoutes.push(...routes);
479
+ if (debug) {
480
+ genLogger.info(`Parsed ${routes.length} routes from ${relative(cwd, resolvedPath)}`);
481
+ }
482
+ }
483
+ }
484
+ const exportedRoutes = allRoutes.filter((r) => routeNames.includes(r.name));
485
+ if (debug) {
486
+ genLogger.info(`Found ${exportedRoutes.length} exported routes`);
487
+ }
488
+ const content = generateRouteMapContent(exportedRoutes);
489
+ const outputDir = dirname(absoluteOutputPath);
490
+ if (!existsSync(outputDir)) {
491
+ mkdirSync(outputDir, { recursive: true });
492
+ }
493
+ writeFileSync(absoluteOutputPath, content, "utf-8");
494
+ genLogger.info(`Generated route map: ${relative(cwd, absoluteOutputPath)} (${exportedRoutes.length} routes)`);
495
+ }
496
+ };
497
+ }
324
498
 
325
499
  // src/codegen/generators/index.ts
326
- var generators = {};
500
+ var generators = {
501
+ "route-map": createRouteMapGenerator
502
+ };
327
503
 
328
504
  export { CodegenOrchestrator, createGeneratorsFromConfig, defineConfig, defineGenerator, generators, loadCodegenConfig };
329
505
  //# sourceMappingURL=index.js.map