@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 +135 -12
- package/dist/index.js.map +1 -1
- package/dist/server/index.d.ts +53 -2
- package/dist/server/index.js +135 -12
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
3639
|
+
serverLogger2.info("Exiting immediately for clean restart");
|
|
3640
|
+
process.exit(1);
|
|
3518
3641
|
});
|
|
3519
3642
|
}
|
|
3520
3643
|
async function cleanupOnFailure(config) {
|