@spfn/core 0.1.0-alpha.84 → 0.1.0-alpha.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -104,7 +104,18 @@ function formatConsole(metadata, colorize = true) {
104
104
  }
105
105
  if (metadata.context && Object.keys(metadata.context).length > 0) {
106
106
  Object.entries(metadata.context).forEach(([key, value]) => {
107
- const valueStr = typeof value === "string" ? value : String(value);
107
+ let valueStr;
108
+ if (typeof value === "string") {
109
+ valueStr = value;
110
+ } else if (typeof value === "object" && value !== null) {
111
+ try {
112
+ valueStr = JSON.stringify(value);
113
+ } catch (error) {
114
+ valueStr = "[circular]";
115
+ }
116
+ } else {
117
+ valueStr = String(value);
118
+ }
108
119
  if (colorize) {
109
120
  parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`);
110
121
  } else {
@@ -2851,6 +2862,7 @@ async function loadRoutes(app, options) {
2851
2862
 
2852
2863
  // src/route/bind.ts
2853
2864
  init_errors();
2865
+ init_logger2();
2854
2866
 
2855
2867
  // src/middleware/error-handler.ts
2856
2868
  init_logger2();
@@ -3116,29 +3128,133 @@ function buildStartupConfig(config, timeouts) {
3116
3128
  };
3117
3129
  }
3118
3130
 
3131
+ // src/server/plugin-discovery.ts
3132
+ init_logger2();
3133
+ var pluginLogger = logger.child("plugin");
3134
+ async function discoverPlugins(cwd = process.cwd()) {
3135
+ const plugins = [];
3136
+ const nodeModulesPath = join(cwd, "node_modules");
3137
+ try {
3138
+ const projectPkgPath = join(cwd, "package.json");
3139
+ if (!existsSync(projectPkgPath)) {
3140
+ pluginLogger.debug("No package.json found, skipping plugin discovery");
3141
+ return plugins;
3142
+ }
3143
+ const projectPkg = JSON.parse(readFileSync(projectPkgPath, "utf-8"));
3144
+ const dependencies = {
3145
+ ...projectPkg.dependencies,
3146
+ ...projectPkg.devDependencies
3147
+ };
3148
+ for (const [packageName] of Object.entries(dependencies)) {
3149
+ if (!packageName.startsWith("@spfn/")) {
3150
+ continue;
3151
+ }
3152
+ try {
3153
+ const plugin = await loadPluginFromPackage(packageName, nodeModulesPath);
3154
+ if (plugin) {
3155
+ plugins.push(plugin);
3156
+ pluginLogger.info("Plugin discovered", {
3157
+ name: plugin.name,
3158
+ hooks: getPluginHookNames(plugin)
3159
+ });
3160
+ }
3161
+ } catch (error) {
3162
+ pluginLogger.debug("Failed to load plugin", {
3163
+ package: packageName,
3164
+ error: error instanceof Error ? error.message : "Unknown error"
3165
+ });
3166
+ }
3167
+ }
3168
+ } catch (error) {
3169
+ pluginLogger.warn("Plugin discovery failed", {
3170
+ error: error instanceof Error ? error.message : "Unknown error"
3171
+ });
3172
+ }
3173
+ return plugins;
3174
+ }
3175
+ async function loadPluginFromPackage(packageName, nodeModulesPath) {
3176
+ const pkgPath = join(nodeModulesPath, ...packageName.split("/"), "package.json");
3177
+ if (!existsSync(pkgPath)) {
3178
+ return null;
3179
+ }
3180
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
3181
+ const packageDir = dirname(pkgPath);
3182
+ const mainEntry = pkg.main || "dist/index.js";
3183
+ const mainPath = join(packageDir, mainEntry);
3184
+ if (!existsSync(mainPath)) {
3185
+ return null;
3186
+ }
3187
+ try {
3188
+ const module = await import(mainPath);
3189
+ if (module.spfnPlugin && isValidPlugin(module.spfnPlugin)) {
3190
+ return module.spfnPlugin;
3191
+ }
3192
+ return null;
3193
+ } catch (error) {
3194
+ return null;
3195
+ }
3196
+ }
3197
+ function isValidPlugin(plugin) {
3198
+ return plugin && typeof plugin === "object" && typeof plugin.name === "string" && (typeof plugin.afterInfrastructure === "function" || typeof plugin.beforeRoutes === "function" || typeof plugin.afterRoutes === "function" || typeof plugin.afterStart === "function" || typeof plugin.beforeShutdown === "function");
3199
+ }
3200
+ function getPluginHookNames(plugin) {
3201
+ const hooks = [];
3202
+ if (plugin.afterInfrastructure) hooks.push("afterInfrastructure");
3203
+ if (plugin.beforeRoutes) hooks.push("beforeRoutes");
3204
+ if (plugin.afterRoutes) hooks.push("afterRoutes");
3205
+ if (plugin.afterStart) hooks.push("afterStart");
3206
+ if (plugin.beforeShutdown) hooks.push("beforeShutdown");
3207
+ return hooks;
3208
+ }
3209
+ async function executePluginHooks(plugins, hookName, ...args) {
3210
+ for (const plugin of plugins) {
3211
+ const hook = plugin[hookName];
3212
+ if (typeof hook === "function") {
3213
+ try {
3214
+ pluginLogger.debug("Executing plugin hook", {
3215
+ plugin: plugin.name,
3216
+ hook: hookName
3217
+ });
3218
+ await hook(...args);
3219
+ } catch (error) {
3220
+ pluginLogger.error("Plugin hook failed", {
3221
+ plugin: plugin.name,
3222
+ hook: hookName,
3223
+ error: error instanceof Error ? error.message : "Unknown error"
3224
+ });
3225
+ throw new Error(
3226
+ `Plugin ${plugin.name} failed in ${hookName} hook: ${error instanceof Error ? error.message : "Unknown error"}`
3227
+ );
3228
+ }
3229
+ }
3230
+ }
3231
+ }
3232
+
3119
3233
  // src/server/create-server.ts
3120
3234
  var serverLogger = logger.child("server");
3121
- async function createServer(config) {
3235
+ async function createServer(config, plugins = []) {
3122
3236
  const cwd = process.cwd();
3123
3237
  const appPath = join(cwd, "src", "server", "app.ts");
3124
3238
  const appJsPath = join(cwd, "src", "server", "app.js");
3125
3239
  if (existsSync(appPath) || existsSync(appJsPath)) {
3126
- return await loadCustomApp(appPath, appJsPath, config);
3240
+ return await loadCustomApp(appPath, appJsPath, config, plugins);
3127
3241
  }
3128
- return await createAutoConfiguredApp(config);
3242
+ return await createAutoConfiguredApp(config, plugins);
3129
3243
  }
3130
- async function loadCustomApp(appPath, appJsPath, config) {
3244
+ async function loadCustomApp(appPath, appJsPath, config, plugins = []) {
3131
3245
  const appModule = await (existsSync(appPath) ? import(appPath) : import(appJsPath));
3132
3246
  const appFactory = appModule.default;
3133
3247
  if (!appFactory) {
3134
3248
  throw new Error("app.ts must export a default function that returns a Hono app");
3135
3249
  }
3136
3250
  const app = await appFactory();
3251
+ await executePluginHooks(plugins, "beforeRoutes", app);
3137
3252
  const debug = config?.debug ?? process.env.NODE_ENV === "development";
3138
3253
  await loadRoutes(app, { routesDir: config?.routesPath, debug });
3254
+ await executePluginHooks(plugins, "afterRoutes", app);
3139
3255
  return app;
3140
3256
  }
3141
- async function createAutoConfiguredApp(config) {
3257
+ async function createAutoConfiguredApp(config, plugins = []) {
3142
3258
  const app = new Hono();
3143
3259
  const middlewareConfig = config?.middleware ?? {};
3144
3260
  const enableLogger = middlewareConfig.logger !== false;
@@ -3154,8 +3270,10 @@ async function createAutoConfiguredApp(config) {
3154
3270
  config?.use?.forEach((mw) => app.use("*", mw));
3155
3271
  registerHealthCheckEndpoint(app, config);
3156
3272
  await executeBeforeRoutesHook(app, config);
3273
+ await executePluginHooks(plugins, "beforeRoutes", app);
3157
3274
  await loadAppRoutes(app, config);
3158
3275
  await executeAfterRoutesHook(app, config);
3276
+ await executePluginHooks(plugins, "afterRoutes", app);
3159
3277
  if (enableErrorHandler) {
3160
3278
  app.onError(ErrorHandler());
3161
3279
  }
@@ -3300,9 +3418,17 @@ async function startServer(config) {
3300
3418
  if (debug) {
3301
3419
  logMiddlewareOrder(finalConfig);
3302
3420
  }
3421
+ serverLogger2.debug("Discovering plugins...");
3422
+ const plugins = await discoverPlugins();
3423
+ if (plugins.length > 0) {
3424
+ serverLogger2.info("Plugins discovered", {
3425
+ count: plugins.length,
3426
+ plugins: plugins.map((p) => p.name)
3427
+ });
3428
+ }
3303
3429
  try {
3304
- await initializeInfrastructure(finalConfig);
3305
- const app = await createServer(finalConfig);
3430
+ await initializeInfrastructure(finalConfig, plugins);
3431
+ const app = await createServer(finalConfig, plugins);
3306
3432
  const server = startHttpServer(app, host, port);
3307
3433
  const timeouts = getTimeoutConfig(finalConfig.timeout);
3308
3434
  applyServerTimeouts(server, timeouts);
@@ -3313,7 +3439,7 @@ async function startServer(config) {
3313
3439
  port
3314
3440
  });
3315
3441
  logServerStarted(debug, host, port, finalConfig, timeouts);
3316
- const shutdownServer = createShutdownHandler(server, finalConfig);
3442
+ const shutdownServer = createShutdownHandler(server, finalConfig, plugins);
3317
3443
  const shutdown = createGracefulShutdown(shutdownServer, finalConfig);
3318
3444
  registerShutdownHandlers(shutdown);
3319
3445
  const serverInstance = {
@@ -3333,6 +3459,7 @@ async function startServer(config) {
3333
3459
  serverLogger2.error("afterStart hook failed", error);
3334
3460
  }
3335
3461
  }
3462
+ await executePluginHooks(plugins, "afterStart", serverInstance);
3336
3463
  return serverInstance;
3337
3464
  } catch (error) {
3338
3465
  const err = error;
@@ -3374,7 +3501,7 @@ function logMiddlewareOrder(config) {
3374
3501
  order: middlewareOrder
3375
3502
  });
3376
3503
  }
3377
- async function initializeInfrastructure(config) {
3504
+ async function initializeInfrastructure(config, plugins) {
3378
3505
  if (config.lifecycle?.beforeInfrastructure) {
3379
3506
  serverLogger2.debug("Executing beforeInfrastructure hook...");
3380
3507
  try {
@@ -3407,6 +3534,7 @@ async function initializeInfrastructure(config) {
3407
3534
  throw new Error("Server initialization failed in afterInfrastructure hook");
3408
3535
  }
3409
3536
  }
3537
+ await executePluginHooks(plugins, "afterInfrastructure");
3410
3538
  }
3411
3539
  function startHttpServer(app, host, port) {
3412
3540
  serverLogger2.debug(`Starting server on ${host}:${port}...`);
@@ -3433,7 +3561,7 @@ function logServerStarted(debug, host, port, config, timeouts) {
3433
3561
  config: startupConfig
3434
3562
  });
3435
3563
  }
3436
- function createShutdownHandler(server, config) {
3564
+ function createShutdownHandler(server, config, plugins) {
3437
3565
  return async () => {
3438
3566
  serverLogger2.debug("Closing HTTP server...");
3439
3567
  await new Promise((resolve) => {
@@ -3450,6 +3578,11 @@ function createShutdownHandler(server, config) {
3450
3578
  serverLogger2.error("beforeShutdown hook failed", error);
3451
3579
  }
3452
3580
  }
3581
+ try {
3582
+ await executePluginHooks(plugins, "beforeShutdown");
3583
+ } catch (error) {
3584
+ serverLogger2.error("Plugin beforeShutdown hooks failed", error);
3585
+ }
3453
3586
  const shouldCloseDatabase = config.infrastructure?.database !== false;
3454
3587
  const shouldCloseRedis = config.infrastructure?.redis !== false;
3455
3588
  if (shouldCloseDatabase) {
@@ -3507,14 +3640,16 @@ function registerShutdownHandlers(shutdown) {
3507
3640
  } else {
3508
3641
  serverLogger2.error("Uncaught exception", error);
3509
3642
  }
3510
- shutdown("UNCAUGHT_EXCEPTION");
3643
+ serverLogger2.info("Exiting immediately for clean restart");
3644
+ process.exit(1);
3511
3645
  });
3512
3646
  process.on("unhandledRejection", (reason, promise) => {
3513
3647
  serverLogger2.error("Unhandled promise rejection", {
3514
3648
  reason,
3515
3649
  promise
3516
3650
  });
3517
- shutdown("UNHANDLED_REJECTION");
3651
+ serverLogger2.info("Exiting immediately for clean restart");
3652
+ process.exit(1);
3518
3653
  });
3519
3654
  }
3520
3655
  async function cleanupOnFailure(config) {