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

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
@@ -3116,29 +3116,133 @@ function buildStartupConfig(config, timeouts) {
3116
3116
  };
3117
3117
  }
3118
3118
 
3119
+ // src/server/plugin-discovery.ts
3120
+ init_logger2();
3121
+ var pluginLogger = logger.child("plugin");
3122
+ async function discoverPlugins(cwd = process.cwd()) {
3123
+ const plugins = [];
3124
+ const nodeModulesPath = join(cwd, "node_modules");
3125
+ try {
3126
+ const projectPkgPath = join(cwd, "package.json");
3127
+ if (!existsSync(projectPkgPath)) {
3128
+ pluginLogger.debug("No package.json found, skipping plugin discovery");
3129
+ return plugins;
3130
+ }
3131
+ const projectPkg = JSON.parse(readFileSync(projectPkgPath, "utf-8"));
3132
+ const dependencies = {
3133
+ ...projectPkg.dependencies,
3134
+ ...projectPkg.devDependencies
3135
+ };
3136
+ for (const [packageName] of Object.entries(dependencies)) {
3137
+ if (!packageName.startsWith("@spfn/")) {
3138
+ continue;
3139
+ }
3140
+ try {
3141
+ const plugin = await loadPluginFromPackage(packageName, nodeModulesPath);
3142
+ if (plugin) {
3143
+ plugins.push(plugin);
3144
+ pluginLogger.info("Plugin discovered", {
3145
+ name: plugin.name,
3146
+ hooks: getPluginHookNames(plugin)
3147
+ });
3148
+ }
3149
+ } catch (error) {
3150
+ pluginLogger.debug("Failed to load plugin", {
3151
+ package: packageName,
3152
+ error: error instanceof Error ? error.message : "Unknown error"
3153
+ });
3154
+ }
3155
+ }
3156
+ } catch (error) {
3157
+ pluginLogger.warn("Plugin discovery failed", {
3158
+ error: error instanceof Error ? error.message : "Unknown error"
3159
+ });
3160
+ }
3161
+ return plugins;
3162
+ }
3163
+ async function loadPluginFromPackage(packageName, nodeModulesPath) {
3164
+ const pkgPath = join(nodeModulesPath, ...packageName.split("/"), "package.json");
3165
+ if (!existsSync(pkgPath)) {
3166
+ return null;
3167
+ }
3168
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
3169
+ const packageDir = dirname(pkgPath);
3170
+ const mainEntry = pkg.main || "dist/index.js";
3171
+ const mainPath = join(packageDir, mainEntry);
3172
+ if (!existsSync(mainPath)) {
3173
+ return null;
3174
+ }
3175
+ try {
3176
+ const module = await import(mainPath);
3177
+ if (module.spfnPlugin && isValidPlugin(module.spfnPlugin)) {
3178
+ return module.spfnPlugin;
3179
+ }
3180
+ return null;
3181
+ } catch (error) {
3182
+ return null;
3183
+ }
3184
+ }
3185
+ function isValidPlugin(plugin) {
3186
+ 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");
3187
+ }
3188
+ function getPluginHookNames(plugin) {
3189
+ const hooks = [];
3190
+ if (plugin.afterInfrastructure) hooks.push("afterInfrastructure");
3191
+ if (plugin.beforeRoutes) hooks.push("beforeRoutes");
3192
+ if (plugin.afterRoutes) hooks.push("afterRoutes");
3193
+ if (plugin.afterStart) hooks.push("afterStart");
3194
+ if (plugin.beforeShutdown) hooks.push("beforeShutdown");
3195
+ return hooks;
3196
+ }
3197
+ async function executePluginHooks(plugins, hookName, ...args) {
3198
+ for (const plugin of plugins) {
3199
+ const hook = plugin[hookName];
3200
+ if (typeof hook === "function") {
3201
+ try {
3202
+ pluginLogger.debug("Executing plugin hook", {
3203
+ plugin: plugin.name,
3204
+ hook: hookName
3205
+ });
3206
+ await hook(...args);
3207
+ } catch (error) {
3208
+ pluginLogger.error("Plugin hook failed", {
3209
+ plugin: plugin.name,
3210
+ hook: hookName,
3211
+ error: error instanceof Error ? error.message : "Unknown error"
3212
+ });
3213
+ throw new Error(
3214
+ `Plugin ${plugin.name} failed in ${hookName} hook: ${error instanceof Error ? error.message : "Unknown error"}`
3215
+ );
3216
+ }
3217
+ }
3218
+ }
3219
+ }
3220
+
3119
3221
  // src/server/create-server.ts
