express-file-cluster 0.1.2 → 0.1.4

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.
@@ -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/errors.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/compose.ts"],"sourcesContent":["import 'dotenv/config';\nimport express from 'express';\nimport cors from 'cors';\nimport cookieParser from 'cookie-parser';\nimport cluster from 'node:cluster';\nimport os from 'node:os';\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\nexport async function ignite(config: EFCConfig): Promise<void> {\n const {\n port: _port,\n cluster: clusterEnabled = true,\n workers,\n apiDir,\n jwtSecret,\n authStrategy = 'http-only',\n globalMiddlewares = [],\n onWorkerReady,\n onWorkerCrash,\n onError,\n } = config;\n\n const port =\n _port != null && !Number.isNaN(_port)\n ? _port\n : Number(process.env['PORT']) || 3000;\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.length === 1 ? list[0]! : list;\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 (config.database === 'mongodb' && config.databaseUrl) {\n const conn = await connectMongo(config.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 if (err instanceof HttpError) {\n res.status(err.statusCode).json({ error: err.message, statusCode: err.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 await new Promise<void>((resolve) => {\n app.listen(port, () => {\n const wid = (cluster.worker as { id: number } | undefined)?.id ?? 'primary';\n console.log(`[EFC] Worker ${wid} listening on :${port}`);\n resolve();\n });\n });\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","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","// 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","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","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 } 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"]}
package/dist/index.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ import http from 'node:http';
1
2
  import { M as ModelSchema, b as ModelCRUD, R as RouteEntry, E as EFCConfig } from './types-CkpRAA3y.cjs';
2
3
  export { A as AuthStrategy, C as CorsConfig, D as DatabaseEngine, T as TaskDefinition, a as TaskOptions } from './types-CkpRAA3y.cjs';
3
4
  import { RequestHandler } from 'express';
@@ -18,6 +19,7 @@ declare const db: AnyClient;
18
19
 
19
20
  declare function scanDir(dir: string, base?: string): RouteEntry[];
20
21
 
21
- declare function ignite(config: EFCConfig): Promise<void>;
22
+ declare function ignite(config: EFCConfig): Promise<http.Server | undefined>;
23
+ declare function gracefulShutdown(server: http.Server, timeoutMs?: number): void;
22
24
 
23
- export { EFCConfig, HttpError, ModelCRUD, ModelSchema, RouteEntry, compose, db, defineModel, getDbClient, ignite, scanDir, setDbClient };
25
+ export { EFCConfig, HttpError, ModelCRUD, ModelSchema, RouteEntry, compose, db, defineModel, getDbClient, gracefulShutdown, ignite, scanDir, setDbClient };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import http from 'node:http';
1
2
  import { M as ModelSchema, b as ModelCRUD, R as RouteEntry, E as EFCConfig } from './types-CkpRAA3y.js';
2
3
  export { A as AuthStrategy, C as CorsConfig, D as DatabaseEngine, T as TaskDefinition, a as TaskOptions } from './types-CkpRAA3y.js';
3
4
  import { RequestHandler } from 'express';
@@ -18,6 +19,7 @@ declare const db: AnyClient;
18
19
 
19
20
  declare function scanDir(dir: string, base?: string): RouteEntry[];
20
21
 
21
- declare function ignite(config: EFCConfig): Promise<void>;
22
+ declare function ignite(config: EFCConfig): Promise<http.Server | undefined>;
23
+ declare function gracefulShutdown(server: http.Server, timeoutMs?: number): void;
22
24
 
23
- export { EFCConfig, HttpError, ModelCRUD, ModelSchema, RouteEntry, compose, db, defineModel, getDbClient, ignite, scanDir, setDbClient };
25
+ export { EFCConfig, HttpError, ModelCRUD, ModelSchema, RouteEntry, compose, db, defineModel, getDbClient, gracefulShutdown, ignite, scanDir, setDbClient };
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  // src/index.ts
2
- import "dotenv/config";
3
2
  import express from "express";
4
3
  import cors from "cors";
5
4
  import cookieParser from "cookie-parser";
6
5
  import cluster2 from "cluster";
7
6
  import os2 from "os";
7
+ import dotenv from "dotenv";
8
8
 
9
9
  // src/router/scan.ts
10
10
  import fs from "fs";
@@ -115,17 +115,6 @@ function configureAuth(config) {
115
115
  _config = config;
116
116
  }
117
117
 
118
- // src/errors.ts
119
- var HttpError = class _HttpError extends Error {
120
- statusCode;
121
- constructor(statusCode, message) {
122
- super(message);
123
- this.name = "HttpError";
124
- this.statusCode = statusCode;
125
- Error.captureStackTrace(this, _HttpError);
126
- }
127
- };
128
-
129
118
  // src/db/mongo.ts
130
119
  var mongoose;
131
120
  async function loadMongoose() {
@@ -386,6 +375,17 @@ function parseRedisUrl(url) {
386
375
  }
387
376
  }
388
377
 
378
+ // src/errors.ts
379
+ var HttpError = class _HttpError extends Error {
380
+ statusCode;
381
+ constructor(statusCode, message) {
382
+ super(message);
383
+ this.name = "HttpError";
384
+ this.statusCode = statusCode;
385
+ Error.captureStackTrace(this, _HttpError);
386
+ }
387
+ };
388
+
389
389
  // src/compose.ts
390
390
  function compose(...handlers) {
391
391
  return (req, res, next) => {
@@ -411,20 +411,32 @@ function compose(...handlers) {
411
411
  }
412
412
 
413
413
  // src/index.ts
414
+ if (process.env["NODE_ENV"] !== "production") {
415
+ dotenv.config();
416
+ }
417
+ function detectDatabase(url) {
418
+ if (!url) return void 0;
419
+ if (url.startsWith("mongodb")) return "mongodb";
420
+ if (url.startsWith("postgres")) return "postgresql";
421
+ return void 0;
422
+ }
414
423
  async function ignite(config) {
415
424
  const {
416
425
  port: _port,
417
- cluster: clusterEnabled = true,
418
426
  workers,
419
427
  apiDir,
420
- jwtSecret,
421
- authStrategy = "http-only",
422
428
  globalMiddlewares = [],
423
429
  onWorkerReady,
424
430
  onWorkerCrash,
425
431
  onError
426
432
  } = config;
427
- const port = _port != null && !Number.isNaN(_port) ? _port : Number(process.env["PORT"]) || 3e3;
433
+ const envPort = process.env["PORT"] != null ? Number(process.env["PORT"]) : NaN;
434
+ const port = _port != null && !Number.isNaN(_port) ? _port : !Number.isNaN(envPort) ? envPort : 3e3;
435
+ const databaseUrl = config.databaseUrl ?? process.env["DATABASE_URL"];
436
+ const database = config.database ?? detectDatabase(databaseUrl);
437
+ const jwtSecret = config.jwtSecret ?? process.env["JWT_SECRET"];
438
+ const authStrategy = config.authStrategy ?? "http-only";
439
+ const clusterEnabled = config.cluster ?? process.env["NODE_ENV"] === "production";
428
440
  if (clusterEnabled && cluster2.isPrimary) {
429
441
  runMaster({
430
442
  workers: workers ?? os2.cpus().length,
@@ -440,7 +452,7 @@ async function ignite(config) {
440
452
  let origin;
441
453
  if (envOrigins) {
442
454
  const list = envOrigins.split(",").map((o) => o.trim()).filter(Boolean);
443
- origin = list.length === 1 ? list[0] : list;
455
+ origin = list;
444
456
  } else if (typeof corsOption === "object" && corsOption.origin !== void 0) {
445
457
  origin = corsOption.origin;
446
458
  } else {
@@ -455,8 +467,8 @@ async function ignite(config) {
455
467
  for (const mw of globalMiddlewares) {
456
468
  app.use(mw);
457
469
  }
458
- if (config.database === "mongodb" && config.databaseUrl) {
459
- const conn = await connectMongo(config.databaseUrl);
470
+ if (database === "mongodb" && databaseUrl) {
471
+ const conn = await connectMongo(databaseUrl);
460
472
  setDbClient(conn);
461
473
  }
462
474
  if (jwtSecret) {
@@ -486,8 +498,9 @@ async function ignite(config) {
486
498
  } else {
487
499
  app.use(
488
500
  (err, _req, res, _next) => {
489
- if (err instanceof HttpError) {
490
- res.status(err.statusCode).json({ error: err.message, statusCode: err.statusCode });
501
+ const asHttp = err instanceof Error && "statusCode" in err ? err : null;
502
+ if (asHttp) {
503
+ res.status(asHttp.statusCode).json({ error: asHttp.message, statusCode: asHttp.statusCode });
491
504
  } else {
492
505
  console.error("[EFC] Unhandled error:", err);
493
506
  res.status(500).json({ error: "Internal Server Error", statusCode: 500 });
@@ -495,20 +508,40 @@ async function ignite(config) {
495
508
  }
496
509
  );
497
510
  }
498
- await new Promise((resolve) => {
499
- app.listen(port, () => {
511
+ return new Promise((resolve, reject) => {
512
+ const server = app.listen(port);
513
+ server.once("listening", () => {
500
514
  const wid = cluster2.worker?.id ?? "primary";
501
- console.log(`[EFC] Worker ${wid} listening on :${port}`);
502
- resolve();
515
+ const addr = server.address();
516
+ console.log(`[EFC] Worker ${wid} listening on :${addr?.port ?? port}`);
517
+ resolve(server);
503
518
  });
519
+ server.once("error", reject);
504
520
  });
505
521
  }
522
+ function gracefulShutdown(server, timeoutMs = 1e4) {
523
+ const shutdown = (signal) => {
524
+ 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();
534
+ };
535
+ process.once("SIGTERM", () => shutdown("SIGTERM"));
536
+ process.once("SIGINT", () => shutdown("SIGINT"));
537
+ }
506
538
  export {
507
539
  HttpError,
508
540
  compose,
509
541
  db,
510
542
  defineModel,
511
543
  getDbClient,
544
+ gracefulShutdown,
512
545
  ignite,
513
546
  scanDir,
514
547
  setDbClient
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/errors.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/compose.ts"],"sourcesContent":["import 'dotenv/config';\nimport express from 'express';\nimport cors from 'cors';\nimport cookieParser from 'cookie-parser';\nimport cluster from 'node:cluster';\nimport os from 'node:os';\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\nexport async function ignite(config: EFCConfig): Promise<void> {\n const {\n port: _port,\n cluster: clusterEnabled = true,\n workers,\n apiDir,\n jwtSecret,\n authStrategy = 'http-only',\n globalMiddlewares = [],\n onWorkerReady,\n onWorkerCrash,\n onError,\n } = config;\n\n const port =\n _port != null && !Number.isNaN(_port)\n ? _port\n : Number(process.env['PORT']) || 3000;\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.length === 1 ? list[0]! : list;\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 (config.database === 'mongodb' && config.databaseUrl) {\n const conn = await connectMongo(config.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 if (err instanceof HttpError) {\n res.status(err.statusCode).json({ error: err.message, statusCode: err.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 await new Promise<void>((resolve) => {\n app.listen(port, () => {\n const wid = (cluster.worker as { id: number } | undefined)?.id ?? 'primary';\n console.log(`[EFC] Worker ${wid} listening on :${port}`);\n resolve();\n });\n });\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","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","// 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","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":";AAAA,OAAO;AACP,OAAO,aAAa;AACpB,OAAO,UAAU;AACjB,OAAO,kBAAkB;AACzB,OAAOA,cAAa;AACpB,OAAOC,SAAQ;;;ACLf,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,IAAM,gBAAgB;AACtB,IAAM,qBAAqB;AAE3B,SAAS,kBAAkB,cAA8B;AACvD,MAAI,IAAI,aAAa,QAAQ,eAAe,EAAE;AAE9C,MAAI,EAAE,QAAQ,YAAY,EAAE;AAE5B,MAAI,EAAE,QAAQ,oBAAoB,KAAK;AACvC,SAAO,MAAM,KAAK,MAAM,EAAE,WAAW,GAAG,IAAI,IAAI,IAAI,CAAC;AACvD;AAEA,SAAS,cAAc,cAAgC;AACrD,QAAM,SAAmB,CAAC;AAC1B,MAAI;AACJ,QAAM,KAAK,IAAI,OAAO,mBAAmB,QAAQ,GAAG;AACpD,UAAQ,QAAQ,GAAG,KAAK,YAAY,OAAO,MAAM;AAC/C,QAAI,MAAM,CAAC,EAAG,QAAO,KAAK,MAAM,CAAC,CAAC;AAAA,EACpC;AACA,SAAO;AACT;AAEO,SAAS,QAAQ,KAAa,OAAe,KAAmB;AACrE,MAAI,CAAC,GAAG,WAAW,GAAG,EAAG,QAAO,CAAC;AAEjC,QAAM,UAAwB,CAAC;AAC/B,QAAM,QAAQ,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAEzD,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,KAAK,KAAK,KAAK,KAAK,IAAI;AACzC,QAAI,KAAK,YAAY,GAAG;AACtB,cAAQ,KAAK,GAAG,QAAQ,UAAU,IAAI,CAAC;AAAA,IACzC,WAAW,cAAc,KAAK,KAAK,IAAI,GAAG;AACxC,YAAM,WAAW,MAAM,KAAK,SAAS,MAAM,QAAQ,EAAE,QAAQ,OAAO,GAAG;AACvE,cAAQ,KAAK;AAAA,QACX,SAAS,kBAAkB,QAAQ;AAAA,QACnC,UAAU;AAAA,QACV,QAAQ,cAAc,QAAQ;AAAA,MAChC,CAAC;AAAA,IACH;AAAA,EACF;AAGA,SAAO,QAAQ,KAAK,CAAC,GAAG,MAAM;AAC5B,UAAM,WAAW,EAAE,QAAQ,SAAS,GAAG,IAAI,IAAI;AAC/C,UAAM,WAAW,EAAE,QAAQ,SAAS,GAAG,IAAI,IAAI;AAC/C,WAAO,WAAW,YAAY,EAAE,QAAQ,cAAc,EAAE,OAAO;AAAA,EACjE,CAAC;AACH;;;ACjDA,IAAM,eAAe,CAAC,OAAO,QAAQ,OAAO,SAAS,UAAU,QAAQ,SAAS;AAGhF,SAAS,UAAU,SAAyC;AAC1D,SAAO,CAAC,KAAK,KAAK,SAAS;AACzB,YAAQ,QAAQ,QAAQ,KAAK,KAAK,IAAI,CAAC,EAAE,MAAM,IAAI;AAAA,EACrD;AACF;AAEA,eAAsB,YAAY,KAAkB,QAAqC;AACvF,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAO,MAAM,OAAO,MAAM;AAChC,UAAM,mBAAqC,MAAM,QAAQ,IAAI,aAAa,CAAC,IACtE,IAAI,aAAa,IAClB,CAAC;AAEL,UAAM,cAA4B,CAAC;AACnC,UAAM,gBAA8B,CAAC;AAErC,eAAW,UAAU,cAAc;AACjC,UAAI,OAAO,IAAI,MAAM,MAAM,YAAY;AACrC,oBAAY,KAAK,MAAM;AACvB,YAAI,OAAO,YAAY,CAA0B;AAAA,UAC/C,MAAM;AAAA,UACN,GAAG;AAAA,UACH,UAAU,IAAI,MAAM,CAAmB;AAAA,QACzC;AAAA,MACF,OAAO;AACL,sBAAc,KAAK,MAAM;AAAA,MAC3B;AAAA,IACF;AAEA,QAAI,YAAY,SAAS,KAAK,cAAc,SAAS,GAAG;AACtD,YAAM,cAAc,YAAY,KAAK,IAAI;AACzC,UAAI,IAAI,MAAM,SAAS,CAAC,KAAK,KAAK,SAAS;AACzC,YAAK,cAA2B,SAAS,IAAI,MAAM,GAAG;AACpD,cAAI,IAAI,SAAS,WAAW,EAAE,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,qBAAqB,CAAC;AAAA,QAChF,OAAO;AACL,eAAK;AAAA,QACP;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AC9CA,OAAO,aAAa;AACpB,OAAO,QAAQ;AAQR,SAAS,UAAU,UAA0B,CAAC,GAAS;AAC5D,QAAM,QAAQ,QAAQ,WAAW,GAAG,KAAK,EAAE;AAE3C,UAAQ,IAAI,iBAAiB,QAAQ,GAAG,aAAa,KAAK,UAAU;AAEpE,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,YAAQ,KAAK;AAAA,EACf;AAEA,UAAQ,GAAG,UAAU,CAAC,WAAW;AAC/B,YAAQ,gBAAgB,OAAO,EAAE;AAAA,EACnC,CAAC;AAED,UAAQ,GAAG,QAAQ,CAAC,QAAQ,MAAM,WAAW;AAC3C,UAAM,WAAW,SAAS,SAAS,KAAK;AACxC,YAAQ,KAAK,gBAAgB,OAAO,EAAE,iBAAiB,QAAQ,qBAAgB;AAC/E,YAAQ,gBAAgB,OAAO,IAAI,QAAQ;AAC3C,YAAQ,KAAK;AAAA,EACf,CAAC;AACH;;;AC3BA,OAAO,SAAS;AAUhB,IAAI,UAA6B;AAE1B,SAAS,cAAc,QAA0B;AACtD,YAAU;AACZ;;;ACfO,IAAM,YAAN,MAAM,mBAAkB,MAAM;AAAA,EAC1B;AAAA,EAET,YAAY,YAAoB,SAAiB;AAC/C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,aAAa;AAClB,UAAM,kBAAkB,MAAM,UAAS;AAAA,EACzC;AACF;;;ACPA,IAAI;AAEJ,eAAe,eAAmD;AAChE,MAAI,SAAU,QAAO;AACrB,MAAI;AACF,eAAW,MAAM,OAAO,UAAU;AAClC,WAAO;AAAA,EACT,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,aAAa,KAAqD;AACtF,QAAM,KAAK,MAAM,aAAa;AAC9B,QAAM,GAAG,QAAQ,GAAG;AACpB,UAAQ,IAAI,yBAAyB;AACrC,SAAO,GAAG;AACZ;;;ACjBA,SAAS,UAAU,KAAyC;AAC1D,QAAM,KAAK,IAAI,KAAK,IAAI,OAAO,IAAI,KAAK,CAAC,IAAI;AAC7C,SAAO,EAAE,GAAG,KAAK,GAAG;AACtB;AAEA,eAAe,SAAS,MAAc,WAAgG;AACpI,MAAI;AACJ,MAAI;AACF,SAAK,MAAM,OAAO,UAAU;AAAA,EAC9B,QAAQ;AACN,UAAM,IAAI,MAAM,4DAA4D;AAAA,EAC9E;AAEA,MAAI,GAAG,OAAO,IAAI,EAAG,QAAO,GAAG,OAAO,IAAI;AAG1C,QAAM,SAAS,IAAI,GAAG,OAAO,WAAkB,EAAE,YAAY,KAAK,CAAC;AACnE,SAAO,GAAG,MAA+B,MAAM,MAAM;AACvD;AAEA,SAAS,oBAAoB,QAA8C;AACzE,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,UAAM,QAAiC,CAAC;AACxC,YAAQ,IAAI,MAAM;AAAA,MAChB,KAAK;AAAY,cAAM,MAAM,IAAI;AAAQ;AAAA,MACzC,KAAK;AAAY,cAAM,MAAM,IAAI;AAAQ;AAAA,MACzC,KAAK;AAAY,cAAM,MAAM,IAAI;AAAS;AAAA,MAC1C,KAAK;AAAY,cAAM,MAAM,IAAI;AAAM;AAAA,MACvC,KAAK;AAAY,cAAM,MAAM,IAAI;AAAQ;AAAA,MACzC,KAAK;AAAY,cAAM,MAAM,IAAI;AAAO;AAAA,IAC1C;AACA,QAAI,IAAI,aAAa,OAAW,OAAM,UAAU,IAAI,IAAI;AACxD,QAAI,IAAI,WAAW,OAAa,OAAM,QAAQ,IAAI,IAAI;AACtD,QAAI,IAAI,YAAY,OAAY,OAAM,SAAS,IAAI,IAAI;AACvD,QAAI,GAAG,IAAI;AAAA,EACb;AACA,SAAO;AACT;AAEO,SAAS,YACd,MACA,QACc;AACd,QAAM,iBAAiB,oBAAoB,MAAM;AAEjD,SAAO;AAAA,IACL,MAAM,KAAK,SAAS,CAAC,GAAG;AACtB,YAAM,IAAI,MAAM,SAAS,MAAM,cAAc;AAC7C,YAAM,OAAO,MAAM,EAAE,KAAK,MAAiC,EAAE,KAAK;AAClE,aAAQ,KAAmC,IAAI,SAAS;AAAA,IAC1D;AAAA,IAEA,MAAM,SAAS,IAAI;AACjB,YAAM,IAAI,MAAM,SAAS,MAAM,cAAc;AAC7C,YAAM,MAAM,MAAM,EAAE,SAAS,EAAE,EAAE,KAAK;AACtC,UAAI,CAAC,IAAK,QAAO;AACjB,aAAO,UAAU,GAA8B;AAAA,IACjD;AAAA,IAEA,MAAM,QAAQ,QAAQ;AACpB,YAAM,IAAI,MAAM,SAAS,MAAM,cAAc;AAC7C,YAAM,MAAM,MAAM,EAAE,QAAQ,MAAiC,EAAE,KAAK;AACpE,UAAI,CAAC,IAAK,QAAO;AACjB,aAAO,UAAU,GAA8B;AAAA,IACjD;AAAA,IAEA,MAAM,OAAO,MAAM;AACjB,YAAM,IAAI,MAAM,SAAS,MAAM,cAAc;AAC7C,YAAM,MAAM,MAAM,EAAE,OAAO,IAAI;AAC/B,aAAO,UAAU,IAAI,SAAS,CAA4B;AAAA,IAC5D;AAAA,IAEA,MAAM,OAAO,IAAI,MAAM;AACrB,YAAM,IAAI,MAAM,SAAS,MAAM,cAAc;AAC7C,YAAM,MAAM,MAAM,EAAE,kBAAkB,IAAI,MAAM,EAAE,KAAK,KAAK,CAAC,EAAE,KAAK;AACpE,UAAI,CAAC,IAAK,QAAO;AACjB,aAAO,UAAU,GAA8B;AAAA,IACjD;AAAA,IAEA,MAAM,OAAO,IAAI;AACf,YAAM,IAAI,MAAM,SAAS,MAAM,cAAc;AAC7C,YAAM,EAAE,kBAAkB,EAAE;AAAA,IAC9B;AAAA,IAEA,MAAM,MAAM,SAAS,CAAC,GAAG;AACvB,YAAM,IAAI,MAAM,SAAS,MAAM,cAAc;AAC7C,aAAO,EAAE,eAAe,MAAiC;AAAA,IAC3D;AAAA,EACF;AACF;;;ACxFA,IAAI,UAA4B;AAEzB,SAAS,YAAY,QAAyB;AACnD,YAAU;AACZ;AAEO,SAAS,cAAyB;AACvC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,gEAA2D;AAAA,EAC7E;AACA,SAAO;AACT;AAEO,IAAM,KAAgB,IAAI,MAAM,CAAC,GAAgB;AAAA,EACtD,IAAI,SAAS,MAAc;AACzB,WAAO,YAAY,EAAE,IAAI;AAAA,EAC3B;AAAA,EACA,IAAI,SAAS,MAAc,OAAgB;AACzC,gBAAY,EAAE,IAAI,IAAI;AACtB,WAAO;AAAA,EACT;AACF,CAAC;;;AC3BD,OAAOC,SAAQ;AACf,OAAOC,WAAU;;;ACCV,IAAM,eAAe,oBAAI,IAA4B;AA+B5D,IAAI,QAA4B;AAEzB,SAAS,eAAe,MAAyB;AACtD,UAAQ;AACV;AAWO,SAAS,aAAa,MAAc,KAA2B;AACpE,eAAa,IAAI,MAAM,EAAE,GAAG,KAAK,KAAK,CAAC;AACzC;;;AD7CA,eAAsB,UAAU,UAAiC;AAC/D,MAAI,CAACC,IAAG,WAAW,QAAQ,EAAG;AAE9B,QAAM,QAAQA,IAAG,YAAY,QAAQ,EAAE,OAAO,CAAC,MAAM,qBAAqB,KAAK,CAAC,CAAC;AAEjF,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAWC,MAAK,KAAK,UAAU,IAAI;AACzC,UAAM,WAAWA,MAAK,SAAS,MAAMA,MAAK,QAAQ,IAAI,CAAC;AAEvD,QAAI;AACF,YAAM,MAAM,MAAM,OAAO;AACzB,YAAM,MAAM,IAAI,SAAS;AAEzB,UAAI,CAAC,OAAO,OAAO,IAAI,YAAY,YAAY;AAC7C,gBAAQ,KAAK,mBAAmB,IAAI,8CAAyC;AAC7E;AAAA,MACF;AAEA,mBAAa,UAAU,EAAE,GAAG,KAAK,SAAS,CAAC;AAC3C,cAAQ,IAAI,0BAA0B,QAAQ,EAAE;AAAA,IAClD,SAAS,KAAK;AACZ,cAAQ,KAAK,6BAA6B,IAAI,KAAK,GAAG;AAAA,IACxD;AAAA,EACF;AACF;;;AE7BA,SAAS,QAAQ,YAAY,YAAY,oBAAoB;AAI7D,IAAI,CAAC,cAAc;AACjB,QAAM,EAAE,aAAa,QAAQ,IAAI;AAEjC,GAAC,YAAY;AACX,QAAI;AACF,YAAM,MAAM,MAAM,OAAO;AACzB,YAAM,MAAM,IAAI,SAAS;AACzB,UAAI,CAAC,OAAO,OAAO,IAAI,YAAY,YAAY;AAC7C,cAAM,IAAI,MAAM,gDAAgD,WAAW,EAAE;AAAA,MAC/E;AACA,YAAM,IAAI,QAAQ,OAAO;AACzB,kBAAY,YAAY,EAAE,IAAI,KAAK,CAAC;AAAA,IACtC,SAAS,KAAK;AACZ,kBAAY,YAAY,EAAE,IAAI,OAAO,OAAO,OAAO,GAAG,EAAE,CAAC;AAAA,IAC3D;AAAA,EACF,GAAG;AACL;AAGO,SAAS,YAAY,aAAqB,SAAiC;AAChF,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAS,IAAI,OAAO,IAAI,IAAI,sBAAsB,YAAY,GAAG,GAAG;AAAA,MACxE,YAAY,EAAE,aAAa,QAAQ;AAAA,IACrC,CAAC;AAED,WAAO,GAAG,WAAW,CAAC,QAAyC;AAC7D,UAAI,IAAI,GAAI,SAAQ;AAAA,UACf,QAAO,IAAI,MAAM,IAAI,SAAS,oBAAoB,CAAC;AAAA,IAC1D,CAAC;AAED,WAAO,GAAG,SAAS,MAAM;AACzB,WAAO,GAAG,QAAQ,CAAC,SAAS;AAC1B,UAAI,SAAS,EAAG,QAAO,IAAI,MAAM,kCAAkC,IAAI,EAAE,CAAC;AAAA,IAC5E,CAAC;AAAA,EACH,CAAC;AACH;;;AC9BA,eAAsB,WAAW,MAAiC;AAChE,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,OAAO,QAAQ;AAAA,EAChC,QAAQ;AACN,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AAEA,QAAM,aAAa,cAAc,KAAK,QAAQ;AAC9C,QAAM,QAAQ,IAAI,OAAO,MAAM,OAAO,EAAE,WAAW,CAAC;AAEpD,QAAM,SAAS,IAAI,OAAO;AAAA,IACxB;AAAA,IACA,OAAO,QAAa;AAClB,YAAM,MAAM,aAAa,IAAI,IAAI,IAAI;AACrC,UAAI,CAAC,KAAK;AACR,cAAM,IAAI,MAAM,sCAAsC,IAAI,IAAI,EAAE;AAAA,MAClE;AAEA,UAAI,IAAI,QAAQ,UAAU,IAAI,UAAU;AACtC,cAAM,YAAY,IAAI,UAAU,IAAI,IAAI;AAAA,MAC1C,OAAO;AACL,cAAM,IAAI,QAAQ,IAAI,IAAI;AAAA,MAC5B;AAAA,IACF;AAAA,IACA,EAAE,YAAY,aAAa,KAAK,YAAY;AAAA,EAC9C;AAEA,SAAO,GAAG,UAAU,CAAC,KAAsB,QAAe;AACxD,YAAQ,MAAM,cAAc,KAAK,QAAQ,SAAS,YAAY,IAAI,OAAO;AAAA,EAC3E,CAAC;AAED,SAAO,GAAG,aAAa,CAAC,QAAa;AACnC,YAAQ,IAAI,cAAc,IAAI,IAAI,kBAAkB,IAAI,EAAE,GAAG;AAAA,EAC/D,CAAC;AAED,iBAAe,OAAO,MAAM,YAAY;AACtC,UAAM,MAAM,aAAa,IAAI,IAAI;AACjC,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,uCAAuC,IAAI,kCAAkC;AAAA,IAC/F;AAEA,UAAM,MAAM,IAAI,MAAM,SAAS;AAAA,MAC7B,UAAU,IAAI,QAAQ,WAAW;AAAA,MACjC,SAAS;AAAA,QACP,MAAM,IAAI,QAAQ,WAAW;AAAA,QAC7B,OAAO;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,UAAQ,IAAI,kCAAkC;AAChD;AAEA,SAAS,cAAc,KAAgE;AACrF,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,GAAG;AACrB,UAAM,SAA4D;AAAA,MAChE,MAAM,EAAE,YAAY;AAAA,MACpB,MAAM,SAAS,EAAE,QAAQ,QAAQ,EAAE;AAAA,IACrC;AACA,QAAI,EAAE,SAAU,QAAO,WAAW,EAAE;AACpC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,EAAE,MAAM,aAAa,MAAM,KAAK;AAAA,EACzC;AACF;;;ACzEO,SAAS,WAAW,UAA4C;AACrE,SAAO,CAAC,KAAK,KAAK,SAAS;AACzB,QAAI,QAAQ;AAEZ,aAAS,SAAS,GAAiB;AACjC,UAAI,KAAK,SAAS,QAAQ;AACxB,aAAK;AACL;AAAA,MACF;AACA,YAAM,UAAU,SAAS,CAAC;AAC1B,UAAI,CAAC,SAAS;AACZ,aAAK;AACL;AAAA,MACF;AACA,UAAI;AACF,gBAAQ,QAAQ,QAAQ,KAAK,KAAK,MAAM,SAAS,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI;AAAA,MACtE,SAAS,KAAK;AACZ,aAAK,GAAG;AAAA,MACV;AAAA,IACF;AAEA,aAAS,KAAK;AAAA,EAChB;AACF;;;AbRA,eAAsB,OAAO,QAAkC;AAC7D,QAAM;AAAA,IACJ,MAAM;AAAA,IACN,SAAS,iBAAiB;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,oBAAoB,CAAC;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,OACJ,SAAS,QAAQ,CAAC,OAAO,MAAM,KAAK,IAChC,QACA,OAAO,QAAQ,IAAI,MAAM,CAAC,KAAK;AAErC,MAAI,kBAAkBC,SAAQ,WAAW;AACvC,cAAU;AAAA,MACR,SAAS,WAAWC,IAAG,KAAK,EAAE;AAAA,MAC9B,GAAI,kBAAkB,UAAa,EAAE,cAAc;AAAA,MACnD,GAAI,kBAAkB,UAAa,EAAE,cAAc;AAAA,IACrD,CAAC;AACD;AAAA,EACF;AAEA,QAAM,MAAM,QAAQ;AAEpB,QAAM,aAAa,OAAO,QAAQ;AAClC,MAAI,eAAe,OAAO;AACxB,UAAM,aAAa,QAAQ,IAAI,cAAc;AAC7C,QAAI;AACJ,QAAI,YAAY;AACd,YAAM,OAAO,WAAW,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AACtE,eAAS,KAAK,WAAW,IAAI,KAAK,CAAC,IAAK;AAAA,IAC1C,WAAW,OAAO,eAAe,YAAY,WAAW,WAAW,QAAW;AAC5E,eAAS,WAAW;AAAA,IACtB,OAAO;AACL,eAAS;AAAA,IACX;AACA,UAAM,WAAW,OAAO,eAAe,WAAW,EAAE,GAAG,YAAY,OAAO,IAAI,EAAE,OAAO;AACvF,QAAI,IAAI,KAAK,QAAQ,CAAC;AAAA,EACxB;AAEA,MAAI,IAAI,QAAQ,KAAK,CAAC;AACtB,MAAI,IAAI,QAAQ,WAAW,EAAE,UAAU,KAAK,CAAC,CAAC;AAC9C,MAAI,IAAI,aAAa,CAAC;AAEtB,aAAW,MAAM,mBAAmB;AAClC,QAAI,IAAI,EAAE;AAAA,EACZ;AAGA,MAAI,OAAO,aAAa,aAAa,OAAO,aAAa;AACvD,UAAM,OAAO,MAAM,aAAa,OAAO,WAAW;AAClD,gBAAY,IAA0C;AAAA,EACxD;AAGA,MAAI,WAAW;AACb,UAAM,eAAe,QAAQ,IAAI,eAAe;AAChD,kBAAc;AAAA,MACZ,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,WAAW,QAAQ,IAAI,gBAAgB,KAAK;AAAA,MAC5C,GAAI,iBAAiB,UAAa,EAAE,aAAa;AAAA,IACnD,CAAC;AAAA,EACH;AAGA,MAAI,OAAO,UAAU;AACnB,UAAM,UAAU,OAAO,QAAQ;AAAA,EACjC;AAGA,MAAI,OAAO,OAAO;AAChB,QAAI,OAAO,MAAM,YAAY,UAAU;AACrC,YAAM,WAAW;AAAA,QACf,UAAU,OAAO,MAAM,YAAY;AAAA,QACnC,aAAa,OAAO,MAAM,eAAe;AAAA,MAC3C,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,SAAS,QAAQ,MAAM;AAC7B,QAAM,YAAY,KAAK,MAAM;AAE7B,MAAI,SAAS;AACX,QAAI,IAAI,OAAO;AAAA,EACjB,OAAO;AACL,QAAI;AAAA,MACF,CACE,KACA,MACA,KACA,UACG;AACH,YAAI,eAAe,WAAW;AAC5B,cAAI,OAAO,IAAI,UAAU,EAAE,KAAK,EAAE,OAAO,IAAI,SAAS,YAAY,IAAI,WAAW,CAAC;AAAA,QACpF,OAAO;AACL,kBAAQ,MAAM,0BAA0B,GAAG;AAC3C,cAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,yBAAyB,YAAY,IAAI,CAAC;AAAA,QAC1E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAI,QAAc,CAAC,YAAY;AACnC,QAAI,OAAO,MAAM,MAAM;AACrB,YAAM,MAAOD,SAAQ,QAAuC,MAAM;AAClE,cAAQ,IAAI,gBAAgB,GAAG,kBAAkB,IAAI,EAAE;AACvD,cAAQ;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;","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 } 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-file-cluster",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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",
@@ -48,8 +48,10 @@
48
48
  "@types/express": "^4.17.21",
49
49
  "@types/jsonwebtoken": "^9.0.6",
50
50
  "@types/node": "^22.0.0",
51
+ "@types/supertest": "^7.2.0",
51
52
  "bullmq": "^5.79.1",
52
53
  "mongoose": "^9.7.2",
54
+ "supertest": "^7.2.2",
53
55
  "tsup": "^8.2.0",
54
56
  "typescript": "^5.5.0",
55
57
  "vitest": "^2.0.0"