express-file-cluster 0.1.4 → 0.1.6

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.
package/dist/index.cjs CHANGED
@@ -133,6 +133,11 @@ async function mountRoutes(app, routes) {
133
133
  // src/cluster/index.ts
134
134
  var import_node_cluster = __toESM(require("cluster"), 1);
135
135
  var import_node_os = __toESM(require("os"), 1);
136
+ var isShuttingDown = false;
137
+ function shutdownMaster() {
138
+ isShuttingDown = true;
139
+ console.log("[EFC] Cluster master shutting down, waiting for workers\u2026");
140
+ }
136
141
  function runMaster(options = {}) {
137
142
  const count = options.workers ?? import_node_os.default.cpus().length;
138
143
  console.log(`[EFC] Primary ${process.pid} starting ${count} workers`);
@@ -144,6 +149,14 @@ function runMaster(options = {}) {
144
149
  });
145
150
  import_node_cluster.default.on("exit", (worker, code, signal) => {
146
151
  const exitCode = code ?? (signal ? -1 : 0);
152
+ if (isShuttingDown) {
153
+ console.log(`[EFC] Worker ${worker.id} gracefully exited`);
154
+ if (Object.keys(import_node_cluster.default.workers ?? {}).length === 0) {
155
+ console.log("[EFC] All workers exited. Primary exiting.");
156
+ process.exit(0);
157
+ }
158
+ return;
159
+ }
147
160
  console.warn(`[EFC] Worker ${worker.id} exited (code=${exitCode}), respawning\u2026`);
148
161
  options.onWorkerCrash?.(worker.id, exitCode);
149
162
  import_node_cluster.default.fork();
@@ -565,15 +578,19 @@ async function ignite(config) {
565
578
  function gracefulShutdown(server, timeoutMs = 1e4) {
566
579
  const shutdown = (signal) => {
567
580
  console.log(`[EFC] ${signal} received \u2014 closing server gracefully\u2026`);
568
- server.closeIdleConnections();
569
- server.close(() => {
570
- console.log("[EFC] Server closed");
571
- process.exit(0);
572
- });
573
- setTimeout(() => {
574
- console.error("[EFC] Forced exit after timeout");
575
- process.exit(1);
576
- }, timeoutMs).unref();
581
+ if (server) {
582
+ server.closeIdleConnections();
583
+ server.close(() => {
584
+ console.log("[EFC] Server closed");
585
+ process.exit(0);
586
+ });
587
+ setTimeout(() => {
588
+ console.error("[EFC] Forced exit after timeout");
589
+ process.exit(1);
590
+ }, timeoutMs).unref();
591
+ } else {
592
+ shutdownMaster();
593
+ }
577
594
  };
578
595
  process.once("SIGTERM", () => shutdown("SIGTERM"));
579
596
  process.once("SIGINT", () => shutdown("SIGINT"));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/router/scan.ts","../src/router/mount.ts","../src/cluster/index.ts","../src/auth/index.ts","../src/db/mongo.ts","../src/db/model.ts","../src/db/index.ts","../src/tasks/scanner.ts","../src/tasks/index.ts","../src/tasks/thread-runner.ts","../src/tasks/bullmq-backend.ts","../src/errors.ts","../src/compose.ts"],"sourcesContent":["import http from 'node:http';\nimport express from 'express';\nimport cors from 'cors';\nimport cookieParser from 'cookie-parser';\nimport cluster from 'node:cluster';\nimport os from 'node:os';\nimport dotenv from 'dotenv';\nimport type { EFCConfig } from './types.js';\nimport { scanDir } from './router/scan.js';\nimport { mountRoutes } from './router/mount.js';\nimport { runMaster } from './cluster/index.js';\nimport { configureAuth } from './auth/index.js';\nimport { HttpError } from './errors.js';\nimport { connectMongo } from './db/mongo.js';\nimport { setDbClient } from './db/index.js';\nimport { scanTasks } from './tasks/scanner.js';\nimport { initBullMQ } from './tasks/bullmq-backend.js';\n\n// Load .env only outside production — production envs are injected by the platform\nif (process.env['NODE_ENV'] !== 'production') {\n dotenv.config();\n}\n\nfunction detectDatabase(url?: string): 'mongodb' | 'postgresql' | undefined {\n if (!url) return undefined;\n if (url.startsWith('mongodb')) return 'mongodb';\n if (url.startsWith('postgres')) return 'postgresql';\n return undefined;\n}\n\nexport async function ignite(config: EFCConfig): Promise<http.Server | undefined> {\n const {\n port: _port,\n workers,\n apiDir,\n globalMiddlewares = [],\n onWorkerReady,\n onWorkerCrash,\n onError,\n } = config;\n\n // All runtime values fall back to environment variables\n const envPort = process.env['PORT'] != null ? Number(process.env['PORT']) : NaN;\n const port = (_port != null && !Number.isNaN(_port)) ? _port\n : !Number.isNaN(envPort) ? envPort\n : 3000;\n const databaseUrl = config.databaseUrl ?? process.env['DATABASE_URL'];\n const database = config.database ?? detectDatabase(databaseUrl);\n const jwtSecret = config.jwtSecret ?? process.env['JWT_SECRET'];\n const authStrategy = config.authStrategy ?? 'http-only';\n const clusterEnabled = config.cluster ?? process.env['NODE_ENV'] === 'production';\n\n if (clusterEnabled && cluster.isPrimary) {\n runMaster({\n workers: workers ?? os.cpus().length,\n ...(onWorkerReady !== undefined && { onWorkerReady }),\n ...(onWorkerCrash !== undefined && { onWorkerCrash }),\n });\n return;\n }\n\n const app = express();\n\n const corsOption = config.cors ?? true;\n if (corsOption !== false) {\n const envOrigins = process.env['CORS_ORIGINS'];\n let origin: string | string[] | boolean;\n if (envOrigins) {\n const list = envOrigins.split(',').map((o) => o.trim()).filter(Boolean);\n origin = list; // always array so cors validates against the request Origin\n } else if (typeof corsOption === 'object' && corsOption.origin !== undefined) {\n origin = corsOption.origin;\n } else {\n origin = true;\n }\n const corsOpts = typeof corsOption === 'object' ? { ...corsOption, origin } : { origin };\n app.use(cors(corsOpts));\n }\n\n app.use(express.json());\n app.use(express.urlencoded({ extended: true }));\n app.use(cookieParser());\n\n for (const mw of globalMiddlewares) {\n app.use(mw);\n }\n\n // Pre-Flight step 1: Connect database\n if (database === 'mongodb' && databaseUrl) {\n const conn = await connectMongo(databaseUrl);\n setDbClient(conn as unknown as Record<string, unknown>);\n }\n\n // Pre-Flight step 2: Configure auth\n if (jwtSecret) {\n const cookieDomain = process.env['COOKIE_DOMAIN'];\n configureAuth({\n secret: jwtSecret,\n strategy: authStrategy,\n expiresIn: process.env['JWT_EXPIRES_IN'] ?? '7d',\n ...(cookieDomain !== undefined && { cookieDomain }),\n });\n }\n\n // Pre-Flight step 3: Scan and register tasks\n if (config.tasksDir) {\n await scanTasks(config.tasksDir);\n }\n\n // Pre-Flight step 4: Start task queue backend\n if (config.tasks) {\n if (config.tasks.backend === 'bullmq') {\n await initBullMQ({\n redisUrl: config.tasks.redisUrl ?? 'redis://localhost:6379',\n concurrency: config.tasks.concurrency ?? 5,\n });\n }\n }\n\n // Pre-Flight step 5: Scan routes and mount\n const routes = scanDir(apiDir);\n await mountRoutes(app, routes);\n\n if (onError) {\n app.use(onError);\n } else {\n app.use(\n (\n err: unknown,\n _req: express.Request,\n res: express.Response,\n _next: express.NextFunction,\n ) => {\n const asHttp = err instanceof Error && 'statusCode' in err\n ? (err as HttpError) : null;\n if (asHttp) {\n res.status(asHttp.statusCode).json({ error: asHttp.message, statusCode: asHttp.statusCode });\n } else {\n console.error('[EFC] Unhandled error:', err);\n res.status(500).json({ error: 'Internal Server Error', statusCode: 500 });\n }\n },\n );\n }\n\n return new Promise<http.Server>((resolve, reject) => {\n const server = app.listen(port);\n\n server.once('listening', () => {\n const wid = (cluster.worker as { id: number } | undefined)?.id ?? 'primary';\n const addr = server.address() as { port: number } | null;\n console.log(`[EFC] Worker ${wid} listening on :${addr?.port ?? port}`);\n resolve(server);\n });\n\n server.once('error', reject);\n });\n}\n\nexport function gracefulShutdown(server: http.Server, timeoutMs = 10_000): void {\n const shutdown = (signal: string) => {\n console.log(`[EFC] ${signal} received — closing server gracefully…`);\n server.closeIdleConnections();\n server.close(() => {\n console.log('[EFC] Server closed');\n process.exit(0);\n });\n setTimeout(() => {\n console.error('[EFC] Forced exit after timeout');\n process.exit(1);\n }, timeoutMs).unref();\n };\n process.once('SIGTERM', () => shutdown('SIGTERM'));\n process.once('SIGINT', () => shutdown('SIGINT'));\n}\n\nexport { HttpError } from './errors.js';\nexport { compose } from './compose.js';\nexport { db, setDbClient, getDbClient, defineModel } from './db/index.js';\nexport { scanDir } from './router/scan.js';\nexport type {\n EFCConfig,\n CorsConfig,\n RouteEntry,\n TaskOptions,\n TaskDefinition,\n DatabaseEngine,\n AuthStrategy,\n ModelSchema,\n ModelCRUD,\n} from './types.js';\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { RouteEntry } from '../types.js';\n\nconst ROUTE_FILE_RE = /\\.(ts|js|mts|mjs|cts|cjs)$/;\nconst DYNAMIC_SEGMENT_RE = /\\[([^\\]]+)\\]/g;\n\nfunction filePathToUrlPath(relativePath: string): string {\n let p = relativePath.replace(ROUTE_FILE_RE, '');\n // index files map to parent path\n p = p.replace(/\\/index$/, '');\n // [param] → :param\n p = p.replace(DYNAMIC_SEGMENT_RE, ':$1');\n return p === '' ? '/' : p.startsWith('/') ? p : `/${p}`;\n}\n\nfunction extractParams(relativePath: string): string[] {\n const params: string[] = [];\n let match: RegExpExecArray | null;\n const re = new RegExp(DYNAMIC_SEGMENT_RE.source, 'g');\n while ((match = re.exec(relativePath)) !== null) {\n if (match[1]) params.push(match[1]);\n }\n return params;\n}\n\nexport function scanDir(dir: string, base: string = dir): RouteEntry[] {\n if (!fs.existsSync(dir)) return [];\n\n const entries: RouteEntry[] = [];\n const items = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const item of items) {\n const fullPath = path.join(dir, item.name);\n if (item.isDirectory()) {\n entries.push(...scanDir(fullPath, base));\n } else if (ROUTE_FILE_RE.test(item.name)) {\n const relative = '/' + path.relative(base, fullPath).replace(/\\\\/g, '/');\n entries.push({\n urlPath: filePathToUrlPath(relative),\n filePath: fullPath,\n params: extractParams(relative),\n });\n }\n }\n\n // Sort: static routes before dynamic ones at each segment level\n return entries.sort((a, b) => {\n const aDynamic = a.urlPath.includes(':') ? 1 : 0;\n const bDynamic = b.urlPath.includes(':') ? 1 : 0;\n return aDynamic - bDynamic || a.urlPath.localeCompare(b.urlPath);\n });\n}\n\nexport { filePathToUrlPath };\n","import type { Application, RequestHandler } from 'express';\nimport type { RouteEntry } from '../types.js';\n\nconst HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const;\ntype HttpMethod = (typeof HTTP_METHODS)[number];\n\nfunction asyncWrap(handler: RequestHandler): RequestHandler {\n return (req, res, next) => {\n Promise.resolve(handler(req, res, next)).catch(next);\n };\n}\n\nexport async function mountRoutes(app: Application, routes: RouteEntry[]): Promise<void> {\n for (const route of routes) {\n const mod = (await import(route.filePath)) as Record<string, unknown>;\n const routeMiddlewares: RequestHandler[] = Array.isArray(mod['middlewares'])\n ? (mod['middlewares'] as RequestHandler[])\n : [];\n\n const implemented: HttpMethod[] = [];\n const unimplemented: HttpMethod[] = [];\n\n for (const method of HTTP_METHODS) {\n if (typeof mod[method] === 'function') {\n implemented.push(method);\n app[method.toLowerCase() as Lowercase<HttpMethod>](\n route.urlPath,\n ...routeMiddlewares,\n asyncWrap(mod[method] as RequestHandler),\n );\n } else {\n unimplemented.push(method);\n }\n }\n\n if (implemented.length > 0 && unimplemented.length > 0) {\n const allowHeader = implemented.join(', ');\n app.all(route.urlPath, (req, res, next) => {\n if ((unimplemented as string[]).includes(req.method)) {\n res.set('Allow', allowHeader).status(405).json({ error: 'Method Not Allowed' });\n } else {\n next();\n }\n });\n }\n }\n}\n\nexport { asyncWrap };\n","import cluster from 'node:cluster';\nimport os from 'node:os';\n\ninterface ClusterOptions {\n workers?: number;\n onWorkerReady?: ((id: number) => void) | undefined;\n onWorkerCrash?: ((id: number, code: number) => void) | undefined;\n}\n\nexport function runMaster(options: ClusterOptions = {}): void {\n const count = options.workers ?? os.cpus().length;\n\n console.log(`[EFC] Primary ${process.pid} starting ${count} workers`);\n\n for (let i = 0; i < count; i++) {\n cluster.fork();\n }\n\n cluster.on('online', (worker) => {\n options.onWorkerReady?.(worker.id);\n });\n\n cluster.on('exit', (worker, code, signal) => {\n const exitCode = code ?? (signal ? -1 : 0);\n console.warn(`[EFC] Worker ${worker.id} exited (code=${exitCode}), respawning…`);\n options.onWorkerCrash?.(worker.id, exitCode);\n cluster.fork();\n });\n}\n\nexport { cluster };\n","import type { RequestHandler, Response } from 'express';\nimport jwt from 'jsonwebtoken';\nimport type { AuthStrategy } from '../types.js';\n\ninterface AuthConfig {\n secret: string;\n strategy: AuthStrategy;\n expiresIn: string;\n cookieDomain?: string | undefined;\n}\n\nlet _config: AuthConfig | null = null;\n\nexport function configureAuth(config: AuthConfig): void {\n _config = config;\n}\n\nfunction getConfig(): AuthConfig {\n if (!_config) throw new Error('[EFC] Auth not configured — pass jwtSecret to ignite()');\n return _config;\n}\n\nexport function issueToken(res: Response, payload: Record<string, unknown>): void {\n const { secret, expiresIn, cookieDomain } = getConfig();\n // expiresIn is a plain string (e.g. '7d'); cast satisfies jwt's branded StringValue\n const token = jwt.sign(payload, secret, { expiresIn: expiresIn as jwt.SignOptions['expiresIn'] & string });\n res.cookie('efc_token', token, {\n httpOnly: true,\n secure: process.env['NODE_ENV'] === 'production',\n sameSite: 'strict',\n ...(cookieDomain !== undefined && { domain: cookieDomain }),\n });\n}\n\nexport function revokeToken(res: Response): void {\n res.clearCookie('efc_token');\n}\n\nexport function signToken(payload: Record<string, unknown>): string {\n const { secret, expiresIn } = getConfig();\n return jwt.sign(payload, secret, { expiresIn: expiresIn as jwt.SignOptions['expiresIn'] & string });\n}\n\nexport const requireAuth: RequestHandler = (req, res, next) => {\n const { secret, strategy } = getConfig();\n\n try {\n let token: string | undefined;\n\n if (strategy === 'http-only') {\n const cookies = (req as typeof req & { cookies: Record<string, string> }).cookies;\n token = cookies['efc_token'];\n } else {\n const auth = req.headers['authorization'];\n if (typeof auth === 'string' && auth.startsWith('Bearer ')) {\n token = auth.slice(7);\n }\n }\n\n if (!token) {\n res.status(401).json({ error: 'Unauthorized' });\n return;\n }\n\n const decoded = jwt.verify(token, secret);\n (req as typeof req & { user: unknown }).user = decoded;\n next();\n } catch {\n res.status(401).json({ error: 'Unauthorized' });\n }\n};\n","// Wraps mongoose connection for Pre-Flight. Throws clearly if mongoose isn't installed.\n\nlet mongoose: typeof import('mongoose');\n\nasync function loadMongoose(): Promise<typeof import('mongoose')> {\n if (mongoose) return mongoose;\n try {\n mongoose = await import('mongoose');\n return mongoose;\n } catch {\n throw new Error(\n '[EFC] mongoose is not installed. Run: npm install mongoose',\n );\n }\n}\n\nexport async function connectMongo(url: string): Promise<import('mongoose').Connection> {\n const mg = await loadMongoose();\n await mg.connect(url);\n console.log('[EFC] MongoDB connected');\n return mg.connection;\n}\n","import type { ModelSchema, ModelCRUD } from '../types.js';\n\ntype AnyRecord = Record<string, unknown> & { id: string };\n\nfunction normalise(doc: Record<string, unknown>): AnyRecord {\n const id = doc['_id'] ? String(doc['_id']) : '';\n return { ...doc, id } as AnyRecord;\n}\n\nasync function getModel(name: string, schemaObj: Record<string, unknown>): Promise<import('mongoose').Model<Record<string, unknown>>> {\n let mg: typeof import('mongoose');\n try {\n mg = await import('mongoose');\n } catch {\n throw new Error('[EFC] mongoose is not installed. Run: npm install mongoose');\n }\n\n if (mg.models[name]) return mg.models[name] as import('mongoose').Model<Record<string, unknown>>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const schema = new mg.Schema(schemaObj as any, { timestamps: true });\n return mg.model<Record<string, unknown>>(name, schema);\n}\n\nfunction buildMongooseSchema(schema: ModelSchema): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, def] of Object.entries(schema)) {\n const entry: Record<string, unknown> = {};\n switch (def.type) {\n case 'string': entry['type'] = String; break;\n case 'number': entry['type'] = Number; break;\n case 'boolean': entry['type'] = Boolean; break;\n case 'date': entry['type'] = Date; break;\n case 'object': entry['type'] = Object; break;\n case 'array': entry['type'] = Array; break;\n }\n if (def.required !== undefined) entry['required'] = def.required;\n if (def.unique !== undefined) entry['unique'] = def.unique;\n if (def.default !== undefined) entry['default'] = def.default;\n out[key] = entry;\n }\n return out;\n}\n\nexport function defineModel<T extends Record<string, unknown>>(\n name: string,\n schema: ModelSchema,\n): ModelCRUD<T> {\n const mongooseSchema = buildMongooseSchema(schema);\n\n return {\n async find(filter = {}) {\n const M = await getModel(name, mongooseSchema);\n const docs = await M.find(filter as Record<string, unknown>).lean();\n return (docs as Record<string, unknown>[]).map(normalise) as (T & { id: string })[];\n },\n\n async findById(id) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findById(id).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async findOne(filter) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findOne(filter as Record<string, unknown>).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async create(data) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.create(data);\n return normalise(doc.toObject() as Record<string, unknown>) as T & { id: string };\n },\n\n async update(id, data) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findByIdAndUpdate(id, data, { new: true }).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async delete(id) {\n const M = await getModel(name, mongooseSchema);\n await M.findByIdAndDelete(id);\n },\n\n async count(filter = {}) {\n const M = await getModel(name, mongooseSchema);\n return M.countDocuments(filter as Record<string, unknown>);\n },\n };\n}\n","// Thread-local DB client populated during Pre-Flight.\n// The Proxy ensures callers import `db` once and never null-check it —\n// it throws with a helpful message if accessed before Pre-Flight completes.\n\ntype AnyClient = Record<string, unknown>;\n\nlet _client: AnyClient | null = null;\n\nexport function setDbClient(client: AnyClient): void {\n _client = client;\n}\n\nexport function getDbClient(): AnyClient {\n if (!_client) {\n throw new Error('[EFC] db not ready — accessed before Pre-Flight completed');\n }\n return _client;\n}\n\nexport const db: AnyClient = new Proxy({} as AnyClient, {\n get(_target, prop: string) {\n return getDbClient()[prop];\n },\n set(_target, prop: string, value: unknown) {\n getDbClient()[prop] = value;\n return true;\n },\n});\n\nexport { connectMongo } from './mongo.js';\nexport { defineModel } from './model.js';\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { registerTask } from './index.js';\nimport type { TaskDefinition } from '../types.js';\n\nexport async function scanTasks(tasksDir: string): Promise<void> {\n if (!fs.existsSync(tasksDir)) return;\n\n const files = fs.readdirSync(tasksDir).filter((f) => /\\.(ts|js|mts|mjs)$/.test(f));\n\n for (const file of files) {\n const filePath = path.join(tasksDir, file);\n const taskName = path.basename(file, path.extname(file));\n\n try {\n const mod = await import(filePath) as Record<string, unknown>;\n const def = mod['default'] as TaskDefinition | undefined;\n\n if (!def || typeof def.handler !== 'function') {\n console.warn(`[EFC] Task file ${file} has no valid default export — skipping`);\n continue;\n }\n\n registerTask(taskName, { ...def, filePath });\n console.log(`[EFC] Registered task: ${taskName}`);\n } catch (err) {\n console.warn(`[EFC] Failed to load task ${file}:`, err);\n }\n }\n}\n","import type { TaskDefinition, TaskOptions } from '../types.js';\n\nexport const taskRegistry = new Map<string, TaskDefinition>();\n\ntype HandlerFn<T> = (payload: T) => Promise<void>;\ntype DefineTaskOverload = {\n <T>(handler: HandlerFn<T>): TaskDefinition<T>;\n <T>(options: TaskOptions, handler: HandlerFn<T>): TaskDefinition<T>;\n};\n\nexport const defineTask: DefineTaskOverload = <T>(\n handlerOrOptions: HandlerFn<T> | TaskOptions,\n maybeHandler?: HandlerFn<T>,\n): TaskDefinition<T> => {\n let options: TaskOptions = {};\n let handler: HandlerFn<T>;\n\n if (typeof handlerOrOptions === 'function') {\n handler = handlerOrOptions;\n } else {\n options = handlerOrOptions;\n if (!maybeHandler) throw new Error('[EFC] defineTask: handler function is required');\n handler = maybeHandler;\n }\n\n return {\n handler: handler as (payload: unknown) => Promise<void>,\n options,\n name: '',\n };\n};\n\ntype EnqueueImpl = (name: string, payload: unknown) => Promise<void>;\nlet _impl: EnqueueImpl | null = null;\n\nexport function setEnqueueImpl(impl: EnqueueImpl): void {\n _impl = impl;\n}\n\nexport async function enqueue<T>(name: string, payload: T): Promise<void> {\n if (!_impl) {\n throw new Error(\n `[EFC] Task queue not initialised. Set tasks.backend in ignite() config.`,\n );\n }\n return _impl(name, payload as unknown);\n}\n\nexport function registerTask(name: string, def: TaskDefinition): void {\n taskRegistry.set(name, { ...def, name });\n}\n","import { Worker, workerData, parentPort, isMainThread } from 'node:worker_threads';\nimport type { TaskDefinition } from '../types.js';\n\n// ── Worker-thread side ────────────────────────────────────────────────────────\nif (!isMainThread) {\n const { handlerPath, payload } = workerData as { handlerPath: string; payload: unknown };\n\n (async () => {\n try {\n const mod = await import(handlerPath) as Record<string, unknown>;\n const def = mod['default'] as TaskDefinition | undefined;\n if (!def || typeof def.handler !== 'function') {\n throw new Error(`[EFC] Thread runner: no valid task export in ${handlerPath}`);\n }\n await def.handler(payload);\n parentPort?.postMessage({ ok: true });\n } catch (err) {\n parentPort?.postMessage({ ok: false, error: String(err) });\n }\n })();\n}\n\n// ── Main-thread side ──────────────────────────────────────────────────────────\nexport function runInThread(handlerPath: string, payload: unknown): Promise<void> {\n return new Promise((resolve, reject) => {\n const worker = new Worker(new URL('./thread-runner.js', import.meta.url), {\n workerData: { handlerPath, payload },\n });\n\n worker.on('message', (msg: { ok: boolean; error?: string }) => {\n if (msg.ok) resolve();\n else reject(new Error(msg.error ?? 'Thread task failed'));\n });\n\n worker.on('error', reject);\n worker.on('exit', (code) => {\n if (code !== 0) reject(new Error(`Thread worker exited with code ${code}`));\n });\n });\n}\n","import type { Job } from 'bullmq';\nimport { taskRegistry, setEnqueueImpl } from './index.js';\nimport { runInThread } from './thread-runner.js';\n\ninterface BullMQOpts {\n redisUrl: string;\n concurrency: number;\n}\n\nexport async function initBullMQ(opts: BullMQOpts): Promise<void> {\n let bullmq: typeof import('bullmq');\n try {\n bullmq = await import('bullmq');\n } catch {\n throw new Error('[EFC] bullmq is not installed. Run: npm install bullmq');\n }\n\n const connection = parseRedisUrl(opts.redisUrl);\n const queue = new bullmq.Queue('efc', { connection });\n\n const worker = new bullmq.Worker(\n 'efc',\n async (job: Job) => {\n const def = taskRegistry.get(job.name);\n if (!def) {\n throw new Error(`[EFC] No task registered for name: ${job.name}`);\n }\n\n if (def.options.thread && def.filePath) {\n await runInThread(def.filePath, job.data);\n } else {\n await def.handler(job.data);\n }\n },\n { connection, concurrency: opts.concurrency },\n );\n\n worker.on('failed', (job: Job | undefined, err: Error) => {\n console.error(`[EFC] Task ${job?.name ?? 'unknown'} failed:`, err.message);\n });\n\n worker.on('completed', (job: Job) => {\n console.log(`[EFC] Task ${job.name} completed (id=${job.id})`);\n });\n\n setEnqueueImpl(async (name, payload) => {\n const def = taskRegistry.get(name);\n if (!def) {\n throw new Error(`[EFC] Cannot enqueue unknown task: \"${name}\". Is the task file in tasksDir?`);\n }\n\n await queue.add(name, payload, {\n attempts: def.options.retries ?? 3,\n backoff: {\n type: def.options.backoff ?? 'exponential',\n delay: 1000,\n },\n });\n });\n\n console.log('[EFC] BullMQ backend initialised');\n}\n\nfunction parseRedisUrl(url: string): { host: string; port: number; password?: string } {\n try {\n const u = new URL(url);\n const result: { host: string; port: number; password?: string } = {\n host: u.hostname || 'localhost',\n port: parseInt(u.port || '6379', 10),\n };\n if (u.password) result.password = u.password;\n return result;\n } catch {\n return { host: 'localhost', port: 6379 };\n }\n}\n","export class HttpError extends Error {\n readonly statusCode: number;\n\n constructor(statusCode: number, message: string) {\n super(message);\n this.name = 'HttpError';\n this.statusCode = statusCode;\n Error.captureStackTrace(this, HttpError);\n }\n}\n","import type { RequestHandler } from 'express';\n\nexport function compose(...handlers: RequestHandler[]): RequestHandler {\n return (req, res, next) => {\n let index = 0;\n\n function dispatch(i: number): void {\n if (i >= handlers.length) {\n next();\n return;\n }\n const handler = handlers[i];\n if (!handler) {\n next();\n return;\n }\n try {\n Promise.resolve(handler(req, res, () => dispatch(i + 1))).catch(next);\n } catch (err) {\n next(err);\n }\n }\n\n dispatch(index);\n };\n}\n"],"mappings":"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","names":["import_node_cluster","import_node_os","fs","path","os","cluster","import_node_fs","import_node_path","fs","path","dotenv","cluster","os","express","cors","cookieParser"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/router/scan.ts","../src/router/mount.ts","../src/cluster/index.ts","../src/auth/index.ts","../src/db/mongo.ts","../src/db/model.ts","../src/db/index.ts","../src/tasks/scanner.ts","../src/tasks/index.ts","../src/tasks/thread-runner.ts","../src/tasks/bullmq-backend.ts","../src/errors.ts","../src/compose.ts"],"sourcesContent":["import http from 'node:http';\nimport express from 'express';\nimport cors from 'cors';\nimport cookieParser from 'cookie-parser';\nimport cluster from 'node:cluster';\nimport os from 'node:os';\nimport dotenv from 'dotenv';\nimport type { EFCConfig } from './types.js';\nimport { scanDir } from './router/scan.js';\nimport { mountRoutes } from './router/mount.js';\nimport { runMaster, shutdownMaster } from './cluster/index.js';\nimport { configureAuth } from './auth/index.js';\nimport { HttpError } from './errors.js';\nimport { connectMongo } from './db/mongo.js';\nimport { setDbClient } from './db/index.js';\nimport { scanTasks } from './tasks/scanner.js';\nimport { initBullMQ } from './tasks/bullmq-backend.js';\n\n// Load .env only outside production — production envs are injected by the platform\nif (process.env['NODE_ENV'] !== 'production') {\n dotenv.config();\n}\n\nfunction detectDatabase(url?: string): 'mongodb' | 'postgresql' | undefined {\n if (!url) return undefined;\n if (url.startsWith('mongodb')) return 'mongodb';\n if (url.startsWith('postgres')) return 'postgresql';\n return undefined;\n}\n\nexport async function ignite(config: EFCConfig): Promise<http.Server | undefined> {\n const {\n port: _port,\n workers,\n apiDir,\n globalMiddlewares = [],\n onWorkerReady,\n onWorkerCrash,\n onError,\n } = config;\n\n // All runtime values fall back to environment variables\n const envPort = process.env['PORT'] != null ? Number(process.env['PORT']) : NaN;\n const port = (_port != null && !Number.isNaN(_port)) ? _port\n : !Number.isNaN(envPort) ? envPort\n : 3000;\n const databaseUrl = config.databaseUrl ?? process.env['DATABASE_URL'];\n const database = config.database ?? detectDatabase(databaseUrl);\n const jwtSecret = config.jwtSecret ?? process.env['JWT_SECRET'];\n const authStrategy = config.authStrategy ?? 'http-only';\n const clusterEnabled = config.cluster ?? process.env['NODE_ENV'] === 'production';\n\n if (clusterEnabled && cluster.isPrimary) {\n runMaster({\n workers: workers ?? os.cpus().length,\n ...(onWorkerReady !== undefined && { onWorkerReady }),\n ...(onWorkerCrash !== undefined && { onWorkerCrash }),\n });\n return;\n }\n\n const app = express();\n\n const corsOption = config.cors ?? true;\n if (corsOption !== false) {\n const envOrigins = process.env['CORS_ORIGINS'];\n let origin: string | string[] | boolean;\n if (envOrigins) {\n const list = envOrigins.split(',').map((o) => o.trim()).filter(Boolean);\n origin = list; // always array so cors validates against the request Origin\n } else if (typeof corsOption === 'object' && corsOption.origin !== undefined) {\n origin = corsOption.origin;\n } else {\n origin = true;\n }\n const corsOpts = typeof corsOption === 'object' ? { ...corsOption, origin } : { origin };\n app.use(cors(corsOpts));\n }\n\n app.use(express.json());\n app.use(express.urlencoded({ extended: true }));\n app.use(cookieParser());\n\n for (const mw of globalMiddlewares) {\n app.use(mw);\n }\n\n // Pre-Flight step 1: Connect database\n if (database === 'mongodb' && databaseUrl) {\n const conn = await connectMongo(databaseUrl);\n setDbClient(conn as unknown as Record<string, unknown>);\n }\n\n // Pre-Flight step 2: Configure auth\n if (jwtSecret) {\n const cookieDomain = process.env['COOKIE_DOMAIN'];\n configureAuth({\n secret: jwtSecret,\n strategy: authStrategy,\n expiresIn: process.env['JWT_EXPIRES_IN'] ?? '7d',\n ...(cookieDomain !== undefined && { cookieDomain }),\n });\n }\n\n // Pre-Flight step 3: Scan and register tasks\n if (config.tasksDir) {\n await scanTasks(config.tasksDir);\n }\n\n // Pre-Flight step 4: Start task queue backend\n if (config.tasks) {\n if (config.tasks.backend === 'bullmq') {\n await initBullMQ({\n redisUrl: config.tasks.redisUrl ?? 'redis://localhost:6379',\n concurrency: config.tasks.concurrency ?? 5,\n });\n }\n }\n\n // Pre-Flight step 5: Scan routes and mount\n const routes = scanDir(apiDir);\n await mountRoutes(app, routes);\n\n if (onError) {\n app.use(onError);\n } else {\n app.use(\n (\n err: unknown,\n _req: express.Request,\n res: express.Response,\n _next: express.NextFunction,\n ) => {\n const asHttp = err instanceof Error && 'statusCode' in err\n ? (err as HttpError) : null;\n if (asHttp) {\n res.status(asHttp.statusCode).json({ error: asHttp.message, statusCode: asHttp.statusCode });\n } else {\n console.error('[EFC] Unhandled error:', err);\n res.status(500).json({ error: 'Internal Server Error', statusCode: 500 });\n }\n },\n );\n }\n\n return new Promise<http.Server>((resolve, reject) => {\n const server = app.listen(port);\n\n server.once('listening', () => {\n const wid = (cluster.worker as { id: number } | undefined)?.id ?? 'primary';\n const addr = server.address() as { port: number } | null;\n console.log(`[EFC] Worker ${wid} listening on :${addr?.port ?? port}`);\n resolve(server);\n });\n\n server.once('error', reject);\n });\n}\n\nexport function gracefulShutdown(server: http.Server | undefined, timeoutMs = 10_000): void {\n const shutdown = (signal: string) => {\n console.log(`[EFC] ${signal} received — closing server gracefully…`);\n if (server) {\n server.closeIdleConnections();\n server.close(() => {\n console.log('[EFC] Server closed');\n process.exit(0);\n });\n setTimeout(() => {\n console.error('[EFC] Forced exit after timeout');\n process.exit(1);\n }, timeoutMs).unref();\n } else {\n shutdownMaster();\n }\n };\n process.once('SIGTERM', () => shutdown('SIGTERM'));\n process.once('SIGINT', () => shutdown('SIGINT'));\n}\n\nexport { HttpError } from './errors.js';\nexport { compose } from './compose.js';\nexport { db, setDbClient, getDbClient, defineModel } from './db/index.js';\nexport { scanDir } from './router/scan.js';\nexport type {\n EFCConfig,\n CorsConfig,\n RouteEntry,\n TaskOptions,\n TaskDefinition,\n DatabaseEngine,\n AuthStrategy,\n ModelSchema,\n ModelCRUD,\n} from './types.js';\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { RouteEntry } from '../types.js';\n\nconst ROUTE_FILE_RE = /\\.(ts|js|mts|mjs|cts|cjs)$/;\nconst DYNAMIC_SEGMENT_RE = /\\[([^\\]]+)\\]/g;\n\nfunction filePathToUrlPath(relativePath: string): string {\n let p = relativePath.replace(ROUTE_FILE_RE, '');\n // index files map to parent path\n p = p.replace(/\\/index$/, '');\n // [param] → :param\n p = p.replace(DYNAMIC_SEGMENT_RE, ':$1');\n return p === '' ? '/' : p.startsWith('/') ? p : `/${p}`;\n}\n\nfunction extractParams(relativePath: string): string[] {\n const params: string[] = [];\n let match: RegExpExecArray | null;\n const re = new RegExp(DYNAMIC_SEGMENT_RE.source, 'g');\n while ((match = re.exec(relativePath)) !== null) {\n if (match[1]) params.push(match[1]);\n }\n return params;\n}\n\nexport function scanDir(dir: string, base: string = dir): RouteEntry[] {\n if (!fs.existsSync(dir)) return [];\n\n const entries: RouteEntry[] = [];\n const items = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const item of items) {\n const fullPath = path.join(dir, item.name);\n if (item.isDirectory()) {\n entries.push(...scanDir(fullPath, base));\n } else if (ROUTE_FILE_RE.test(item.name)) {\n const relative = '/' + path.relative(base, fullPath).replace(/\\\\/g, '/');\n entries.push({\n urlPath: filePathToUrlPath(relative),\n filePath: fullPath,\n params: extractParams(relative),\n });\n }\n }\n\n // Sort: static routes before dynamic ones at each segment level\n return entries.sort((a, b) => {\n const aDynamic = a.urlPath.includes(':') ? 1 : 0;\n const bDynamic = b.urlPath.includes(':') ? 1 : 0;\n return aDynamic - bDynamic || a.urlPath.localeCompare(b.urlPath);\n });\n}\n\nexport { filePathToUrlPath };\n","import type { Application, RequestHandler } from 'express';\nimport type { RouteEntry } from '../types.js';\n\nconst HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const;\ntype HttpMethod = (typeof HTTP_METHODS)[number];\n\nfunction asyncWrap(handler: RequestHandler): RequestHandler {\n return (req, res, next) => {\n Promise.resolve(handler(req, res, next)).catch(next);\n };\n}\n\nexport async function mountRoutes(app: Application, routes: RouteEntry[]): Promise<void> {\n for (const route of routes) {\n const mod = (await import(route.filePath)) as Record<string, unknown>;\n const routeMiddlewares: RequestHandler[] = Array.isArray(mod['middlewares'])\n ? (mod['middlewares'] as RequestHandler[])\n : [];\n\n const implemented: HttpMethod[] = [];\n const unimplemented: HttpMethod[] = [];\n\n for (const method of HTTP_METHODS) {\n if (typeof mod[method] === 'function') {\n implemented.push(method);\n app[method.toLowerCase() as Lowercase<HttpMethod>](\n route.urlPath,\n ...routeMiddlewares,\n asyncWrap(mod[method] as RequestHandler),\n );\n } else {\n unimplemented.push(method);\n }\n }\n\n if (implemented.length > 0 && unimplemented.length > 0) {\n const allowHeader = implemented.join(', ');\n app.all(route.urlPath, (req, res, next) => {\n if ((unimplemented as string[]).includes(req.method)) {\n res.set('Allow', allowHeader).status(405).json({ error: 'Method Not Allowed' });\n } else {\n next();\n }\n });\n }\n }\n}\n\nexport { asyncWrap };\n","import cluster from 'node:cluster';\nimport os from 'node:os';\n\ninterface ClusterOptions {\n workers?: number;\n onWorkerReady?: ((id: number) => void) | undefined;\n onWorkerCrash?: ((id: number, code: number) => void) | undefined;\n}\n\nlet isShuttingDown = false;\n\nexport function shutdownMaster(): void {\n isShuttingDown = true;\n console.log('[EFC] Cluster master shutting down, waiting for workers…');\n}\n\nexport function runMaster(options: ClusterOptions = {}): void {\n const count = options.workers ?? os.cpus().length;\n\n console.log(`[EFC] Primary ${process.pid} starting ${count} workers`);\n\n for (let i = 0; i < count; i++) {\n cluster.fork();\n }\n\n cluster.on('online', (worker) => {\n options.onWorkerReady?.(worker.id);\n });\n\n cluster.on('exit', (worker, code, signal) => {\n const exitCode = code ?? (signal ? -1 : 0);\n if (isShuttingDown) {\n console.log(`[EFC] Worker ${worker.id} gracefully exited`);\n if (Object.keys(cluster.workers ?? {}).length === 0) {\n console.log('[EFC] All workers exited. Primary exiting.');\n process.exit(0);\n }\n return;\n }\n \n console.warn(`[EFC] Worker ${worker.id} exited (code=${exitCode}), respawning…`);\n options.onWorkerCrash?.(worker.id, exitCode);\n cluster.fork();\n });\n}\n\nexport { cluster };\n","import type { RequestHandler, Response } from 'express';\nimport jwt from 'jsonwebtoken';\nimport type { AuthStrategy } from '../types.js';\n\ninterface AuthConfig {\n secret: string;\n strategy: AuthStrategy;\n expiresIn: string;\n cookieDomain?: string | undefined;\n}\n\nlet _config: AuthConfig | null = null;\n\nexport function configureAuth(config: AuthConfig): void {\n _config = config;\n}\n\nfunction getConfig(): AuthConfig {\n if (!_config) throw new Error('[EFC] Auth not configured — pass jwtSecret to ignite()');\n return _config;\n}\n\nexport function issueToken(res: Response, payload: Record<string, unknown>): void {\n const { secret, expiresIn, cookieDomain } = getConfig();\n // expiresIn is a plain string (e.g. '7d'); cast satisfies jwt's branded StringValue\n const token = jwt.sign(payload, secret, { expiresIn: expiresIn as jwt.SignOptions['expiresIn'] & string });\n res.cookie('efc_token', token, {\n httpOnly: true,\n secure: process.env['NODE_ENV'] === 'production',\n sameSite: 'strict',\n ...(cookieDomain !== undefined && { domain: cookieDomain }),\n });\n}\n\nexport function revokeToken(res: Response): void {\n res.clearCookie('efc_token');\n}\n\nexport function signToken(payload: Record<string, unknown>): string {\n const { secret, expiresIn } = getConfig();\n return jwt.sign(payload, secret, { expiresIn: expiresIn as jwt.SignOptions['expiresIn'] & string });\n}\n\nexport const requireAuth: RequestHandler = (req, res, next) => {\n const { secret, strategy } = getConfig();\n\n try {\n let token: string | undefined;\n\n if (strategy === 'http-only') {\n const cookies = (req as typeof req & { cookies: Record<string, string> }).cookies;\n token = cookies['efc_token'];\n } else {\n const auth = req.headers['authorization'];\n if (typeof auth === 'string' && auth.startsWith('Bearer ')) {\n token = auth.slice(7);\n }\n }\n\n if (!token) {\n res.status(401).json({ error: 'Unauthorized' });\n return;\n }\n\n const decoded = jwt.verify(token, secret);\n (req as typeof req & { user: unknown }).user = decoded;\n next();\n } catch {\n res.status(401).json({ error: 'Unauthorized' });\n }\n};\n","// Wraps mongoose connection for Pre-Flight. Throws clearly if mongoose isn't installed.\n\nlet mongoose: typeof import('mongoose');\n\nasync function loadMongoose(): Promise<typeof import('mongoose')> {\n if (mongoose) return mongoose;\n try {\n mongoose = await import('mongoose');\n return mongoose;\n } catch {\n throw new Error(\n '[EFC] mongoose is not installed. Run: npm install mongoose',\n );\n }\n}\n\nexport async function connectMongo(url: string): Promise<import('mongoose').Connection> {\n const mg = await loadMongoose();\n await mg.connect(url);\n console.log('[EFC] MongoDB connected');\n return mg.connection;\n}\n","import type { ModelSchema, ModelCRUD } from '../types.js';\n\ntype AnyRecord = Record<string, unknown> & { id: string };\n\nfunction normalise(doc: Record<string, unknown>): AnyRecord {\n const id = doc['_id'] ? String(doc['_id']) : '';\n return { ...doc, id } as AnyRecord;\n}\n\nasync function getModel(name: string, schemaObj: Record<string, unknown>): Promise<import('mongoose').Model<Record<string, unknown>>> {\n let mg: typeof import('mongoose');\n try {\n mg = await import('mongoose');\n } catch {\n throw new Error('[EFC] mongoose is not installed. Run: npm install mongoose');\n }\n\n if (mg.models[name]) return mg.models[name] as import('mongoose').Model<Record<string, unknown>>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const schema = new mg.Schema(schemaObj as any, { timestamps: true });\n return mg.model<Record<string, unknown>>(name, schema);\n}\n\nfunction buildMongooseSchema(schema: ModelSchema): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, def] of Object.entries(schema)) {\n const entry: Record<string, unknown> = {};\n switch (def.type) {\n case 'string': entry['type'] = String; break;\n case 'number': entry['type'] = Number; break;\n case 'boolean': entry['type'] = Boolean; break;\n case 'date': entry['type'] = Date; break;\n case 'object': entry['type'] = Object; break;\n case 'array': entry['type'] = Array; break;\n }\n if (def.required !== undefined) entry['required'] = def.required;\n if (def.unique !== undefined) entry['unique'] = def.unique;\n if (def.default !== undefined) entry['default'] = def.default;\n out[key] = entry;\n }\n return out;\n}\n\nexport function defineModel<T extends Record<string, unknown>>(\n name: string,\n schema: ModelSchema,\n): ModelCRUD<T> {\n const mongooseSchema = buildMongooseSchema(schema);\n\n return {\n async find(filter = {}) {\n const M = await getModel(name, mongooseSchema);\n const docs = await M.find(filter as Record<string, unknown>).lean();\n return (docs as Record<string, unknown>[]).map(normalise) as (T & { id: string })[];\n },\n\n async findById(id) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findById(id).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async findOne(filter) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findOne(filter as Record<string, unknown>).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async create(data) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.create(data);\n return normalise(doc.toObject() as Record<string, unknown>) as T & { id: string };\n },\n\n async update(id, data) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findByIdAndUpdate(id, data, { new: true }).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async delete(id) {\n const M = await getModel(name, mongooseSchema);\n await M.findByIdAndDelete(id);\n },\n\n async count(filter = {}) {\n const M = await getModel(name, mongooseSchema);\n return M.countDocuments(filter as Record<string, unknown>);\n },\n };\n}\n","// Thread-local DB client populated during Pre-Flight.\n// The Proxy ensures callers import `db` once and never null-check it —\n// it throws with a helpful message if accessed before Pre-Flight completes.\n\ntype AnyClient = Record<string, unknown>;\n\nlet _client: AnyClient | null = null;\n\nexport function setDbClient(client: AnyClient): void {\n _client = client;\n}\n\nexport function getDbClient(): AnyClient {\n if (!_client) {\n throw new Error('[EFC] db not ready — accessed before Pre-Flight completed');\n }\n return _client;\n}\n\nexport const db: AnyClient = new Proxy({} as AnyClient, {\n get(_target, prop: string) {\n return getDbClient()[prop];\n },\n set(_target, prop: string, value: unknown) {\n getDbClient()[prop] = value;\n return true;\n },\n});\n\nexport { connectMongo } from './mongo.js';\nexport { defineModel } from './model.js';\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { registerTask } from './index.js';\nimport type { TaskDefinition } from '../types.js';\n\nexport async function scanTasks(tasksDir: string): Promise<void> {\n if (!fs.existsSync(tasksDir)) return;\n\n const files = fs.readdirSync(tasksDir).filter((f) => /\\.(ts|js|mts|mjs)$/.test(f));\n\n for (const file of files) {\n const filePath = path.join(tasksDir, file);\n const taskName = path.basename(file, path.extname(file));\n\n try {\n const mod = await import(filePath) as Record<string, unknown>;\n const def = mod['default'] as TaskDefinition | undefined;\n\n if (!def || typeof def.handler !== 'function') {\n console.warn(`[EFC] Task file ${file} has no valid default export — skipping`);\n continue;\n }\n\n registerTask(taskName, { ...def, filePath });\n console.log(`[EFC] Registered task: ${taskName}`);\n } catch (err) {\n console.warn(`[EFC] Failed to load task ${file}:`, err);\n }\n }\n}\n","import type { TaskDefinition, TaskOptions } from '../types.js';\n\nexport const taskRegistry = new Map<string, TaskDefinition>();\n\ntype HandlerFn<T> = (payload: T) => Promise<void>;\ntype DefineTaskOverload = {\n <T>(handler: HandlerFn<T>): TaskDefinition<T>;\n <T>(options: TaskOptions, handler: HandlerFn<T>): TaskDefinition<T>;\n};\n\nexport const defineTask: DefineTaskOverload = <T>(\n handlerOrOptions: HandlerFn<T> | TaskOptions,\n maybeHandler?: HandlerFn<T>,\n): TaskDefinition<T> => {\n let options: TaskOptions = {};\n let handler: HandlerFn<T>;\n\n if (typeof handlerOrOptions === 'function') {\n handler = handlerOrOptions;\n } else {\n options = handlerOrOptions;\n if (!maybeHandler) throw new Error('[EFC] defineTask: handler function is required');\n handler = maybeHandler;\n }\n\n return {\n handler: handler as (payload: unknown) => Promise<void>,\n options,\n name: '',\n };\n};\n\ntype EnqueueImpl = (name: string, payload: unknown) => Promise<void>;\nlet _impl: EnqueueImpl | null = null;\n\nexport function setEnqueueImpl(impl: EnqueueImpl): void {\n _impl = impl;\n}\n\nexport async function enqueue<T>(name: string, payload: T): Promise<void> {\n if (!_impl) {\n throw new Error(\n `[EFC] Task queue not initialised. Set tasks.backend in ignite() config.`,\n );\n }\n return _impl(name, payload as unknown);\n}\n\nexport function registerTask(name: string, def: TaskDefinition): void {\n taskRegistry.set(name, { ...def, name });\n}\n","import { Worker, workerData, parentPort, isMainThread } from 'node:worker_threads';\nimport type { TaskDefinition } from '../types.js';\n\n// ── Worker-thread side ────────────────────────────────────────────────────────\nif (!isMainThread) {\n const { handlerPath, payload } = workerData as { handlerPath: string; payload: unknown };\n\n (async () => {\n try {\n const mod = await import(handlerPath) as Record<string, unknown>;\n const def = mod['default'] as TaskDefinition | undefined;\n if (!def || typeof def.handler !== 'function') {\n throw new Error(`[EFC] Thread runner: no valid task export in ${handlerPath}`);\n }\n await def.handler(payload);\n parentPort?.postMessage({ ok: true });\n } catch (err) {\n parentPort?.postMessage({ ok: false, error: String(err) });\n }\n })();\n}\n\n// ── Main-thread side ──────────────────────────────────────────────────────────\nexport function runInThread(handlerPath: string, payload: unknown): Promise<void> {\n return new Promise((resolve, reject) => {\n const worker = new Worker(new URL('./thread-runner.js', import.meta.url), {\n workerData: { handlerPath, payload },\n });\n\n worker.on('message', (msg: { ok: boolean; error?: string }) => {\n if (msg.ok) resolve();\n else reject(new Error(msg.error ?? 'Thread task failed'));\n });\n\n worker.on('error', reject);\n worker.on('exit', (code) => {\n if (code !== 0) reject(new Error(`Thread worker exited with code ${code}`));\n });\n });\n}\n","import type { Job } from 'bullmq';\nimport { taskRegistry, setEnqueueImpl } from './index.js';\nimport { runInThread } from './thread-runner.js';\n\ninterface BullMQOpts {\n redisUrl: string;\n concurrency: number;\n}\n\nexport async function initBullMQ(opts: BullMQOpts): Promise<void> {\n let bullmq: typeof import('bullmq');\n try {\n bullmq = await import('bullmq');\n } catch {\n throw new Error('[EFC] bullmq is not installed. Run: npm install bullmq');\n }\n\n const connection = parseRedisUrl(opts.redisUrl);\n const queue = new bullmq.Queue('efc', { connection });\n\n const worker = new bullmq.Worker(\n 'efc',\n async (job: Job) => {\n const def = taskRegistry.get(job.name);\n if (!def) {\n throw new Error(`[EFC] No task registered for name: ${job.name}`);\n }\n\n if (def.options.thread && def.filePath) {\n await runInThread(def.filePath, job.data);\n } else {\n await def.handler(job.data);\n }\n },\n { connection, concurrency: opts.concurrency },\n );\n\n worker.on('failed', (job: Job | undefined, err: Error) => {\n console.error(`[EFC] Task ${job?.name ?? 'unknown'} failed:`, err.message);\n });\n\n worker.on('completed', (job: Job) => {\n console.log(`[EFC] Task ${job.name} completed (id=${job.id})`);\n });\n\n setEnqueueImpl(async (name, payload) => {\n const def = taskRegistry.get(name);\n if (!def) {\n throw new Error(`[EFC] Cannot enqueue unknown task: \"${name}\". Is the task file in tasksDir?`);\n }\n\n await queue.add(name, payload, {\n attempts: def.options.retries ?? 3,\n backoff: {\n type: def.options.backoff ?? 'exponential',\n delay: 1000,\n },\n });\n });\n\n console.log('[EFC] BullMQ backend initialised');\n}\n\nfunction parseRedisUrl(url: string): { host: string; port: number; password?: string } {\n try {\n const u = new URL(url);\n const result: { host: string; port: number; password?: string } = {\n host: u.hostname || 'localhost',\n port: parseInt(u.port || '6379', 10),\n };\n if (u.password) result.password = u.password;\n return result;\n } catch {\n return { host: 'localhost', port: 6379 };\n }\n}\n","export class HttpError extends Error {\n readonly statusCode: number;\n\n constructor(statusCode: number, message: string) {\n super(message);\n this.name = 'HttpError';\n this.statusCode = statusCode;\n Error.captureStackTrace(this, HttpError);\n }\n}\n","import type { RequestHandler } from 'express';\n\nexport function compose(...handlers: RequestHandler[]): RequestHandler {\n return (req, res, next) => {\n let index = 0;\n\n function dispatch(i: number): void {\n if (i >= handlers.length) {\n next();\n return;\n }\n const handler = handlers[i];\n if (!handler) {\n next();\n return;\n }\n try {\n Promise.resolve(handler(req, res, () => dispatch(i + 1))).catch(next);\n } catch (err) {\n next(err);\n }\n }\n\n dispatch(index);\n };\n}\n"],"mappings":"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","names":["import_node_cluster","import_node_os","fs","path","os","cluster","import_node_fs","import_node_path","fs","path","dotenv","cluster","os","express","cors","cookieParser"]}
package/dist/index.d.cts CHANGED
@@ -20,6 +20,6 @@ declare const db: AnyClient;
20
20
  declare function scanDir(dir: string, base?: string): RouteEntry[];
21
21
 
22
22
  declare function ignite(config: EFCConfig): Promise<http.Server | undefined>;
23
- declare function gracefulShutdown(server: http.Server, timeoutMs?: number): void;
23
+ declare function gracefulShutdown(server: http.Server | undefined, timeoutMs?: number): void;
24
24
 
25
25
  export { EFCConfig, HttpError, ModelCRUD, ModelSchema, RouteEntry, compose, db, defineModel, getDbClient, gracefulShutdown, ignite, scanDir, setDbClient };
package/dist/index.d.ts CHANGED
@@ -20,6 +20,6 @@ declare const db: AnyClient;
20
20
  declare function scanDir(dir: string, base?: string): RouteEntry[];
21
21
 
22
22
  declare function ignite(config: EFCConfig): Promise<http.Server | undefined>;
23
- declare function gracefulShutdown(server: http.Server, timeoutMs?: number): void;
23
+ declare function gracefulShutdown(server: http.Server | undefined, timeoutMs?: number): void;
24
24
 
25
25
  export { EFCConfig, HttpError, ModelCRUD, ModelSchema, RouteEntry, compose, db, defineModel, getDbClient, gracefulShutdown, ignite, scanDir, setDbClient };
package/dist/index.js CHANGED
@@ -91,6 +91,11 @@ async function mountRoutes(app, routes) {
91
91
  // src/cluster/index.ts
92
92
  import cluster from "cluster";
93
93
  import os from "os";
94
+ var isShuttingDown = false;
95
+ function shutdownMaster() {
96
+ isShuttingDown = true;
97
+ console.log("[EFC] Cluster master shutting down, waiting for workers\u2026");
98
+ }
94
99
  function runMaster(options = {}) {
95
100
  const count = options.workers ?? os.cpus().length;
96
101
  console.log(`[EFC] Primary ${process.pid} starting ${count} workers`);
@@ -102,6 +107,14 @@ function runMaster(options = {}) {
102
107
  });
103
108
  cluster.on("exit", (worker, code, signal) => {
104
109
  const exitCode = code ?? (signal ? -1 : 0);
110
+ if (isShuttingDown) {
111
+ console.log(`[EFC] Worker ${worker.id} gracefully exited`);
112
+ if (Object.keys(cluster.workers ?? {}).length === 0) {
113
+ console.log("[EFC] All workers exited. Primary exiting.");
114
+ process.exit(0);
115
+ }
116
+ return;
117
+ }
105
118
  console.warn(`[EFC] Worker ${worker.id} exited (code=${exitCode}), respawning\u2026`);
106
119
  options.onWorkerCrash?.(worker.id, exitCode);
107
120
  cluster.fork();
@@ -522,15 +535,19 @@ async function ignite(config) {
522
535
  function gracefulShutdown(server, timeoutMs = 1e4) {
523
536
  const shutdown = (signal) => {
524
537
  console.log(`[EFC] ${signal} received \u2014 closing server gracefully\u2026`);
525
- server.closeIdleConnections();
526
- server.close(() => {
527
- console.log("[EFC] Server closed");
528
- process.exit(0);
529
- });
530
- setTimeout(() => {
531
- console.error("[EFC] Forced exit after timeout");
532
- process.exit(1);
533
- }, timeoutMs).unref();
538
+ if (server) {
539
+ server.closeIdleConnections();
540
+ server.close(() => {
541
+ console.log("[EFC] Server closed");
542
+ process.exit(0);
543
+ });
544
+ setTimeout(() => {
545
+ console.error("[EFC] Forced exit after timeout");
546
+ process.exit(1);
547
+ }, timeoutMs).unref();
548
+ } else {
549
+ shutdownMaster();
550
+ }
534
551
  };
535
552
  process.once("SIGTERM", () => shutdown("SIGTERM"));
536
553
  process.once("SIGINT", () => shutdown("SIGINT"));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/router/scan.ts","../src/router/mount.ts","../src/cluster/index.ts","../src/auth/index.ts","../src/db/mongo.ts","../src/db/model.ts","../src/db/index.ts","../src/tasks/scanner.ts","../src/tasks/index.ts","../src/tasks/thread-runner.ts","../src/tasks/bullmq-backend.ts","../src/errors.ts","../src/compose.ts"],"sourcesContent":["import http from 'node:http';\nimport express from 'express';\nimport cors from 'cors';\nimport cookieParser from 'cookie-parser';\nimport cluster from 'node:cluster';\nimport os from 'node:os';\nimport dotenv from 'dotenv';\nimport type { EFCConfig } from './types.js';\nimport { scanDir } from './router/scan.js';\nimport { mountRoutes } from './router/mount.js';\nimport { runMaster } from './cluster/index.js';\nimport { configureAuth } from './auth/index.js';\nimport { HttpError } from './errors.js';\nimport { connectMongo } from './db/mongo.js';\nimport { setDbClient } from './db/index.js';\nimport { scanTasks } from './tasks/scanner.js';\nimport { initBullMQ } from './tasks/bullmq-backend.js';\n\n// Load .env only outside production — production envs are injected by the platform\nif (process.env['NODE_ENV'] !== 'production') {\n dotenv.config();\n}\n\nfunction detectDatabase(url?: string): 'mongodb' | 'postgresql' | undefined {\n if (!url) return undefined;\n if (url.startsWith('mongodb')) return 'mongodb';\n if (url.startsWith('postgres')) return 'postgresql';\n return undefined;\n}\n\nexport async function ignite(config: EFCConfig): Promise<http.Server | undefined> {\n const {\n port: _port,\n workers,\n apiDir,\n globalMiddlewares = [],\n onWorkerReady,\n onWorkerCrash,\n onError,\n } = config;\n\n // All runtime values fall back to environment variables\n const envPort = process.env['PORT'] != null ? Number(process.env['PORT']) : NaN;\n const port = (_port != null && !Number.isNaN(_port)) ? _port\n : !Number.isNaN(envPort) ? envPort\n : 3000;\n const databaseUrl = config.databaseUrl ?? process.env['DATABASE_URL'];\n const database = config.database ?? detectDatabase(databaseUrl);\n const jwtSecret = config.jwtSecret ?? process.env['JWT_SECRET'];\n const authStrategy = config.authStrategy ?? 'http-only';\n const clusterEnabled = config.cluster ?? process.env['NODE_ENV'] === 'production';\n\n if (clusterEnabled && cluster.isPrimary) {\n runMaster({\n workers: workers ?? os.cpus().length,\n ...(onWorkerReady !== undefined && { onWorkerReady }),\n ...(onWorkerCrash !== undefined && { onWorkerCrash }),\n });\n return;\n }\n\n const app = express();\n\n const corsOption = config.cors ?? true;\n if (corsOption !== false) {\n const envOrigins = process.env['CORS_ORIGINS'];\n let origin: string | string[] | boolean;\n if (envOrigins) {\n const list = envOrigins.split(',').map((o) => o.trim()).filter(Boolean);\n origin = list; // always array so cors validates against the request Origin\n } else if (typeof corsOption === 'object' && corsOption.origin !== undefined) {\n origin = corsOption.origin;\n } else {\n origin = true;\n }\n const corsOpts = typeof corsOption === 'object' ? { ...corsOption, origin } : { origin };\n app.use(cors(corsOpts));\n }\n\n app.use(express.json());\n app.use(express.urlencoded({ extended: true }));\n app.use(cookieParser());\n\n for (const mw of globalMiddlewares) {\n app.use(mw);\n }\n\n // Pre-Flight step 1: Connect database\n if (database === 'mongodb' && databaseUrl) {\n const conn = await connectMongo(databaseUrl);\n setDbClient(conn as unknown as Record<string, unknown>);\n }\n\n // Pre-Flight step 2: Configure auth\n if (jwtSecret) {\n const cookieDomain = process.env['COOKIE_DOMAIN'];\n configureAuth({\n secret: jwtSecret,\n strategy: authStrategy,\n expiresIn: process.env['JWT_EXPIRES_IN'] ?? '7d',\n ...(cookieDomain !== undefined && { cookieDomain }),\n });\n }\n\n // Pre-Flight step 3: Scan and register tasks\n if (config.tasksDir) {\n await scanTasks(config.tasksDir);\n }\n\n // Pre-Flight step 4: Start task queue backend\n if (config.tasks) {\n if (config.tasks.backend === 'bullmq') {\n await initBullMQ({\n redisUrl: config.tasks.redisUrl ?? 'redis://localhost:6379',\n concurrency: config.tasks.concurrency ?? 5,\n });\n }\n }\n\n // Pre-Flight step 5: Scan routes and mount\n const routes = scanDir(apiDir);\n await mountRoutes(app, routes);\n\n if (onError) {\n app.use(onError);\n } else {\n app.use(\n (\n err: unknown,\n _req: express.Request,\n res: express.Response,\n _next: express.NextFunction,\n ) => {\n const asHttp = err instanceof Error && 'statusCode' in err\n ? (err as HttpError) : null;\n if (asHttp) {\n res.status(asHttp.statusCode).json({ error: asHttp.message, statusCode: asHttp.statusCode });\n } else {\n console.error('[EFC] Unhandled error:', err);\n res.status(500).json({ error: 'Internal Server Error', statusCode: 500 });\n }\n },\n );\n }\n\n return new Promise<http.Server>((resolve, reject) => {\n const server = app.listen(port);\n\n server.once('listening', () => {\n const wid = (cluster.worker as { id: number } | undefined)?.id ?? 'primary';\n const addr = server.address() as { port: number } | null;\n console.log(`[EFC] Worker ${wid} listening on :${addr?.port ?? port}`);\n resolve(server);\n });\n\n server.once('error', reject);\n });\n}\n\nexport function gracefulShutdown(server: http.Server, timeoutMs = 10_000): void {\n const shutdown = (signal: string) => {\n console.log(`[EFC] ${signal} received — closing server gracefully…`);\n server.closeIdleConnections();\n server.close(() => {\n console.log('[EFC] Server closed');\n process.exit(0);\n });\n setTimeout(() => {\n console.error('[EFC] Forced exit after timeout');\n process.exit(1);\n }, timeoutMs).unref();\n };\n process.once('SIGTERM', () => shutdown('SIGTERM'));\n process.once('SIGINT', () => shutdown('SIGINT'));\n}\n\nexport { HttpError } from './errors.js';\nexport { compose } from './compose.js';\nexport { db, setDbClient, getDbClient, defineModel } from './db/index.js';\nexport { scanDir } from './router/scan.js';\nexport type {\n EFCConfig,\n CorsConfig,\n RouteEntry,\n TaskOptions,\n TaskDefinition,\n DatabaseEngine,\n AuthStrategy,\n ModelSchema,\n ModelCRUD,\n} from './types.js';\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { RouteEntry } from '../types.js';\n\nconst ROUTE_FILE_RE = /\\.(ts|js|mts|mjs|cts|cjs)$/;\nconst DYNAMIC_SEGMENT_RE = /\\[([^\\]]+)\\]/g;\n\nfunction filePathToUrlPath(relativePath: string): string {\n let p = relativePath.replace(ROUTE_FILE_RE, '');\n // index files map to parent path\n p = p.replace(/\\/index$/, '');\n // [param] → :param\n p = p.replace(DYNAMIC_SEGMENT_RE, ':$1');\n return p === '' ? '/' : p.startsWith('/') ? p : `/${p}`;\n}\n\nfunction extractParams(relativePath: string): string[] {\n const params: string[] = [];\n let match: RegExpExecArray | null;\n const re = new RegExp(DYNAMIC_SEGMENT_RE.source, 'g');\n while ((match = re.exec(relativePath)) !== null) {\n if (match[1]) params.push(match[1]);\n }\n return params;\n}\n\nexport function scanDir(dir: string, base: string = dir): RouteEntry[] {\n if (!fs.existsSync(dir)) return [];\n\n const entries: RouteEntry[] = [];\n const items = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const item of items) {\n const fullPath = path.join(dir, item.name);\n if (item.isDirectory()) {\n entries.push(...scanDir(fullPath, base));\n } else if (ROUTE_FILE_RE.test(item.name)) {\n const relative = '/' + path.relative(base, fullPath).replace(/\\\\/g, '/');\n entries.push({\n urlPath: filePathToUrlPath(relative),\n filePath: fullPath,\n params: extractParams(relative),\n });\n }\n }\n\n // Sort: static routes before dynamic ones at each segment level\n return entries.sort((a, b) => {\n const aDynamic = a.urlPath.includes(':') ? 1 : 0;\n const bDynamic = b.urlPath.includes(':') ? 1 : 0;\n return aDynamic - bDynamic || a.urlPath.localeCompare(b.urlPath);\n });\n}\n\nexport { filePathToUrlPath };\n","import type { Application, RequestHandler } from 'express';\nimport type { RouteEntry } from '../types.js';\n\nconst HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const;\ntype HttpMethod = (typeof HTTP_METHODS)[number];\n\nfunction asyncWrap(handler: RequestHandler): RequestHandler {\n return (req, res, next) => {\n Promise.resolve(handler(req, res, next)).catch(next);\n };\n}\n\nexport async function mountRoutes(app: Application, routes: RouteEntry[]): Promise<void> {\n for (const route of routes) {\n const mod = (await import(route.filePath)) as Record<string, unknown>;\n const routeMiddlewares: RequestHandler[] = Array.isArray(mod['middlewares'])\n ? (mod['middlewares'] as RequestHandler[])\n : [];\n\n const implemented: HttpMethod[] = [];\n const unimplemented: HttpMethod[] = [];\n\n for (const method of HTTP_METHODS) {\n if (typeof mod[method] === 'function') {\n implemented.push(method);\n app[method.toLowerCase() as Lowercase<HttpMethod>](\n route.urlPath,\n ...routeMiddlewares,\n asyncWrap(mod[method] as RequestHandler),\n );\n } else {\n unimplemented.push(method);\n }\n }\n\n if (implemented.length > 0 && unimplemented.length > 0) {\n const allowHeader = implemented.join(', ');\n app.all(route.urlPath, (req, res, next) => {\n if ((unimplemented as string[]).includes(req.method)) {\n res.set('Allow', allowHeader).status(405).json({ error: 'Method Not Allowed' });\n } else {\n next();\n }\n });\n }\n }\n}\n\nexport { asyncWrap };\n","import cluster from 'node:cluster';\nimport os from 'node:os';\n\ninterface ClusterOptions {\n workers?: number;\n onWorkerReady?: ((id: number) => void) | undefined;\n onWorkerCrash?: ((id: number, code: number) => void) | undefined;\n}\n\nexport function runMaster(options: ClusterOptions = {}): void {\n const count = options.workers ?? os.cpus().length;\n\n console.log(`[EFC] Primary ${process.pid} starting ${count} workers`);\n\n for (let i = 0; i < count; i++) {\n cluster.fork();\n }\n\n cluster.on('online', (worker) => {\n options.onWorkerReady?.(worker.id);\n });\n\n cluster.on('exit', (worker, code, signal) => {\n const exitCode = code ?? (signal ? -1 : 0);\n console.warn(`[EFC] Worker ${worker.id} exited (code=${exitCode}), respawning…`);\n options.onWorkerCrash?.(worker.id, exitCode);\n cluster.fork();\n });\n}\n\nexport { cluster };\n","import type { RequestHandler, Response } from 'express';\nimport jwt from 'jsonwebtoken';\nimport type { AuthStrategy } from '../types.js';\n\ninterface AuthConfig {\n secret: string;\n strategy: AuthStrategy;\n expiresIn: string;\n cookieDomain?: string | undefined;\n}\n\nlet _config: AuthConfig | null = null;\n\nexport function configureAuth(config: AuthConfig): void {\n _config = config;\n}\n\nfunction getConfig(): AuthConfig {\n if (!_config) throw new Error('[EFC] Auth not configured — pass jwtSecret to ignite()');\n return _config;\n}\n\nexport function issueToken(res: Response, payload: Record<string, unknown>): void {\n const { secret, expiresIn, cookieDomain } = getConfig();\n // expiresIn is a plain string (e.g. '7d'); cast satisfies jwt's branded StringValue\n const token = jwt.sign(payload, secret, { expiresIn: expiresIn as jwt.SignOptions['expiresIn'] & string });\n res.cookie('efc_token', token, {\n httpOnly: true,\n secure: process.env['NODE_ENV'] === 'production',\n sameSite: 'strict',\n ...(cookieDomain !== undefined && { domain: cookieDomain }),\n });\n}\n\nexport function revokeToken(res: Response): void {\n res.clearCookie('efc_token');\n}\n\nexport function signToken(payload: Record<string, unknown>): string {\n const { secret, expiresIn } = getConfig();\n return jwt.sign(payload, secret, { expiresIn: expiresIn as jwt.SignOptions['expiresIn'] & string });\n}\n\nexport const requireAuth: RequestHandler = (req, res, next) => {\n const { secret, strategy } = getConfig();\n\n try {\n let token: string | undefined;\n\n if (strategy === 'http-only') {\n const cookies = (req as typeof req & { cookies: Record<string, string> }).cookies;\n token = cookies['efc_token'];\n } else {\n const auth = req.headers['authorization'];\n if (typeof auth === 'string' && auth.startsWith('Bearer ')) {\n token = auth.slice(7);\n }\n }\n\n if (!token) {\n res.status(401).json({ error: 'Unauthorized' });\n return;\n }\n\n const decoded = jwt.verify(token, secret);\n (req as typeof req & { user: unknown }).user = decoded;\n next();\n } catch {\n res.status(401).json({ error: 'Unauthorized' });\n }\n};\n","// Wraps mongoose connection for Pre-Flight. Throws clearly if mongoose isn't installed.\n\nlet mongoose: typeof import('mongoose');\n\nasync function loadMongoose(): Promise<typeof import('mongoose')> {\n if (mongoose) return mongoose;\n try {\n mongoose = await import('mongoose');\n return mongoose;\n } catch {\n throw new Error(\n '[EFC] mongoose is not installed. Run: npm install mongoose',\n );\n }\n}\n\nexport async function connectMongo(url: string): Promise<import('mongoose').Connection> {\n const mg = await loadMongoose();\n await mg.connect(url);\n console.log('[EFC] MongoDB connected');\n return mg.connection;\n}\n","import type { ModelSchema, ModelCRUD } from '../types.js';\n\ntype AnyRecord = Record<string, unknown> & { id: string };\n\nfunction normalise(doc: Record<string, unknown>): AnyRecord {\n const id = doc['_id'] ? String(doc['_id']) : '';\n return { ...doc, id } as AnyRecord;\n}\n\nasync function getModel(name: string, schemaObj: Record<string, unknown>): Promise<import('mongoose').Model<Record<string, unknown>>> {\n let mg: typeof import('mongoose');\n try {\n mg = await import('mongoose');\n } catch {\n throw new Error('[EFC] mongoose is not installed. Run: npm install mongoose');\n }\n\n if (mg.models[name]) return mg.models[name] as import('mongoose').Model<Record<string, unknown>>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const schema = new mg.Schema(schemaObj as any, { timestamps: true });\n return mg.model<Record<string, unknown>>(name, schema);\n}\n\nfunction buildMongooseSchema(schema: ModelSchema): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, def] of Object.entries(schema)) {\n const entry: Record<string, unknown> = {};\n switch (def.type) {\n case 'string': entry['type'] = String; break;\n case 'number': entry['type'] = Number; break;\n case 'boolean': entry['type'] = Boolean; break;\n case 'date': entry['type'] = Date; break;\n case 'object': entry['type'] = Object; break;\n case 'array': entry['type'] = Array; break;\n }\n if (def.required !== undefined) entry['required'] = def.required;\n if (def.unique !== undefined) entry['unique'] = def.unique;\n if (def.default !== undefined) entry['default'] = def.default;\n out[key] = entry;\n }\n return out;\n}\n\nexport function defineModel<T extends Record<string, unknown>>(\n name: string,\n schema: ModelSchema,\n): ModelCRUD<T> {\n const mongooseSchema = buildMongooseSchema(schema);\n\n return {\n async find(filter = {}) {\n const M = await getModel(name, mongooseSchema);\n const docs = await M.find(filter as Record<string, unknown>).lean();\n return (docs as Record<string, unknown>[]).map(normalise) as (T & { id: string })[];\n },\n\n async findById(id) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findById(id).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async findOne(filter) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findOne(filter as Record<string, unknown>).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async create(data) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.create(data);\n return normalise(doc.toObject() as Record<string, unknown>) as T & { id: string };\n },\n\n async update(id, data) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findByIdAndUpdate(id, data, { new: true }).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async delete(id) {\n const M = await getModel(name, mongooseSchema);\n await M.findByIdAndDelete(id);\n },\n\n async count(filter = {}) {\n const M = await getModel(name, mongooseSchema);\n return M.countDocuments(filter as Record<string, unknown>);\n },\n };\n}\n","// Thread-local DB client populated during Pre-Flight.\n// The Proxy ensures callers import `db` once and never null-check it —\n// it throws with a helpful message if accessed before Pre-Flight completes.\n\ntype AnyClient = Record<string, unknown>;\n\nlet _client: AnyClient | null = null;\n\nexport function setDbClient(client: AnyClient): void {\n _client = client;\n}\n\nexport function getDbClient(): AnyClient {\n if (!_client) {\n throw new Error('[EFC] db not ready — accessed before Pre-Flight completed');\n }\n return _client;\n}\n\nexport const db: AnyClient = new Proxy({} as AnyClient, {\n get(_target, prop: string) {\n return getDbClient()[prop];\n },\n set(_target, prop: string, value: unknown) {\n getDbClient()[prop] = value;\n return true;\n },\n});\n\nexport { connectMongo } from './mongo.js';\nexport { defineModel } from './model.js';\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { registerTask } from './index.js';\nimport type { TaskDefinition } from '../types.js';\n\nexport async function scanTasks(tasksDir: string): Promise<void> {\n if (!fs.existsSync(tasksDir)) return;\n\n const files = fs.readdirSync(tasksDir).filter((f) => /\\.(ts|js|mts|mjs)$/.test(f));\n\n for (const file of files) {\n const filePath = path.join(tasksDir, file);\n const taskName = path.basename(file, path.extname(file));\n\n try {\n const mod = await import(filePath) as Record<string, unknown>;\n const def = mod['default'] as TaskDefinition | undefined;\n\n if (!def || typeof def.handler !== 'function') {\n console.warn(`[EFC] Task file ${file} has no valid default export — skipping`);\n continue;\n }\n\n registerTask(taskName, { ...def, filePath });\n console.log(`[EFC] Registered task: ${taskName}`);\n } catch (err) {\n console.warn(`[EFC] Failed to load task ${file}:`, err);\n }\n }\n}\n","import type { TaskDefinition, TaskOptions } from '../types.js';\n\nexport const taskRegistry = new Map<string, TaskDefinition>();\n\ntype HandlerFn<T> = (payload: T) => Promise<void>;\ntype DefineTaskOverload = {\n <T>(handler: HandlerFn<T>): TaskDefinition<T>;\n <T>(options: TaskOptions, handler: HandlerFn<T>): TaskDefinition<T>;\n};\n\nexport const defineTask: DefineTaskOverload = <T>(\n handlerOrOptions: HandlerFn<T> | TaskOptions,\n maybeHandler?: HandlerFn<T>,\n): TaskDefinition<T> => {\n let options: TaskOptions = {};\n let handler: HandlerFn<T>;\n\n if (typeof handlerOrOptions === 'function') {\n handler = handlerOrOptions;\n } else {\n options = handlerOrOptions;\n if (!maybeHandler) throw new Error('[EFC] defineTask: handler function is required');\n handler = maybeHandler;\n }\n\n return {\n handler: handler as (payload: unknown) => Promise<void>,\n options,\n name: '',\n };\n};\n\ntype EnqueueImpl = (name: string, payload: unknown) => Promise<void>;\nlet _impl: EnqueueImpl | null = null;\n\nexport function setEnqueueImpl(impl: EnqueueImpl): void {\n _impl = impl;\n}\n\nexport async function enqueue<T>(name: string, payload: T): Promise<void> {\n if (!_impl) {\n throw new Error(\n `[EFC] Task queue not initialised. Set tasks.backend in ignite() config.`,\n );\n }\n return _impl(name, payload as unknown);\n}\n\nexport function registerTask(name: string, def: TaskDefinition): void {\n taskRegistry.set(name, { ...def, name });\n}\n","import { Worker, workerData, parentPort, isMainThread } from 'node:worker_threads';\nimport type { TaskDefinition } from '../types.js';\n\n// ── Worker-thread side ────────────────────────────────────────────────────────\nif (!isMainThread) {\n const { handlerPath, payload } = workerData as { handlerPath: string; payload: unknown };\n\n (async () => {\n try {\n const mod = await import(handlerPath) as Record<string, unknown>;\n const def = mod['default'] as TaskDefinition | undefined;\n if (!def || typeof def.handler !== 'function') {\n throw new Error(`[EFC] Thread runner: no valid task export in ${handlerPath}`);\n }\n await def.handler(payload);\n parentPort?.postMessage({ ok: true });\n } catch (err) {\n parentPort?.postMessage({ ok: false, error: String(err) });\n }\n })();\n}\n\n// ── Main-thread side ──────────────────────────────────────────────────────────\nexport function runInThread(handlerPath: string, payload: unknown): Promise<void> {\n return new Promise((resolve, reject) => {\n const worker = new Worker(new URL('./thread-runner.js', import.meta.url), {\n workerData: { handlerPath, payload },\n });\n\n worker.on('message', (msg: { ok: boolean; error?: string }) => {\n if (msg.ok) resolve();\n else reject(new Error(msg.error ?? 'Thread task failed'));\n });\n\n worker.on('error', reject);\n worker.on('exit', (code) => {\n if (code !== 0) reject(new Error(`Thread worker exited with code ${code}`));\n });\n });\n}\n","import type { Job } from 'bullmq';\nimport { taskRegistry, setEnqueueImpl } from './index.js';\nimport { runInThread } from './thread-runner.js';\n\ninterface BullMQOpts {\n redisUrl: string;\n concurrency: number;\n}\n\nexport async function initBullMQ(opts: BullMQOpts): Promise<void> {\n let bullmq: typeof import('bullmq');\n try {\n bullmq = await import('bullmq');\n } catch {\n throw new Error('[EFC] bullmq is not installed. Run: npm install bullmq');\n }\n\n const connection = parseRedisUrl(opts.redisUrl);\n const queue = new bullmq.Queue('efc', { connection });\n\n const worker = new bullmq.Worker(\n 'efc',\n async (job: Job) => {\n const def = taskRegistry.get(job.name);\n if (!def) {\n throw new Error(`[EFC] No task registered for name: ${job.name}`);\n }\n\n if (def.options.thread && def.filePath) {\n await runInThread(def.filePath, job.data);\n } else {\n await def.handler(job.data);\n }\n },\n { connection, concurrency: opts.concurrency },\n );\n\n worker.on('failed', (job: Job | undefined, err: Error) => {\n console.error(`[EFC] Task ${job?.name ?? 'unknown'} failed:`, err.message);\n });\n\n worker.on('completed', (job: Job) => {\n console.log(`[EFC] Task ${job.name} completed (id=${job.id})`);\n });\n\n setEnqueueImpl(async (name, payload) => {\n const def = taskRegistry.get(name);\n if (!def) {\n throw new Error(`[EFC] Cannot enqueue unknown task: \"${name}\". Is the task file in tasksDir?`);\n }\n\n await queue.add(name, payload, {\n attempts: def.options.retries ?? 3,\n backoff: {\n type: def.options.backoff ?? 'exponential',\n delay: 1000,\n },\n });\n });\n\n console.log('[EFC] BullMQ backend initialised');\n}\n\nfunction parseRedisUrl(url: string): { host: string; port: number; password?: string } {\n try {\n const u = new URL(url);\n const result: { host: string; port: number; password?: string } = {\n host: u.hostname || 'localhost',\n port: parseInt(u.port || '6379', 10),\n };\n if (u.password) result.password = u.password;\n return result;\n } catch {\n return { host: 'localhost', port: 6379 };\n }\n}\n","export class HttpError extends Error {\n readonly statusCode: number;\n\n constructor(statusCode: number, message: string) {\n super(message);\n this.name = 'HttpError';\n this.statusCode = statusCode;\n Error.captureStackTrace(this, HttpError);\n }\n}\n","import type { RequestHandler } from 'express';\n\nexport function compose(...handlers: RequestHandler[]): RequestHandler {\n return (req, res, next) => {\n let index = 0;\n\n function dispatch(i: number): void {\n if (i >= handlers.length) {\n next();\n return;\n }\n const handler = handlers[i];\n if (!handler) {\n next();\n return;\n }\n try {\n Promise.resolve(handler(req, res, () => dispatch(i + 1))).catch(next);\n } catch (err) {\n next(err);\n }\n }\n\n dispatch(index);\n };\n}\n"],"mappings":"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","names":["cluster","os","fs","path","fs","path","cluster","os"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/router/scan.ts","../src/router/mount.ts","../src/cluster/index.ts","../src/auth/index.ts","../src/db/mongo.ts","../src/db/model.ts","../src/db/index.ts","../src/tasks/scanner.ts","../src/tasks/index.ts","../src/tasks/thread-runner.ts","../src/tasks/bullmq-backend.ts","../src/errors.ts","../src/compose.ts"],"sourcesContent":["import http from 'node:http';\nimport express from 'express';\nimport cors from 'cors';\nimport cookieParser from 'cookie-parser';\nimport cluster from 'node:cluster';\nimport os from 'node:os';\nimport dotenv from 'dotenv';\nimport type { EFCConfig } from './types.js';\nimport { scanDir } from './router/scan.js';\nimport { mountRoutes } from './router/mount.js';\nimport { runMaster, shutdownMaster } from './cluster/index.js';\nimport { configureAuth } from './auth/index.js';\nimport { HttpError } from './errors.js';\nimport { connectMongo } from './db/mongo.js';\nimport { setDbClient } from './db/index.js';\nimport { scanTasks } from './tasks/scanner.js';\nimport { initBullMQ } from './tasks/bullmq-backend.js';\n\n// Load .env only outside production — production envs are injected by the platform\nif (process.env['NODE_ENV'] !== 'production') {\n dotenv.config();\n}\n\nfunction detectDatabase(url?: string): 'mongodb' | 'postgresql' | undefined {\n if (!url) return undefined;\n if (url.startsWith('mongodb')) return 'mongodb';\n if (url.startsWith('postgres')) return 'postgresql';\n return undefined;\n}\n\nexport async function ignite(config: EFCConfig): Promise<http.Server | undefined> {\n const {\n port: _port,\n workers,\n apiDir,\n globalMiddlewares = [],\n onWorkerReady,\n onWorkerCrash,\n onError,\n } = config;\n\n // All runtime values fall back to environment variables\n const envPort = process.env['PORT'] != null ? Number(process.env['PORT']) : NaN;\n const port = (_port != null && !Number.isNaN(_port)) ? _port\n : !Number.isNaN(envPort) ? envPort\n : 3000;\n const databaseUrl = config.databaseUrl ?? process.env['DATABASE_URL'];\n const database = config.database ?? detectDatabase(databaseUrl);\n const jwtSecret = config.jwtSecret ?? process.env['JWT_SECRET'];\n const authStrategy = config.authStrategy ?? 'http-only';\n const clusterEnabled = config.cluster ?? process.env['NODE_ENV'] === 'production';\n\n if (clusterEnabled && cluster.isPrimary) {\n runMaster({\n workers: workers ?? os.cpus().length,\n ...(onWorkerReady !== undefined && { onWorkerReady }),\n ...(onWorkerCrash !== undefined && { onWorkerCrash }),\n });\n return;\n }\n\n const app = express();\n\n const corsOption = config.cors ?? true;\n if (corsOption !== false) {\n const envOrigins = process.env['CORS_ORIGINS'];\n let origin: string | string[] | boolean;\n if (envOrigins) {\n const list = envOrigins.split(',').map((o) => o.trim()).filter(Boolean);\n origin = list; // always array so cors validates against the request Origin\n } else if (typeof corsOption === 'object' && corsOption.origin !== undefined) {\n origin = corsOption.origin;\n } else {\n origin = true;\n }\n const corsOpts = typeof corsOption === 'object' ? { ...corsOption, origin } : { origin };\n app.use(cors(corsOpts));\n }\n\n app.use(express.json());\n app.use(express.urlencoded({ extended: true }));\n app.use(cookieParser());\n\n for (const mw of globalMiddlewares) {\n app.use(mw);\n }\n\n // Pre-Flight step 1: Connect database\n if (database === 'mongodb' && databaseUrl) {\n const conn = await connectMongo(databaseUrl);\n setDbClient(conn as unknown as Record<string, unknown>);\n }\n\n // Pre-Flight step 2: Configure auth\n if (jwtSecret) {\n const cookieDomain = process.env['COOKIE_DOMAIN'];\n configureAuth({\n secret: jwtSecret,\n strategy: authStrategy,\n expiresIn: process.env['JWT_EXPIRES_IN'] ?? '7d',\n ...(cookieDomain !== undefined && { cookieDomain }),\n });\n }\n\n // Pre-Flight step 3: Scan and register tasks\n if (config.tasksDir) {\n await scanTasks(config.tasksDir);\n }\n\n // Pre-Flight step 4: Start task queue backend\n if (config.tasks) {\n if (config.tasks.backend === 'bullmq') {\n await initBullMQ({\n redisUrl: config.tasks.redisUrl ?? 'redis://localhost:6379',\n concurrency: config.tasks.concurrency ?? 5,\n });\n }\n }\n\n // Pre-Flight step 5: Scan routes and mount\n const routes = scanDir(apiDir);\n await mountRoutes(app, routes);\n\n if (onError) {\n app.use(onError);\n } else {\n app.use(\n (\n err: unknown,\n _req: express.Request,\n res: express.Response,\n _next: express.NextFunction,\n ) => {\n const asHttp = err instanceof Error && 'statusCode' in err\n ? (err as HttpError) : null;\n if (asHttp) {\n res.status(asHttp.statusCode).json({ error: asHttp.message, statusCode: asHttp.statusCode });\n } else {\n console.error('[EFC] Unhandled error:', err);\n res.status(500).json({ error: 'Internal Server Error', statusCode: 500 });\n }\n },\n );\n }\n\n return new Promise<http.Server>((resolve, reject) => {\n const server = app.listen(port);\n\n server.once('listening', () => {\n const wid = (cluster.worker as { id: number } | undefined)?.id ?? 'primary';\n const addr = server.address() as { port: number } | null;\n console.log(`[EFC] Worker ${wid} listening on :${addr?.port ?? port}`);\n resolve(server);\n });\n\n server.once('error', reject);\n });\n}\n\nexport function gracefulShutdown(server: http.Server | undefined, timeoutMs = 10_000): void {\n const shutdown = (signal: string) => {\n console.log(`[EFC] ${signal} received — closing server gracefully…`);\n if (server) {\n server.closeIdleConnections();\n server.close(() => {\n console.log('[EFC] Server closed');\n process.exit(0);\n });\n setTimeout(() => {\n console.error('[EFC] Forced exit after timeout');\n process.exit(1);\n }, timeoutMs).unref();\n } else {\n shutdownMaster();\n }\n };\n process.once('SIGTERM', () => shutdown('SIGTERM'));\n process.once('SIGINT', () => shutdown('SIGINT'));\n}\n\nexport { HttpError } from './errors.js';\nexport { compose } from './compose.js';\nexport { db, setDbClient, getDbClient, defineModel } from './db/index.js';\nexport { scanDir } from './router/scan.js';\nexport type {\n EFCConfig,\n CorsConfig,\n RouteEntry,\n TaskOptions,\n TaskDefinition,\n DatabaseEngine,\n AuthStrategy,\n ModelSchema,\n ModelCRUD,\n} from './types.js';\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { RouteEntry } from '../types.js';\n\nconst ROUTE_FILE_RE = /\\.(ts|js|mts|mjs|cts|cjs)$/;\nconst DYNAMIC_SEGMENT_RE = /\\[([^\\]]+)\\]/g;\n\nfunction filePathToUrlPath(relativePath: string): string {\n let p = relativePath.replace(ROUTE_FILE_RE, '');\n // index files map to parent path\n p = p.replace(/\\/index$/, '');\n // [param] → :param\n p = p.replace(DYNAMIC_SEGMENT_RE, ':$1');\n return p === '' ? '/' : p.startsWith('/') ? p : `/${p}`;\n}\n\nfunction extractParams(relativePath: string): string[] {\n const params: string[] = [];\n let match: RegExpExecArray | null;\n const re = new RegExp(DYNAMIC_SEGMENT_RE.source, 'g');\n while ((match = re.exec(relativePath)) !== null) {\n if (match[1]) params.push(match[1]);\n }\n return params;\n}\n\nexport function scanDir(dir: string, base: string = dir): RouteEntry[] {\n if (!fs.existsSync(dir)) return [];\n\n const entries: RouteEntry[] = [];\n const items = fs.readdirSync(dir, { withFileTypes: true });\n\n for (const item of items) {\n const fullPath = path.join(dir, item.name);\n if (item.isDirectory()) {\n entries.push(...scanDir(fullPath, base));\n } else if (ROUTE_FILE_RE.test(item.name)) {\n const relative = '/' + path.relative(base, fullPath).replace(/\\\\/g, '/');\n entries.push({\n urlPath: filePathToUrlPath(relative),\n filePath: fullPath,\n params: extractParams(relative),\n });\n }\n }\n\n // Sort: static routes before dynamic ones at each segment level\n return entries.sort((a, b) => {\n const aDynamic = a.urlPath.includes(':') ? 1 : 0;\n const bDynamic = b.urlPath.includes(':') ? 1 : 0;\n return aDynamic - bDynamic || a.urlPath.localeCompare(b.urlPath);\n });\n}\n\nexport { filePathToUrlPath };\n","import type { Application, RequestHandler } from 'express';\nimport type { RouteEntry } from '../types.js';\n\nconst HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const;\ntype HttpMethod = (typeof HTTP_METHODS)[number];\n\nfunction asyncWrap(handler: RequestHandler): RequestHandler {\n return (req, res, next) => {\n Promise.resolve(handler(req, res, next)).catch(next);\n };\n}\n\nexport async function mountRoutes(app: Application, routes: RouteEntry[]): Promise<void> {\n for (const route of routes) {\n const mod = (await import(route.filePath)) as Record<string, unknown>;\n const routeMiddlewares: RequestHandler[] = Array.isArray(mod['middlewares'])\n ? (mod['middlewares'] as RequestHandler[])\n : [];\n\n const implemented: HttpMethod[] = [];\n const unimplemented: HttpMethod[] = [];\n\n for (const method of HTTP_METHODS) {\n if (typeof mod[method] === 'function') {\n implemented.push(method);\n app[method.toLowerCase() as Lowercase<HttpMethod>](\n route.urlPath,\n ...routeMiddlewares,\n asyncWrap(mod[method] as RequestHandler),\n );\n } else {\n unimplemented.push(method);\n }\n }\n\n if (implemented.length > 0 && unimplemented.length > 0) {\n const allowHeader = implemented.join(', ');\n app.all(route.urlPath, (req, res, next) => {\n if ((unimplemented as string[]).includes(req.method)) {\n res.set('Allow', allowHeader).status(405).json({ error: 'Method Not Allowed' });\n } else {\n next();\n }\n });\n }\n }\n}\n\nexport { asyncWrap };\n","import cluster from 'node:cluster';\nimport os from 'node:os';\n\ninterface ClusterOptions {\n workers?: number;\n onWorkerReady?: ((id: number) => void) | undefined;\n onWorkerCrash?: ((id: number, code: number) => void) | undefined;\n}\n\nlet isShuttingDown = false;\n\nexport function shutdownMaster(): void {\n isShuttingDown = true;\n console.log('[EFC] Cluster master shutting down, waiting for workers…');\n}\n\nexport function runMaster(options: ClusterOptions = {}): void {\n const count = options.workers ?? os.cpus().length;\n\n console.log(`[EFC] Primary ${process.pid} starting ${count} workers`);\n\n for (let i = 0; i < count; i++) {\n cluster.fork();\n }\n\n cluster.on('online', (worker) => {\n options.onWorkerReady?.(worker.id);\n });\n\n cluster.on('exit', (worker, code, signal) => {\n const exitCode = code ?? (signal ? -1 : 0);\n if (isShuttingDown) {\n console.log(`[EFC] Worker ${worker.id} gracefully exited`);\n if (Object.keys(cluster.workers ?? {}).length === 0) {\n console.log('[EFC] All workers exited. Primary exiting.');\n process.exit(0);\n }\n return;\n }\n \n console.warn(`[EFC] Worker ${worker.id} exited (code=${exitCode}), respawning…`);\n options.onWorkerCrash?.(worker.id, exitCode);\n cluster.fork();\n });\n}\n\nexport { cluster };\n","import type { RequestHandler, Response } from 'express';\nimport jwt from 'jsonwebtoken';\nimport type { AuthStrategy } from '../types.js';\n\ninterface AuthConfig {\n secret: string;\n strategy: AuthStrategy;\n expiresIn: string;\n cookieDomain?: string | undefined;\n}\n\nlet _config: AuthConfig | null = null;\n\nexport function configureAuth(config: AuthConfig): void {\n _config = config;\n}\n\nfunction getConfig(): AuthConfig {\n if (!_config) throw new Error('[EFC] Auth not configured — pass jwtSecret to ignite()');\n return _config;\n}\n\nexport function issueToken(res: Response, payload: Record<string, unknown>): void {\n const { secret, expiresIn, cookieDomain } = getConfig();\n // expiresIn is a plain string (e.g. '7d'); cast satisfies jwt's branded StringValue\n const token = jwt.sign(payload, secret, { expiresIn: expiresIn as jwt.SignOptions['expiresIn'] & string });\n res.cookie('efc_token', token, {\n httpOnly: true,\n secure: process.env['NODE_ENV'] === 'production',\n sameSite: 'strict',\n ...(cookieDomain !== undefined && { domain: cookieDomain }),\n });\n}\n\nexport function revokeToken(res: Response): void {\n res.clearCookie('efc_token');\n}\n\nexport function signToken(payload: Record<string, unknown>): string {\n const { secret, expiresIn } = getConfig();\n return jwt.sign(payload, secret, { expiresIn: expiresIn as jwt.SignOptions['expiresIn'] & string });\n}\n\nexport const requireAuth: RequestHandler = (req, res, next) => {\n const { secret, strategy } = getConfig();\n\n try {\n let token: string | undefined;\n\n if (strategy === 'http-only') {\n const cookies = (req as typeof req & { cookies: Record<string, string> }).cookies;\n token = cookies['efc_token'];\n } else {\n const auth = req.headers['authorization'];\n if (typeof auth === 'string' && auth.startsWith('Bearer ')) {\n token = auth.slice(7);\n }\n }\n\n if (!token) {\n res.status(401).json({ error: 'Unauthorized' });\n return;\n }\n\n const decoded = jwt.verify(token, secret);\n (req as typeof req & { user: unknown }).user = decoded;\n next();\n } catch {\n res.status(401).json({ error: 'Unauthorized' });\n }\n};\n","// Wraps mongoose connection for Pre-Flight. Throws clearly if mongoose isn't installed.\n\nlet mongoose: typeof import('mongoose');\n\nasync function loadMongoose(): Promise<typeof import('mongoose')> {\n if (mongoose) return mongoose;\n try {\n mongoose = await import('mongoose');\n return mongoose;\n } catch {\n throw new Error(\n '[EFC] mongoose is not installed. Run: npm install mongoose',\n );\n }\n}\n\nexport async function connectMongo(url: string): Promise<import('mongoose').Connection> {\n const mg = await loadMongoose();\n await mg.connect(url);\n console.log('[EFC] MongoDB connected');\n return mg.connection;\n}\n","import type { ModelSchema, ModelCRUD } from '../types.js';\n\ntype AnyRecord = Record<string, unknown> & { id: string };\n\nfunction normalise(doc: Record<string, unknown>): AnyRecord {\n const id = doc['_id'] ? String(doc['_id']) : '';\n return { ...doc, id } as AnyRecord;\n}\n\nasync function getModel(name: string, schemaObj: Record<string, unknown>): Promise<import('mongoose').Model<Record<string, unknown>>> {\n let mg: typeof import('mongoose');\n try {\n mg = await import('mongoose');\n } catch {\n throw new Error('[EFC] mongoose is not installed. Run: npm install mongoose');\n }\n\n if (mg.models[name]) return mg.models[name] as import('mongoose').Model<Record<string, unknown>>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const schema = new mg.Schema(schemaObj as any, { timestamps: true });\n return mg.model<Record<string, unknown>>(name, schema);\n}\n\nfunction buildMongooseSchema(schema: ModelSchema): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, def] of Object.entries(schema)) {\n const entry: Record<string, unknown> = {};\n switch (def.type) {\n case 'string': entry['type'] = String; break;\n case 'number': entry['type'] = Number; break;\n case 'boolean': entry['type'] = Boolean; break;\n case 'date': entry['type'] = Date; break;\n case 'object': entry['type'] = Object; break;\n case 'array': entry['type'] = Array; break;\n }\n if (def.required !== undefined) entry['required'] = def.required;\n if (def.unique !== undefined) entry['unique'] = def.unique;\n if (def.default !== undefined) entry['default'] = def.default;\n out[key] = entry;\n }\n return out;\n}\n\nexport function defineModel<T extends Record<string, unknown>>(\n name: string,\n schema: ModelSchema,\n): ModelCRUD<T> {\n const mongooseSchema = buildMongooseSchema(schema);\n\n return {\n async find(filter = {}) {\n const M = await getModel(name, mongooseSchema);\n const docs = await M.find(filter as Record<string, unknown>).lean();\n return (docs as Record<string, unknown>[]).map(normalise) as (T & { id: string })[];\n },\n\n async findById(id) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findById(id).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async findOne(filter) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findOne(filter as Record<string, unknown>).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async create(data) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.create(data);\n return normalise(doc.toObject() as Record<string, unknown>) as T & { id: string };\n },\n\n async update(id, data) {\n const M = await getModel(name, mongooseSchema);\n const doc = await M.findByIdAndUpdate(id, data, { new: true }).lean();\n if (!doc) return null;\n return normalise(doc as Record<string, unknown>) as T & { id: string };\n },\n\n async delete(id) {\n const M = await getModel(name, mongooseSchema);\n await M.findByIdAndDelete(id);\n },\n\n async count(filter = {}) {\n const M = await getModel(name, mongooseSchema);\n return M.countDocuments(filter as Record<string, unknown>);\n },\n };\n}\n","// Thread-local DB client populated during Pre-Flight.\n// The Proxy ensures callers import `db` once and never null-check it —\n// it throws with a helpful message if accessed before Pre-Flight completes.\n\ntype AnyClient = Record<string, unknown>;\n\nlet _client: AnyClient | null = null;\n\nexport function setDbClient(client: AnyClient): void {\n _client = client;\n}\n\nexport function getDbClient(): AnyClient {\n if (!_client) {\n throw new Error('[EFC] db not ready — accessed before Pre-Flight completed');\n }\n return _client;\n}\n\nexport const db: AnyClient = new Proxy({} as AnyClient, {\n get(_target, prop: string) {\n return getDbClient()[prop];\n },\n set(_target, prop: string, value: unknown) {\n getDbClient()[prop] = value;\n return true;\n },\n});\n\nexport { connectMongo } from './mongo.js';\nexport { defineModel } from './model.js';\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { registerTask } from './index.js';\nimport type { TaskDefinition } from '../types.js';\n\nexport async function scanTasks(tasksDir: string): Promise<void> {\n if (!fs.existsSync(tasksDir)) return;\n\n const files = fs.readdirSync(tasksDir).filter((f) => /\\.(ts|js|mts|mjs)$/.test(f));\n\n for (const file of files) {\n const filePath = path.join(tasksDir, file);\n const taskName = path.basename(file, path.extname(file));\n\n try {\n const mod = await import(filePath) as Record<string, unknown>;\n const def = mod['default'] as TaskDefinition | undefined;\n\n if (!def || typeof def.handler !== 'function') {\n console.warn(`[EFC] Task file ${file} has no valid default export — skipping`);\n continue;\n }\n\n registerTask(taskName, { ...def, filePath });\n console.log(`[EFC] Registered task: ${taskName}`);\n } catch (err) {\n console.warn(`[EFC] Failed to load task ${file}:`, err);\n }\n }\n}\n","import type { TaskDefinition, TaskOptions } from '../types.js';\n\nexport const taskRegistry = new Map<string, TaskDefinition>();\n\ntype HandlerFn<T> = (payload: T) => Promise<void>;\ntype DefineTaskOverload = {\n <T>(handler: HandlerFn<T>): TaskDefinition<T>;\n <T>(options: TaskOptions, handler: HandlerFn<T>): TaskDefinition<T>;\n};\n\nexport const defineTask: DefineTaskOverload = <T>(\n handlerOrOptions: HandlerFn<T> | TaskOptions,\n maybeHandler?: HandlerFn<T>,\n): TaskDefinition<T> => {\n let options: TaskOptions = {};\n let handler: HandlerFn<T>;\n\n if (typeof handlerOrOptions === 'function') {\n handler = handlerOrOptions;\n } else {\n options = handlerOrOptions;\n if (!maybeHandler) throw new Error('[EFC] defineTask: handler function is required');\n handler = maybeHandler;\n }\n\n return {\n handler: handler as (payload: unknown) => Promise<void>,\n options,\n name: '',\n };\n};\n\ntype EnqueueImpl = (name: string, payload: unknown) => Promise<void>;\nlet _impl: EnqueueImpl | null = null;\n\nexport function setEnqueueImpl(impl: EnqueueImpl): void {\n _impl = impl;\n}\n\nexport async function enqueue<T>(name: string, payload: T): Promise<void> {\n if (!_impl) {\n throw new Error(\n `[EFC] Task queue not initialised. Set tasks.backend in ignite() config.`,\n );\n }\n return _impl(name, payload as unknown);\n}\n\nexport function registerTask(name: string, def: TaskDefinition): void {\n taskRegistry.set(name, { ...def, name });\n}\n","import { Worker, workerData, parentPort, isMainThread } from 'node:worker_threads';\nimport type { TaskDefinition } from '../types.js';\n\n// ── Worker-thread side ────────────────────────────────────────────────────────\nif (!isMainThread) {\n const { handlerPath, payload } = workerData as { handlerPath: string; payload: unknown };\n\n (async () => {\n try {\n const mod = await import(handlerPath) as Record<string, unknown>;\n const def = mod['default'] as TaskDefinition | undefined;\n if (!def || typeof def.handler !== 'function') {\n throw new Error(`[EFC] Thread runner: no valid task export in ${handlerPath}`);\n }\n await def.handler(payload);\n parentPort?.postMessage({ ok: true });\n } catch (err) {\n parentPort?.postMessage({ ok: false, error: String(err) });\n }\n })();\n}\n\n// ── Main-thread side ──────────────────────────────────────────────────────────\nexport function runInThread(handlerPath: string, payload: unknown): Promise<void> {\n return new Promise((resolve, reject) => {\n const worker = new Worker(new URL('./thread-runner.js', import.meta.url), {\n workerData: { handlerPath, payload },\n });\n\n worker.on('message', (msg: { ok: boolean; error?: string }) => {\n if (msg.ok) resolve();\n else reject(new Error(msg.error ?? 'Thread task failed'));\n });\n\n worker.on('error', reject);\n worker.on('exit', (code) => {\n if (code !== 0) reject(new Error(`Thread worker exited with code ${code}`));\n });\n });\n}\n","import type { Job } from 'bullmq';\nimport { taskRegistry, setEnqueueImpl } from './index.js';\nimport { runInThread } from './thread-runner.js';\n\ninterface BullMQOpts {\n redisUrl: string;\n concurrency: number;\n}\n\nexport async function initBullMQ(opts: BullMQOpts): Promise<void> {\n let bullmq: typeof import('bullmq');\n try {\n bullmq = await import('bullmq');\n } catch {\n throw new Error('[EFC] bullmq is not installed. Run: npm install bullmq');\n }\n\n const connection = parseRedisUrl(opts.redisUrl);\n const queue = new bullmq.Queue('efc', { connection });\n\n const worker = new bullmq.Worker(\n 'efc',\n async (job: Job) => {\n const def = taskRegistry.get(job.name);\n if (!def) {\n throw new Error(`[EFC] No task registered for name: ${job.name}`);\n }\n\n if (def.options.thread && def.filePath) {\n await runInThread(def.filePath, job.data);\n } else {\n await def.handler(job.data);\n }\n },\n { connection, concurrency: opts.concurrency },\n );\n\n worker.on('failed', (job: Job | undefined, err: Error) => {\n console.error(`[EFC] Task ${job?.name ?? 'unknown'} failed:`, err.message);\n });\n\n worker.on('completed', (job: Job) => {\n console.log(`[EFC] Task ${job.name} completed (id=${job.id})`);\n });\n\n setEnqueueImpl(async (name, payload) => {\n const def = taskRegistry.get(name);\n if (!def) {\n throw new Error(`[EFC] Cannot enqueue unknown task: \"${name}\". Is the task file in tasksDir?`);\n }\n\n await queue.add(name, payload, {\n attempts: def.options.retries ?? 3,\n backoff: {\n type: def.options.backoff ?? 'exponential',\n delay: 1000,\n },\n });\n });\n\n console.log('[EFC] BullMQ backend initialised');\n}\n\nfunction parseRedisUrl(url: string): { host: string; port: number; password?: string } {\n try {\n const u = new URL(url);\n const result: { host: string; port: number; password?: string } = {\n host: u.hostname || 'localhost',\n port: parseInt(u.port || '6379', 10),\n };\n if (u.password) result.password = u.password;\n return result;\n } catch {\n return { host: 'localhost', port: 6379 };\n }\n}\n","export class HttpError extends Error {\n readonly statusCode: number;\n\n constructor(statusCode: number, message: string) {\n super(message);\n this.name = 'HttpError';\n this.statusCode = statusCode;\n Error.captureStackTrace(this, HttpError);\n }\n}\n","import type { RequestHandler } from 'express';\n\nexport function compose(...handlers: RequestHandler[]): RequestHandler {\n return (req, res, next) => {\n let index = 0;\n\n function dispatch(i: number): void {\n if (i >= handlers.length) {\n next();\n return;\n }\n const handler = handlers[i];\n if (!handler) {\n next();\n return;\n }\n try {\n Promise.resolve(handler(req, res, () => dispatch(i + 1))).catch(next);\n } catch (err) {\n next(err);\n }\n }\n\n dispatch(index);\n };\n}\n"],"mappings":"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","names":["cluster","os","fs","path","fs","path","cluster","os"]}
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "express-file-cluster",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Enterprise-grade, zero-boilerplate backend framework with file-based routing and multi-core clustering",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "main": "./dist/index.cjs",
8
11
  "module": "./dist/index.js",
9
12
  "types": "./dist/index.d.ts",
package/src/auth/index.ts DELETED
@@ -1,71 +0,0 @@
1
- import type { RequestHandler, Response } from 'express';
2
- import jwt from 'jsonwebtoken';
3
- import type { AuthStrategy } from '../types.js';
4
-
5
- interface AuthConfig {
6
- secret: string;
7
- strategy: AuthStrategy;
8
- expiresIn: string;
9
- cookieDomain?: string | undefined;
10
- }
11
-
12
- let _config: AuthConfig | null = null;
13
-
14
- export function configureAuth(config: AuthConfig): void {
15
- _config = config;
16
- }
17
-
18
- function getConfig(): AuthConfig {
19
- if (!_config) throw new Error('[EFC] Auth not configured — pass jwtSecret to ignite()');
20
- return _config;
21
- }
22
-
23
- export function issueToken(res: Response, payload: Record<string, unknown>): void {
24
- const { secret, expiresIn, cookieDomain } = getConfig();
25
- // expiresIn is a plain string (e.g. '7d'); cast satisfies jwt's branded StringValue
26
- const token = jwt.sign(payload, secret, { expiresIn: expiresIn as jwt.SignOptions['expiresIn'] & string });
27
- res.cookie('efc_token', token, {
28
- httpOnly: true,
29
- secure: process.env['NODE_ENV'] === 'production',
30
- sameSite: 'strict',
31
- ...(cookieDomain !== undefined && { domain: cookieDomain }),
32
- });
33
- }
34
-
35
- export function revokeToken(res: Response): void {
36
- res.clearCookie('efc_token');
37
- }
38
-
39
- export function signToken(payload: Record<string, unknown>): string {
40
- const { secret, expiresIn } = getConfig();
41
- return jwt.sign(payload, secret, { expiresIn: expiresIn as jwt.SignOptions['expiresIn'] & string });
42
- }
43
-
44
- export const requireAuth: RequestHandler = (req, res, next) => {
45
- const { secret, strategy } = getConfig();
46
-
47
- try {
48
- let token: string | undefined;
49
-
50
- if (strategy === 'http-only') {
51
- const cookies = (req as typeof req & { cookies: Record<string, string> }).cookies;
52
- token = cookies['efc_token'];
53
- } else {
54
- const auth = req.headers['authorization'];
55
- if (typeof auth === 'string' && auth.startsWith('Bearer ')) {
56
- token = auth.slice(7);
57
- }
58
- }
59
-
60
- if (!token) {
61
- res.status(401).json({ error: 'Unauthorized' });
62
- return;
63
- }
64
-
65
- const decoded = jwt.verify(token, secret);
66
- (req as typeof req & { user: unknown }).user = decoded;
67
- next();
68
- } catch {
69
- res.status(401).json({ error: 'Unauthorized' });
70
- }
71
- };