3120
3222
  var serverLogger = logger.child("server");
3121
- async function createServer(config) {
3223
+ async function createServer(config, plugins = []) {
3122
3224
  const cwd = process.cwd();
3123
3225
  const appPath = join(cwd, "src", "server", "app.ts");
3124
3226
  const appJsPath = join(cwd, "src", "server", "app.js");
3125
3227
  if (existsSync(appPath) || existsSync(appJsPath)) {
3126
- return await loadCustomApp(appPath, appJsPath, config);
3228
+ return await loadCustomApp(appPath, appJsPath, config, plugins);
3127
3229
  }
3128
- return await createAutoConfiguredApp(config);
3230
+ return await createAutoConfiguredApp(config, plugins);
3129
3231
  }
3130
- async function loadCustomApp(appPath, appJsPath, config) {
3232
+ async function loadCustomApp(appPath, appJsPath, config, plugins = []) {
3131
3233
  const appModule = await (existsSync(appPath) ? import(appPath) : import(appJsPath));
3132
3234
  const appFactory = appModule.default;
3133
3235
  if (!appFactory) {
3134
3236
  throw new Error("app.ts must export a default function that returns a Hono app");
3135
3237
  }
3136
3238
  const app = await appFactory();
3239
+ await executePluginHooks(plugins, "beforeRoutes", app);
3137
3240
  const debug = config?.debug ?? process.env.NODE_ENV === "development";
3138
3241
  await loadRoutes(app, { routesDir: config?.routesPath, debug });
3242
+ await executePluginHooks(plugins, "afterRoutes", app);
3139
3243
  return app;
3140
3244
  }
3141
- async function createAutoConfiguredApp(config) {
3245
+ async function createAutoConfiguredApp(config, plugins = []) {
3142
3246
  const app = new Hono();
3143
3247
  const middlewareConfig = config?.middleware ?? {};
3144
3248
  const enableLogger = middlewareConfig.logger !== false;
@@ -3154,8 +3258,10 @@ async function createAutoConfiguredApp(config) {
3154
3258
  config?.use?.forEach((mw) => app.use("*", mw));
3155
3259
  registerHealthCheckEndpoint(app, config);
3156
3260
  await executeBeforeRoutesHook(app, config);
3261
+ await executePluginHooks(plugins, "beforeRoutes", app);
3157
3262
  await loadAppRoutes(app, config);
3158
3263
  await executeAfterRoutesHook(app, config);
3264
+ await executePluginHooks(plugins, "afterRoutes", app);
3159
3265
  if (enableErrorHandler) {
3160
3266
  app.onError(ErrorHandler());
3161
3267
  }
@@ -3300,9 +3406,17 @@ async function startServer(config) {
3300
3406
  if (debug) {
3301
3407
  logMiddlewareOrder(finalConfig);
3302
3408
  }
3409
+ serverLogger2.debug("Discovering plugins...");
3410
+ const plugins = await discoverPlugins();
3411
+ if (plugins.length > 0) {
3412
+ serverLogger2.info("Plugins discovered", {
3413
+ count: plugins.length,
3414
+ plugins: plugins.map((p) => p.name)
3415
+ });
3416
+ }
3303
3417
  try {
3304
- await initializeInfrastructure(finalConfig);
3305
- const app = await createServer(finalConfig);
3418
+ await initializeInfrastructure(finalConfig, plugins);
3419
+ const app = await createServer(finalConfig, plugins);
3306
3420
  const server = startHttpServer(app, host, port);
3307
3421
  const timeouts = getTimeoutConfig(finalConfig.timeout);
3308
3422
  applyServerTimeouts(server, timeouts);
@@ -3313,7 +3427,7 @@ async function startServer(config) {
3313
3427
  port
3314
3428
  });
3315
3429
  logServerStarted(debug, host, port, finalConfig, timeouts);
3316
- const shutdownServer = createShutdownHandler(server, finalConfig);
3430
+ const shutdownServer = createShutdownHandler(server, finalConfig, plugins);
3317
3431
  const shutdown = createGracefulShutdown(shutdownServer, finalConfig);
3318
3432
  registerShutdownHandlers(shutdown);
3319
3433
  const serverInstance = {
@@ -3333,6 +3447,7 @@ async function startServer(config) {
3333
3447
  serverLogger2.error("afterStart hook failed", error);
3334
3448
  }
3335
3449
  }
3450
+ await executePluginHooks(plugins, "afterStart", serverInstance);
3336
3451
  return serverInstance;
3337
3452
  } catch (error) {
3338
3453
  const err = error;
@@ -3374,7 +3489,7 @@ function logMiddlewareOrder(config) {
3374
3489
  order: middlewareOrder
3375
3490
  });
3376
3491
  }
3377
- async function initializeInfrastructure(config) {
3492
+ async function initializeInfrastructure(config, plugins) {
3378
3493
  if (config.lifecycle?.beforeInfrastructure) {
3379
3494
  serverLogger2.debug("Executing beforeInfrastructure hook...");
3380
3495
  try {
@@ -3407,6 +3522,7 @@ async function initializeInfrastructure(config) {
3407
3522
  throw new Error("Server initialization failed in afterInfrastructure hook");
3408
3523
  }
3409
3524
  }
3525
+ await executePluginHooks(plugins, "afterInfrastructure");
3410
3526
  }
3411
3527
  function startHttpServer(app, host, port) {
3412
3528
  serverLogger2.debug(`Starting server on ${host}:${port}...`);
@@ -3433,7 +3549,7 @@ function logServerStarted(debug, host, port, config, timeouts) {
3433
3549
  config: startupConfig
3434
3550
  });
3435
3551
  }
3436
- function createShutdownHandler(server, config) {
3552
+ function createShutdownHandler(server, config, plugins) {
3437
3553
  return async () => {
3438
3554
  serverLogger2.debug("Closing HTTP server...");
3439
3555
  await new Promise((resolve) => {
@@ -3450,6 +3566,11 @@ function createShutdownHandler(server, config) {
3450
3566
  serverLogger2.error("beforeShutdown hook failed", error);
3451
3567
  }
3452
3568
  }
3569
+ try {
3570
+ await executePluginHooks(plugins, "beforeShutdown");
3571
+ } catch (error) {
3572
+ serverLogger2.error("Plugin beforeShutdown hooks failed", error);
3573
+ }
3453
3574
  const shouldCloseDatabase = config.infrastructure?.database !== false;
3454
3575
  const shouldCloseRedis = config.infrastructure?.redis !== false;
3455
3576
  if (shouldCloseDatabase) {
@@ -3507,14 +3628,16 @@ function registerShutdownHandlers(shutdown) {
3507
3628
  } else {
3508
3629
  serverLogger2.error("Uncaught exception", error);
3509
3630
  }
3510
- shutdown("UNCAUGHT_EXCEPTION");
3631
+ serverLogger2.info("Exiting immediately for clean restart");
3632
+ process.exit(1);
3511
3633
  });
3512
3634
  process.on("unhandledRejection", (reason, promise) => {
3513
3635
  serverLogger2.error("Unhandled promise rejection", {
3514
3636
  reason,
3515
3637
  promise
3516
3638
  });
3517
- shutdown("UNHANDLED_REJECTION");
3639
+ serverLogger2.info("Exiting immediately for clean restart");
3640
+ process.exit(1);
3518
3641
  });
3519
3642
  }
3520
3643
  async function cleanupOnFailure(config) {