@spfn/core 0.2.0-beta.12 → 0.2.0-beta.14

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,13 +1,14 @@
1
1
  import { env } from '@spfn/core/config';
2
- import { config } from 'dotenv';
3
- import { existsSync } from 'fs';
2
+ import { existsSync, readFileSync } from 'fs';
4
3
  import { resolve, join } from 'path';
4
+ import { parse } from 'dotenv';
5
+ import { logger } from '@spfn/core/logger';
5
6
  import { Hono } from 'hono';
6
7
  import { cors } from 'hono/cors';
7
8
  import { registerRoutes } from '@spfn/core/route';
8
9
  import { ErrorHandler, RequestLogger } from '@spfn/core/middleware';
9
10
  import { streamSSE } from 'hono/streaming';
10
- import { logger } from '@spfn/core/logger';
11
+ import { randomBytes } from 'crypto';
11
12
  import { initDatabase, getDatabase, closeDatabase } from '@spfn/core/db';
12
13
  import { initCache, getCache, closeCache } from '@spfn/core/cache';
13
14
  import { serve } from '@hono/node-server';
@@ -314,36 +315,96 @@ var init_formatters = __esm({
314
315
  };
315
316
  }
316
317
  });
317
- function loadEnvFiles() {
318
- const cwd = process.cwd();
319
- const nodeEnv = process.env.NODE_ENV || "development";
320
- const envFiles = [
321
- ".env.server.local",
322
- ".env.server",
323
- `.env.${nodeEnv}.local`,
324
- nodeEnv !== "test" ? ".env.local" : null,
325
- `.env.${nodeEnv}`,
326
- ".env"
327
- ].filter((file) => file !== null);
328
- for (const file of envFiles) {
329
- const filePath = resolve(cwd, file);
330
- if (existsSync(filePath)) {
331
- config({ path: filePath });
318
+ var envLogger = logger.child("@spfn/core:env-loader");
319
+ function getEnvFiles(nodeEnv, server) {
320
+ const files = [
321
+ ".env",
322
+ `.env.${nodeEnv}`
323
+ ];
324
+ if (nodeEnv !== "test") {
325
+ files.push(".env.local");
326
+ }
327
+ files.push(`.env.${nodeEnv}.local`);
328
+ if (server) {
329
+ files.push(".env.server");
330
+ files.push(".env.server.local");
331
+ }
332
+ return files;
333
+ }
334
+ function parseEnvFile(filePath) {
335
+ if (!existsSync(filePath)) {
336
+ return null;
337
+ }
338
+ return parse(readFileSync(filePath, "utf-8"));
339
+ }
340
+ function loadEnv(options = {}) {
341
+ const {
342
+ cwd = process.cwd(),
343
+ nodeEnv = process.env.NODE_ENV || "local",
344
+ server = true,
345
+ debug = false,
346
+ override = false
347
+ } = options;
348
+ const envFiles = getEnvFiles(nodeEnv, server);
349
+ const loadedFiles = [];
350
+ const existingKeys = new Set(Object.keys(process.env));
351
+ const merged = {};
352
+ for (const fileName of envFiles) {
353
+ const filePath = resolve(cwd, fileName);
354
+ const parsed = parseEnvFile(filePath);
355
+ if (parsed === null) {
356
+ continue;
357
+ }
358
+ loadedFiles.push(fileName);
359
+ Object.assign(merged, parsed);
360
+ }
361
+ const loadedKeys = [];
362
+ for (const [key, value] of Object.entries(merged)) {
363
+ if (!override && existingKeys.has(key)) {
364
+ continue;
332
365
  }
366
+ process.env[key] = value;
367
+ loadedKeys.push(key);
368
+ }
369
+ if (debug && loadedFiles.length > 0) {
370
+ envLogger.debug(`Loaded env files: ${loadedFiles.join(", ")}`);
371
+ envLogger.debug(`Loaded ${loadedKeys.length} environment variables`);
372
+ }
373
+ return { loadedFiles, loadedKeys };
374
+ }
375
+
376
+ // src/server/dotenv-loader.ts
377
+ var warned = false;
378
+ function loadEnvFiles() {
379
+ if (!warned) {
380
+ warned = true;
381
+ console.warn(
382
+ '[SPFN] loadEnvFiles() is deprecated. Use loadEnv() from "@spfn/core/env/loader" instead.'
383
+ );
333
384
  }
385
+ loadEnv();
334
386
  }
335
387
  var sseLogger = logger.child("@spfn/core:sse");
336
- function createSSEHandler(router, config2 = {}) {
388
+ function createSSEHandler(router, config = {}, tokenManager) {
337
389
  const {
338
- pingInterval = 3e4
339
- // headers: customHeaders = {}, // Reserved for future use
340
- } = config2;
390
+ pingInterval = 3e4,
391
+ auth: authConfig
392
+ } = config;
341
393
  return async (c) => {
342
- const eventsParam = c.req.query("events");
343
- if (!eventsParam) {
394
+ const subject = await authenticateToken(c, tokenManager);
395
+ if (subject === false) {
396
+ return c.json({ error: "Missing token parameter" }, 401);
397
+ }
398
+ if (subject === null) {
399
+ return c.json({ error: "Invalid or expired token" }, 401);
400
+ }
401
+ if (subject) {
402
+ c.set("sseSubject", subject);
403
+ }
404
+ const requestedEvents = parseRequestedEvents(c);
405
+ if (!requestedEvents) {
344
406
  return c.json({ error: "Missing events parameter" }, 400);
345
407
  }
346
- const requestedEvents = eventsParam.split(",").map((e) => e.trim());
347
408
  const validEventNames = router.eventNames;
348
409
  const invalidEvents = requestedEvents.filter((e) => !validEventNames.includes(e));
349
410
  if (invalidEvents.length > 0) {
@@ -353,19 +414,29 @@ function createSSEHandler(router, config2 = {}) {
353
414
  validEvents: validEventNames
354
415
  }, 400);
355
416
  }
417
+ const allowedEvents = await authorizeEvents(subject, requestedEvents, authConfig);
418
+ if (allowedEvents === null) {
419
+ return c.json({ error: "Not authorized for any requested events" }, 403);
420
+ }
356
421
  sseLogger.debug("SSE connection requested", {
357
- events: requestedEvents,
422
+ events: allowedEvents,
423
+ subject: subject || void 0,
358
424
  clientIp: c.req.header("x-forwarded-for") || c.req.header("x-real-ip")
359
425
  });
360
426
  return streamSSE(c, async (stream) => {
361
427
  const unsubscribes = [];
362
428
  let messageId = 0;
363
- for (const eventName of requestedEvents) {
429
+ for (const eventName of allowedEvents) {
364
430
  const eventDef = router.events[eventName];
365
431
  if (!eventDef) {
366
432
  continue;
367
433
  }
368
434
  const unsubscribe = eventDef.subscribe((payload) => {
435
+ if (subject && authConfig?.filter?.[eventName]) {
436
+ if (!authConfig.filter[eventName](subject, payload)) {
437
+ return;
438
+ }
439
+ }
369
440
  messageId++;
370
441
  const message = {
371
442
  event: eventName,
@@ -384,13 +455,13 @@ function createSSEHandler(router, config2 = {}) {
384
455
  unsubscribes.push(unsubscribe);
385
456
  }
386
457
  sseLogger.info("SSE connection established", {
387
- events: requestedEvents,
458
+ events: allowedEvents,
388
459
  subscriptionCount: unsubscribes.length
389
460
  });
390
461
  await stream.writeSSE({
391
462
  event: "connected",
392
463
  data: JSON.stringify({
393
- subscribedEvents: requestedEvents,
464
+ subscribedEvents: allowedEvents,
394
465
  timestamp: Date.now()
395
466
  })
396
467
  });
@@ -407,7 +478,7 @@ function createSSEHandler(router, config2 = {}) {
407
478
  clearInterval(pingTimer);
408
479
  unsubscribes.forEach((fn) => fn());
409
480
  sseLogger.info("SSE connection closed", {
410
- events: requestedEvents
481
+ events: allowedEvents
411
482
  });
412
483
  }, async (err) => {
413
484
  sseLogger.error("SSE stream error", {
@@ -416,6 +487,99 @@ function createSSEHandler(router, config2 = {}) {
416
487
  });
417
488
  };
418
489
  }
490
+ async function authenticateToken(c, tokenManager) {
491
+ if (!tokenManager) {
492
+ return void 0;
493
+ }
494
+ const token = c.req.query("token");
495
+ if (!token) {
496
+ return false;
497
+ }
498
+ return await tokenManager.verify(token);
499
+ }
500
+ function parseRequestedEvents(c) {
501
+ const eventsParam = c.req.query("events");
502
+ if (!eventsParam) {
503
+ return null;
504
+ }
505
+ return eventsParam.split(",").map((e) => e.trim());
506
+ }
507
+ async function authorizeEvents(subject, requestedEvents, authConfig) {
508
+ if (!subject || !authConfig?.authorize) {
509
+ return requestedEvents;
510
+ }
511
+ const allowed = await authConfig.authorize(subject, requestedEvents);
512
+ if (allowed.length === 0) {
513
+ return null;
514
+ }
515
+ return allowed;
516
+ }
517
+ var InMemoryTokenStore = class {
518
+ tokens = /* @__PURE__ */ new Map();
519
+ async set(token, data) {
520
+ this.tokens.set(token, data);
521
+ }
522
+ async consume(token) {
523
+ const data = this.tokens.get(token);
524
+ if (!data) {
525
+ return null;
526
+ }
527
+ this.tokens.delete(token);
528
+ return data;
529
+ }
530
+ async cleanup() {
531
+ const now = Date.now();
532
+ for (const [token, data] of this.tokens) {
533
+ if (data.expiresAt <= now) {
534
+ this.tokens.delete(token);
535
+ }
536
+ }
537
+ }
538
+ };
539
+ var SSETokenManager = class {
540
+ store;
541
+ ttl;
542
+ cleanupTimer = null;
543
+ constructor(config) {
544
+ this.ttl = config?.ttl ?? 3e4;
545
+ this.store = config?.store ?? new InMemoryTokenStore();
546
+ const cleanupInterval = config?.cleanupInterval ?? 6e4;
547
+ this.cleanupTimer = setInterval(() => void this.store.cleanup(), cleanupInterval);
548
+ this.cleanupTimer.unref();
549
+ }
550
+ /**
551
+ * Issue a new one-time-use token for the given subject
552
+ */
553
+ async issue(subject) {
554
+ const token = randomBytes(32).toString("hex");
555
+ await this.store.set(token, {
556
+ token,
557
+ subject,
558
+ expiresAt: Date.now() + this.ttl
559
+ });
560
+ return token;
561
+ }
562
+ /**
563
+ * Verify and consume a token
564
+ * @returns subject string if valid, null if invalid/expired/already consumed
565
+ */
566
+ async verify(token) {
567
+ const data = await this.store.consume(token);
568
+ if (!data || data.expiresAt <= Date.now()) {
569
+ return null;
570
+ }
571
+ return data.subject;
572
+ }
573
+ /**
574
+ * Cleanup timer and resources
575
+ */
576
+ destroy() {
577
+ if (this.cleanupTimer) {
578
+ clearInterval(this.cleanupTimer);
579
+ this.cleanupTimer = null;
580
+ }
581
+ }
582
+ };
419
583
  function createHealthCheckHandler(detailed) {
420
584
  return async (c) => {
421
585
  const response = {
@@ -474,34 +638,34 @@ function applyServerTimeouts(server, timeouts) {
474
638
  server.headersTimeout = timeouts.headers;
475
639
  }
476
640
  }
477
- function getTimeoutConfig(config2) {
641
+ function getTimeoutConfig(config) {
478
642
  return {
479
- request: config2?.request ?? env.SERVER_TIMEOUT,
480
- keepAlive: config2?.keepAlive ?? env.SERVER_KEEPALIVE_TIMEOUT,
481
- headers: config2?.headers ?? env.SERVER_HEADERS_TIMEOUT
643
+ request: config?.request ?? env.SERVER_TIMEOUT,
644
+ keepAlive: config?.keepAlive ?? env.SERVER_KEEPALIVE_TIMEOUT,
645
+ headers: config?.headers ?? env.SERVER_HEADERS_TIMEOUT
482
646
  };
483
647
  }
484
- function getShutdownTimeout(config2) {
485
- return config2?.timeout ?? env.SHUTDOWN_TIMEOUT;
648
+ function getShutdownTimeout(config) {
649
+ return config?.timeout ?? env.SHUTDOWN_TIMEOUT;
486
650
  }
487
- function buildMiddlewareOrder(config2) {
651
+ function buildMiddlewareOrder(config) {
488
652
  const order = [];
489
- const middlewareConfig = config2.middleware ?? {};
653
+ const middlewareConfig = config.middleware ?? {};
490
654
  const enableLogger = middlewareConfig.logger !== false;
491
655
  const enableCors = middlewareConfig.cors !== false;
492
656
  const enableErrorHandler = middlewareConfig.errorHandler !== false;
493
657
  if (enableLogger) order.push("RequestLogger");
494
658
  if (enableCors) order.push("CORS");
495
- config2.use?.forEach((_, i) => order.push(`Custom[${i}]`));
496
- if (config2.beforeRoutes) order.push("beforeRoutes hook");
659
+ config.use?.forEach((_, i) => order.push(`Custom[${i}]`));
660
+ if (config.beforeRoutes) order.push("beforeRoutes hook");
497
661
  order.push("Routes");
498
- if (config2.afterRoutes) order.push("afterRoutes hook");
662
+ if (config.afterRoutes) order.push("afterRoutes hook");
499
663
  if (enableErrorHandler) order.push("ErrorHandler");
500
664
  return order;
501
665
  }
502
- function buildStartupConfig(config2, timeouts) {
503
- const middlewareConfig = config2.middleware ?? {};
504
- const healthCheckConfig = config2.healthCheck ?? {};
666
+ function buildStartupConfig(config, timeouts) {
667
+ const middlewareConfig = config.middleware ?? {};
668
+ const healthCheckConfig = config.healthCheck ?? {};
505
669
  const healthCheckEnabled = healthCheckConfig.enabled !== false;
506
670
  const healthCheckPath = healthCheckConfig.path ?? "/health";
507
671
  const healthCheckDetailed = healthCheckConfig.detailed ?? env.NODE_ENV === "development";
@@ -510,7 +674,7 @@ function buildStartupConfig(config2, timeouts) {
510
674
  logger: middlewareConfig.logger !== false,
511
675
  cors: middlewareConfig.cors !== false,
512
676
  errorHandler: middlewareConfig.errorHandler !== false,
513
- custom: config2.use?.length ?? 0
677
+ custom: config.use?.length ?? 0
514
678
  },
515
679
  healthCheck: healthCheckEnabled ? {
516
680
  enabled: true,
@@ -518,8 +682,8 @@ function buildStartupConfig(config2, timeouts) {
518
682
  detailed: healthCheckDetailed
519
683
  } : { enabled: false },
520
684
  hooks: {
521
- beforeRoutes: !!config2.beforeRoutes,
522
- afterRoutes: !!config2.afterRoutes
685
+ beforeRoutes: !!config.beforeRoutes,
686
+ afterRoutes: !!config.afterRoutes
523
687
  },
524
688
  timeout: {
525
689
  request: `${timeouts.request}ms`,
@@ -527,23 +691,23 @@ function buildStartupConfig(config2, timeouts) {
527
691
  headers: `${timeouts.headers}ms`
528
692
  },
529
693
  shutdown: {
530
- timeout: `${config2.shutdown?.timeout ?? env.SHUTDOWN_TIMEOUT}ms`
694
+ timeout: `${config.shutdown?.timeout ?? env.SHUTDOWN_TIMEOUT}ms`
531
695
  }
532
696
  };
533
697
  }
534
698
  var serverLogger = logger.child("@spfn/core:server");
535
699
 
536
700
  // src/server/create-server.ts
537
- async function createServer(config2) {
701
+ async function createServer(config) {
538
702
  const cwd = process.cwd();
539
703
  const appPath = join(cwd, "src", "server", "app.ts");
540
704
  const appJsPath = join(cwd, "src", "server", "app");
541
705
  if (existsSync(appPath) || existsSync(appJsPath)) {
542
- return await loadCustomApp(appPath, appJsPath, config2);
706
+ return await loadCustomApp(appPath, appJsPath, config);
543
707
  }
544
- return await createAutoConfiguredApp(config2);
708
+ return await createAutoConfiguredApp(config);
545
709
  }
546
- async function loadCustomApp(appPath, appJsPath, config2) {
710
+ async function loadCustomApp(appPath, appJsPath, config) {
547
711
  const actualPath = existsSync(appPath) ? appPath : appJsPath;
548
712
  const appModule = await import(actualPath);
549
713
  const appFactory = appModule.default;
@@ -551,15 +715,15 @@ async function loadCustomApp(appPath, appJsPath, config2) {
551
715
  throw new Error("app.ts must export a default function that returns a Hono app");
552
716
  }
553
717
  const app = await appFactory();
554
- if (config2?.routes) {
555
- const routes = registerRoutes(app, config2.routes, config2.middlewares);
556
- logRegisteredRoutes(routes, config2?.debug ?? false);
718
+ if (config?.routes) {
719
+ const routes = registerRoutes(app, config.routes, config.middlewares);
720
+ logRegisteredRoutes(routes, config?.debug ?? false);
557
721
  }
558
722
  return app;
559
723
  }
560
- async function createAutoConfiguredApp(config2) {
724
+ async function createAutoConfiguredApp(config) {
561
725
  const app = new Hono();
562
- const middlewareConfig = config2?.middleware ?? {};
726
+ const middlewareConfig = config?.middleware ?? {};
563
727
  const enableLogger = middlewareConfig.logger !== false;
564
728
  const enableCors = middlewareConfig.cors !== false;
565
729
  const enableErrorHandler = middlewareConfig.errorHandler !== false;
@@ -569,31 +733,31 @@ async function createAutoConfiguredApp(config2) {
569
733
  await next();
570
734
  });
571
735
  }
572
- applyDefaultMiddleware(app, config2, enableLogger, enableCors);
573
- if (Array.isArray(config2?.use)) {
574
- config2.use.forEach((mw) => app.use("*", mw));
736
+ applyDefaultMiddleware(app, config, enableLogger, enableCors);
737
+ if (Array.isArray(config?.use)) {
738
+ config.use.forEach((mw) => app.use("*", mw));
575
739
  }
576
- registerHealthCheckEndpoint(app, config2);
577
- await executeBeforeRoutesHook(app, config2);
578
- await loadAppRoutes(app, config2);
579
- registerSSEEndpoint(app, config2);
580
- await executeAfterRoutesHook(app, config2);
740
+ registerHealthCheckEndpoint(app, config);
741
+ await executeBeforeRoutesHook(app, config);
742
+ await loadAppRoutes(app, config);
743
+ registerSSEEndpoint(app, config);
744
+ await executeAfterRoutesHook(app, config);
581
745
  if (enableErrorHandler) {
582
746
  app.onError(ErrorHandler());
583
747
  }
584
748
  return app;
585
749
  }
586
- function applyDefaultMiddleware(app, config2, enableLogger, enableCors) {
750
+ function applyDefaultMiddleware(app, config, enableLogger, enableCors) {
587
751
  if (enableLogger) {
588
752
  app.use("*", RequestLogger());
589
753
  }
590
754
  if (enableCors) {
591
- const corsOptions = config2?.cors !== false ? config2?.cors : void 0;
755
+ const corsOptions = config?.cors !== false ? config?.cors : void 0;
592
756
  app.use("*", cors(corsOptions));
593
757
  }
594
758
  }
595
- function registerHealthCheckEndpoint(app, config2) {
596
- const healthCheckConfig = config2?.healthCheck ?? {};
759
+ function registerHealthCheckEndpoint(app, config) {
760
+ const healthCheckConfig = config?.healthCheck ?? {};
597
761
  const healthCheckEnabled = healthCheckConfig.enabled !== false;
598
762
  const healthCheckPath = healthCheckConfig.path ?? "/health";
599
763
  const healthCheckDetailed = healthCheckConfig.detailed ?? process.env.NODE_ENV === "development";
@@ -602,15 +766,15 @@ function registerHealthCheckEndpoint(app, config2) {
602
766
  serverLogger.debug(`Health check endpoint enabled at ${healthCheckPath}`);
603
767
  }
604
768
  }
605
- async function executeBeforeRoutesHook(app, config2) {
606
- if (config2?.lifecycle?.beforeRoutes) {
607
- await config2.lifecycle.beforeRoutes(app);
769
+ async function executeBeforeRoutesHook(app, config) {
770
+ if (config?.lifecycle?.beforeRoutes) {
771
+ await config.lifecycle.beforeRoutes(app);
608
772
  }
609
773
  }
610
- async function loadAppRoutes(app, config2) {
611
- const debug = isDebugMode(config2);
612
- if (config2?.routes) {
613
- const routes = registerRoutes(app, config2.routes, config2.middlewares);
774
+ async function loadAppRoutes(app, config) {
775
+ const debug = isDebugMode(config);
776
+ if (config?.routes) {
777
+ const routes = registerRoutes(app, config.routes, config.middlewares);
614
778
  logRegisteredRoutes(routes, debug);
615
779
  } else if (debug) {
616
780
  serverLogger.warn("\u26A0\uFE0F No routes configured. Use defineServerConfig().routes() to register routes.");
@@ -631,28 +795,51 @@ function logRegisteredRoutes(routes, debug) {
631
795
  serverLogger.info(`\u2713 Routes registered (${routes.length}):
632
796
  ${routeLines}`);
633
797
  }
634
- async function executeAfterRoutesHook(app, config2) {
635
- if (config2?.lifecycle?.afterRoutes) {
636
- await config2.lifecycle.afterRoutes(app);
798
+ async function executeAfterRoutesHook(app, config) {
799
+ if (config?.lifecycle?.afterRoutes) {
800
+ await config.lifecycle.afterRoutes(app);
637
801
  }
638
802
  }
639
- function registerSSEEndpoint(app, config2) {
640
- if (!config2?.events) {
803
+ function registerSSEEndpoint(app, config) {
804
+ if (!config?.events) {
641
805
  return;
642
806
  }
643
- const eventsConfig = config2.eventsConfig ?? {};
644
- const path = eventsConfig.path ?? "/events/stream";
645
- const debug = isDebugMode(config2);
646
- app.get(path, createSSEHandler(config2.events, eventsConfig));
807
+ const eventsConfig = config.eventsConfig ?? {};
808
+ const streamPath = eventsConfig.path ?? "/events/stream";
809
+ const authConfig = eventsConfig.auth;
810
+ const debug = isDebugMode(config);
811
+ let tokenManager;
812
+ if (authConfig?.enabled) {
813
+ tokenManager = new SSETokenManager({
814
+ ttl: authConfig.tokenTtl,
815
+ store: authConfig.store
816
+ });
817
+ const tokenPath = streamPath.replace(/\/[^/]+$/, "/token");
818
+ const mwHandlers = (config.middlewares ?? []).map((mw) => mw.handler);
819
+ const getSubject = authConfig.getSubject ?? ((c) => c.get("auth")?.userId ?? null);
820
+ app.post(tokenPath, ...mwHandlers, async (c) => {
821
+ const subject = getSubject(c);
822
+ if (!subject) {
823
+ return c.json({ error: "Unable to identify subject" }, 401);
824
+ }
825
+ const token = await tokenManager.issue(subject);
826
+ return c.json({ token });
827
+ });
828
+ if (debug) {
829
+ serverLogger.info(`\u2713 SSE token endpoint registered at POST ${tokenPath}`);
830
+ }
831
+ }
832
+ app.get(streamPath, createSSEHandler(config.events, eventsConfig, tokenManager));
647
833
  if (debug) {
648
- const eventNames = config2.events.eventNames;
649
- serverLogger.info(`\u2713 SSE endpoint registered at ${path}`, {
650
- events: eventNames
834
+ const eventNames = config.events.eventNames;
835
+ serverLogger.info(`\u2713 SSE endpoint registered at ${streamPath}`, {
836
+ events: eventNames,
837
+ auth: !!authConfig?.enabled
651
838
  });
652
839
  }
653
840
  }
654
- function isDebugMode(config2) {
655
- return config2?.debug ?? process.env.NODE_ENV === "development";
841
+ function isDebugMode(config) {
842
+ return config?.debug ?? process.env.NODE_ENV === "development";
656
843
  }
657
844
  var jobLogger = logger.child("@spfn/core:job");
658
845
  var bossInstance = null;
@@ -888,16 +1075,16 @@ function printBanner(options) {
888
1075
  }
889
1076
 
890
1077
  // src/server/validation.ts
891
- function validateServerConfig(config2) {
892
- if (config2.port !== void 0) {
893
- if (!Number.isInteger(config2.port) || config2.port < 0 || config2.port > 65535) {
1078
+ function validateServerConfig(config) {
1079
+ if (config.port !== void 0) {
1080
+ if (!Number.isInteger(config.port) || config.port < 0 || config.port > 65535) {
894
1081
  throw new Error(
895
- `Invalid port: ${config2.port}. Port must be an integer between 0 and 65535.`
1082
+ `Invalid port: ${config.port}. Port must be an integer between 0 and 65535.`
896
1083
  );
897
1084
  }
898
1085
  }
899
- if (config2.timeout) {
900
- const { request, keepAlive, headers } = config2.timeout;
1086
+ if (config.timeout) {
1087
+ const { request, keepAlive, headers } = config.timeout;
901
1088
  if (request !== void 0 && (request < 0 || !Number.isFinite(request))) {
902
1089
  throw new Error(`Invalid timeout.request: ${request}. Must be a positive number.`);
903
1090
  }
@@ -913,16 +1100,16 @@ function validateServerConfig(config2) {
913
1100
  );
914
1101
  }
915
1102
  }
916
- if (config2.shutdown?.timeout !== void 0) {
917
- const timeout = config2.shutdown.timeout;
1103
+ if (config.shutdown?.timeout !== void 0) {
1104
+ const timeout = config.shutdown.timeout;
918
1105
  if (timeout < 0 || !Number.isFinite(timeout)) {
919
1106
  throw new Error(`Invalid shutdown.timeout: ${timeout}. Must be a positive number.`);
920
1107
  }
921
1108
  }
922
- if (config2.healthCheck?.path) {
923
- if (!config2.healthCheck.path.startsWith("/")) {
1109
+ if (config.healthCheck?.path) {
1110
+ if (!config.healthCheck.path.startsWith("/")) {
924
1111
  throw new Error(
925
- `Invalid healthCheck.path: "${config2.healthCheck.path}". Must start with "/".`
1112
+ `Invalid healthCheck.path: "${config.healthCheck.path}". Must start with "/".`
926
1113
  );
927
1114
  }
928
1115
  }
@@ -939,9 +1126,9 @@ var CONFIG_FILE_PATHS = [
939
1126
  "src/server/server.config.ts"
940
1127
  ];
941
1128
  var processHandlersRegistered = false;
942
- async function startServer(config2) {
943
- loadEnvFiles();
944
- const finalConfig = await loadAndMergeConfig(config2);
1129
+ async function startServer(config) {
1130
+ loadEnv();
1131
+ const finalConfig = await loadAndMergeConfig(config);
945
1132
  const { host, port, debug } = finalConfig;
946
1133
  validateServerConfig(finalConfig);
947
1134
  if (!host || !port) {
@@ -999,7 +1186,7 @@ async function startServer(config2) {
999
1186
  throw error;
1000
1187
  }
1001
1188
  }
1002
- async function loadAndMergeConfig(config2) {
1189
+ async function loadAndMergeConfig(config) {
1003
1190
  const cwd = process.cwd();
1004
1191
  let fileConfig = {};
1005
1192
  let loadedConfigPath = null;
@@ -1023,26 +1210,26 @@ async function loadAndMergeConfig(config2) {
1023
1210
  }
1024
1211
  return {
1025
1212
  ...fileConfig,
1026
- ...config2,
1027
- port: config2?.port ?? fileConfig?.port ?? env.PORT,
1028
- host: config2?.host ?? fileConfig?.host ?? env.HOST
1213
+ ...config,
1214
+ port: config?.port ?? fileConfig?.port ?? env.PORT,
1215
+ host: config?.host ?? fileConfig?.host ?? env.HOST
1029
1216
  };
1030
1217
  }
1031
- function getInfrastructureConfig(config2) {
1218
+ function getInfrastructureConfig(config) {
1032
1219
  return {
1033
- database: config2.infrastructure?.database !== false,
1034
- redis: config2.infrastructure?.redis !== false
1220
+ database: config.infrastructure?.database !== false,
1221
+ redis: config.infrastructure?.redis !== false
1035
1222
  };
1036
1223
  }
1037
- async function initializeInfrastructure(config2) {
1038
- if (config2.lifecycle?.beforeInfrastructure) {
1224
+ async function initializeInfrastructure(config) {
1225
+ if (config.lifecycle?.beforeInfrastructure) {
1039
1226
  serverLogger.debug("Executing beforeInfrastructure hook...");
1040
- await config2.lifecycle.beforeInfrastructure(config2);
1227
+ await config.lifecycle.beforeInfrastructure(config);
1041
1228
  }
1042
- const infraConfig = getInfrastructureConfig(config2);
1229
+ const infraConfig = getInfrastructureConfig(config);
1043
1230
  if (infraConfig.database) {
1044
1231
  serverLogger.debug("Initializing database...");
1045
- await initDatabase(config2.database);
1232
+ await initDatabase(config.database);
1046
1233
  } else {
1047
1234
  serverLogger.debug("Database initialization disabled");
1048
1235
  }
@@ -1052,11 +1239,11 @@ async function initializeInfrastructure(config2) {
1052
1239
  } else {
1053
1240
  serverLogger.debug("Redis initialization disabled");
1054
1241
  }
1055
- if (config2.lifecycle?.afterInfrastructure) {
1242
+ if (config.lifecycle?.afterInfrastructure) {
1056
1243
  serverLogger.debug("Executing afterInfrastructure hook...");
1057
- await config2.lifecycle.afterInfrastructure();
1244
+ await config.lifecycle.afterInfrastructure();
1058
1245
  }
1059
- if (config2.jobs) {
1246
+ if (config.jobs) {
1060
1247
  const dbUrl = env.DATABASE_URL;
1061
1248
  if (!dbUrl) {
1062
1249
  throw new Error(
@@ -1066,22 +1253,22 @@ async function initializeInfrastructure(config2) {
1066
1253
  serverLogger.debug("Initializing pg-boss...");
1067
1254
  await initBoss({
1068
1255
  connectionString: dbUrl,
1069
- ...config2.jobsConfig
1256
+ ...config.jobsConfig
1070
1257
  });
1071
1258
  serverLogger.debug("Registering jobs...");
1072
- await registerJobs(config2.jobs);
1259
+ await registerJobs(config.jobs);
1073
1260
  }
1074
- if (config2.workflows) {
1075
- const infraConfig2 = getInfrastructureConfig(config2);
1261
+ if (config.workflows) {
1262
+ const infraConfig2 = getInfrastructureConfig(config);
1076
1263
  if (!infraConfig2.database) {
1077
1264
  throw new Error(
1078
1265
  "Workflows require database connection. Ensure database is enabled in infrastructure config."
1079
1266
  );
1080
1267
  }
1081
1268
  serverLogger.debug("Initializing workflow engine...");
1082
- config2.workflows._init(
1269
+ config.workflows._init(
1083
1270
  getDatabase(),
1084
- config2.workflowsConfig
1271
+ config.workflowsConfig
1085
1272
  );
1086
1273
  serverLogger.info("Workflow engine initialized");
1087
1274
  }
@@ -1094,8 +1281,8 @@ function startHttpServer(app, host, port) {
1094
1281
  hostname: host
1095
1282
  });
1096
1283
  }
1097
- function logMiddlewareOrder(config2) {
1098
- const middlewareOrder = buildMiddlewareOrder(config2);
1284
+ function logMiddlewareOrder(config) {
1285
+ const middlewareOrder = buildMiddlewareOrder(config);
1099
1286
  serverLogger.debug("Middleware execution order", {
1100
1287
  order: middlewareOrder
1101
1288
  });
@@ -1107,8 +1294,8 @@ function logServerTimeouts(timeouts) {
1107
1294
  headers: `${timeouts.headers}ms`
1108
1295
  });
1109
1296
  }
1110
- function logServerStarted(debug, host, port, config2, timeouts) {
1111
- const startupConfig = buildStartupConfig(config2, timeouts);
1297
+ function logServerStarted(debug, host, port, config, timeouts) {
1298
+ const startupConfig = buildStartupConfig(config, timeouts);
1112
1299
  serverLogger.info("Server started successfully", {
1113
1300
  mode: debug ? "development" : "production",
1114
1301
  host,
@@ -1116,7 +1303,7 @@ function logServerStarted(debug, host, port, config2, timeouts) {
1116
1303
  config: startupConfig
1117
1304
  });
1118
1305
  }
1119
- function createShutdownHandler(server, config2, shutdownState) {
1306
+ function createShutdownHandler(server, config, shutdownState) {
1120
1307
  return async () => {
1121
1308
  if (shutdownState.isShuttingDown) {
1122
1309
  serverLogger.debug("Shutdown already in progress for this instance, skipping");
@@ -1147,7 +1334,7 @@ function createShutdownHandler(server, config2, shutdownState) {
1147
1334
  if (timeoutId) clearTimeout(timeoutId);
1148
1335
  serverLogger.warn("HTTP server close timeout, forcing shutdown", error);
1149
1336
  });
1150
- if (config2.jobs) {
1337
+ if (config.jobs) {
1151
1338
  serverLogger.debug("Stopping pg-boss...");
1152
1339
  try {
1153
1340
  await stopBoss();
@@ -1155,15 +1342,15 @@ function createShutdownHandler(server, config2, shutdownState) {
1155
1342
  serverLogger.error("pg-boss stop failed", error);
1156
1343
  }
1157
1344
  }
1158
- if (config2.lifecycle?.beforeShutdown) {
1345
+ if (config.lifecycle?.beforeShutdown) {
1159
1346
  serverLogger.debug("Executing beforeShutdown hook...");
1160
1347
  try {
1161
- await config2.lifecycle.beforeShutdown();
1348
+ await config.lifecycle.beforeShutdown();
1162
1349
  } catch (error) {
1163
1350
  serverLogger.error("beforeShutdown hook failed", error);
1164
1351
  }
1165
1352
  }
1166
- const infraConfig = getInfrastructureConfig(config2);
1353
+ const infraConfig = getInfrastructureConfig(config);
1167
1354
  if (infraConfig.database) {
1168
1355
  serverLogger.debug("Closing database connections...");
1169
1356
  await closeInfrastructure(closeDatabase, "Database", TIMEOUTS.DATABASE_CLOSE);
@@ -1194,14 +1381,14 @@ async function closeInfrastructure(closeFn, name, timeout) {
1194
1381
  serverLogger.error(`${name} close failed or timed out`, error);
1195
1382
  }
1196
1383
  }
1197
- function createGracefulShutdown(shutdownServer, config2, shutdownState) {
1384
+ function createGracefulShutdown(shutdownServer, config, shutdownState) {
1198
1385
  return async (signal) => {
1199
1386
  if (shutdownState.isShuttingDown) {
1200
1387
  serverLogger.warn(`${signal} received but shutdown already in progress, ignoring`);
1201
1388
  return;
1202
1389
  }
1203
1390
  serverLogger.info(`${signal} received, starting graceful shutdown...`);
1204
- const shutdownTimeout = getShutdownTimeout(config2.shutdown);
1391
+ const shutdownTimeout = getShutdownTimeout(config.shutdown);
1205
1392
  let timeoutId;
1206
1393
  try {
1207
1394
  await Promise.race([
@@ -1287,10 +1474,10 @@ function registerProcessHandlers(shutdown) {
1287
1474
  });
1288
1475
  serverLogger.debug("Process-level shutdown handlers registered successfully");
1289
1476
  }
1290
- async function cleanupOnFailure(config2) {
1477
+ async function cleanupOnFailure(config) {
1291
1478
  try {
1292
1479
  serverLogger.debug("Cleaning up after initialization failure...");
1293
- const infraConfig = getInfrastructureConfig(config2);
1480
+ const infraConfig = getInfrastructureConfig(config);
1294
1481
  if (infraConfig.database) {
1295
1482
  await closeInfrastructure(closeDatabase, "Database", TIMEOUTS.DATABASE_CLOSE);
1296
1483
  }
@@ -1422,10 +1609,10 @@ var ServerConfigBuilder = class {
1422
1609
  * .build();
1423
1610
  * ```
1424
1611
  */
1425
- jobs(router, config2) {
1612
+ jobs(router, config) {
1426
1613
  this.config.jobs = router;
1427
- if (config2) {
1428
- this.config.jobsConfig = config2;
1614
+ if (config) {
1615
+ this.config.jobsConfig = config;
1429
1616
  }
1430
1617
  return this;
1431
1618
  }
@@ -1457,10 +1644,10 @@ var ServerConfigBuilder = class {
1457
1644
  * .events(eventRouter, { path: '/sse' })
1458
1645
  * ```
1459
1646
  */
1460
- events(router, config2) {
1647
+ events(router, config) {
1461
1648
  this.config.events = router;
1462
- if (config2) {
1463
- this.config.eventsConfig = config2;
1649
+ if (config) {
1650
+ this.config.eventsConfig = config;
1464
1651
  }
1465
1652
  return this;
1466
1653
  }
@@ -1526,10 +1713,10 @@ var ServerConfigBuilder = class {
1526
1713
  * .build();
1527
1714
  * ```
1528
1715
  */
1529
- workflows(router, config2) {
1716
+ workflows(router, config) {
1530
1717
  this.config.workflows = router;
1531
- if (config2) {
1532
- this.config.workflowsConfig = config2;
1718
+ if (config) {
1719
+ this.config.workflowsConfig = config;
1533
1720
  }
1534
1721
  return this;
1535
1722
  }
@@ -1580,6 +1767,6 @@ function defineServerConfig() {
1580
1767
  return new ServerConfigBuilder();
1581
1768
  }
1582
1769
 
1583
- export { createServer, defineServerConfig, loadEnvFiles, startServer };
1770
+ export { createServer, defineServerConfig, loadEnv, loadEnvFiles, startServer };
1584
1771
  //# sourceMappingURL=index.js.map
1585
1772
  //# sourceMappingURL=index.js.map