blaizejs 0.1.0 → 0.2.0
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/LICENSE +21 -0
- package/README.md +746 -198
- package/dist/index.cjs +314 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +314 -40
- package/dist/index.js.map +1 -1
- package/package.json +23 -24
package/dist/index.d.cts
CHANGED
|
@@ -15,7 +15,7 @@ declare function compose(middlewareStack: Middleware[]): MiddlewareFunction;
|
|
|
15
15
|
/**
|
|
16
16
|
* Create a plugin with the given name, version, and setup function
|
|
17
17
|
*/
|
|
18
|
-
declare function create$1<T = any>(name: string, version: string, setup: (app: Server, options: T) => void | PluginHooks | Promise<void> | Promise<PluginHooks
|
|
18
|
+
declare function create$1<T = any>(name: string, version: string, setup: (app: Server, options: T) => void | Partial<PluginHooks> | Promise<void> | Promise<Partial<PluginHooks>>, defaultOptions?: Partial<T>): PluginFactory<T>;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Create a GET route
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ declare function compose(middlewareStack: Middleware[]): MiddlewareFunction;
|
|
|
15
15
|
/**
|
|
16
16
|
* Create a plugin with the given name, version, and setup function
|
|
17
17
|
*/
|
|
18
|
-
declare function create$1<T = any>(name: string, version: string, setup: (app: Server, options: T) => void | PluginHooks | Promise<void> | Promise<PluginHooks
|
|
18
|
+
declare function create$1<T = any>(name: string, version: string, setup: (app: Server, options: T) => void | Partial<PluginHooks> | Promise<void> | Promise<Partial<PluginHooks>>, defaultOptions?: Partial<T>): PluginFactory<T>;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Create a GET route
|
package/dist/index.js
CHANGED
|
@@ -80,8 +80,31 @@ function compose(middlewareStack) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// src/plugins/create.ts
|
|
83
|
-
function create2(name, version, setup,
|
|
84
|
-
|
|
83
|
+
function create2(name, version, setup, defaultOptions = {}) {
|
|
84
|
+
if (!name || typeof name !== "string") {
|
|
85
|
+
throw new Error("Plugin name must be a non-empty string");
|
|
86
|
+
}
|
|
87
|
+
if (!version || typeof version !== "string") {
|
|
88
|
+
throw new Error("Plugin version must be a non-empty string");
|
|
89
|
+
}
|
|
90
|
+
if (typeof setup !== "function") {
|
|
91
|
+
throw new Error("Plugin setup must be a function");
|
|
92
|
+
}
|
|
93
|
+
return function pluginFactory(userOptions) {
|
|
94
|
+
const mergedOptions = { ...defaultOptions, ...userOptions };
|
|
95
|
+
const plugin = {
|
|
96
|
+
name,
|
|
97
|
+
version,
|
|
98
|
+
// The register hook calls the user's setup function
|
|
99
|
+
register: async (app) => {
|
|
100
|
+
const result = await setup(app, mergedOptions);
|
|
101
|
+
if (result && typeof result === "object") {
|
|
102
|
+
Object.assign(plugin, result);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
return plugin;
|
|
107
|
+
};
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
// src/router/discovery/finder.ts
|
|
@@ -121,12 +144,18 @@ async function findRouteFiles(routesDir, options = {}) {
|
|
|
121
144
|
return routeFiles;
|
|
122
145
|
}
|
|
123
146
|
function isRouteFile(filename) {
|
|
124
|
-
return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"))
|
|
147
|
+
return !filename.startsWith("_") && (filename.endsWith(".ts") || filename.endsWith(".js"));
|
|
125
148
|
}
|
|
126
149
|
|
|
127
150
|
// src/router/discovery/parser.ts
|
|
128
151
|
import * as path2 from "node:path";
|
|
129
152
|
function parseRoutePath(filePath, basePath) {
|
|
153
|
+
if (filePath.startsWith("file://")) {
|
|
154
|
+
filePath = filePath.replace("file://", "");
|
|
155
|
+
}
|
|
156
|
+
if (basePath.startsWith("file://")) {
|
|
157
|
+
basePath = basePath.replace("file://", "");
|
|
158
|
+
}
|
|
130
159
|
const forwardSlashFilePath = filePath.replace(/\\/g, "/");
|
|
131
160
|
const forwardSlashBasePath = basePath.replace(/\\/g, "/");
|
|
132
161
|
const normalizedBasePath = forwardSlashBasePath.endsWith("/") ? forwardSlashBasePath : `${forwardSlashBasePath}/`;
|
|
@@ -660,21 +689,56 @@ function createRouter(options) {
|
|
|
660
689
|
const matcher = createMatcher();
|
|
661
690
|
let initialized = false;
|
|
662
691
|
let initializationPromise = null;
|
|
663
|
-
let
|
|
692
|
+
let _watchers = null;
|
|
693
|
+
const routeSources = /* @__PURE__ */ new Map();
|
|
694
|
+
const routeDirectories = /* @__PURE__ */ new Set([routerOptions.routesDir]);
|
|
695
|
+
function addRouteWithSource(route, source) {
|
|
696
|
+
const existingSources = routeSources.get(route.path) || [];
|
|
697
|
+
if (existingSources.includes(source)) {
|
|
698
|
+
console.warn(`Skipping duplicate route: ${route.path} from ${source}`);
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (existingSources.length > 0) {
|
|
702
|
+
const conflictError = new Error(
|
|
703
|
+
`Route conflict for path "${route.path}": already defined in ${existingSources.join(", ")}, now being added from ${source}`
|
|
704
|
+
);
|
|
705
|
+
console.error(conflictError.message);
|
|
706
|
+
throw conflictError;
|
|
707
|
+
}
|
|
708
|
+
routeSources.set(route.path, [...existingSources, source]);
|
|
709
|
+
addRouteInternal(route);
|
|
710
|
+
}
|
|
711
|
+
async function loadRoutesFromDirectory(directory, source, prefix) {
|
|
712
|
+
try {
|
|
713
|
+
const discoveredRoutes = await findRoutes(directory, {
|
|
714
|
+
basePath: routerOptions.basePath
|
|
715
|
+
});
|
|
716
|
+
for (const route of discoveredRoutes) {
|
|
717
|
+
const finalRoute = prefix ? {
|
|
718
|
+
...route,
|
|
719
|
+
path: `${prefix}${route.path}`
|
|
720
|
+
} : route;
|
|
721
|
+
addRouteWithSource(finalRoute, source);
|
|
722
|
+
}
|
|
723
|
+
console.log(
|
|
724
|
+
`Loaded ${discoveredRoutes.length} routes from ${source}${prefix ? ` with prefix ${prefix}` : ""}`
|
|
725
|
+
);
|
|
726
|
+
} catch (error) {
|
|
727
|
+
console.error(`Failed to load routes from ${source}:`, error);
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
664
731
|
async function initialize() {
|
|
665
732
|
if (initialized || initializationPromise) {
|
|
666
733
|
return initializationPromise;
|
|
667
734
|
}
|
|
668
735
|
initializationPromise = (async () => {
|
|
669
736
|
try {
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
});
|
|
673
|
-
for (const route of discoveredRoutes) {
|
|
674
|
-
addRouteInternal(route);
|
|
737
|
+
for (const directory of routeDirectories) {
|
|
738
|
+
await loadRoutesFromDirectory(directory, directory);
|
|
675
739
|
}
|
|
676
740
|
if (routerOptions.watchMode) {
|
|
677
|
-
|
|
741
|
+
setupWatcherForAllDirectories();
|
|
678
742
|
}
|
|
679
743
|
initialized = true;
|
|
680
744
|
} catch (error) {
|
|
@@ -691,47 +755,81 @@ function createRouter(options) {
|
|
|
691
755
|
matcher.add(route.path, method, methodOptions);
|
|
692
756
|
});
|
|
693
757
|
}
|
|
694
|
-
function
|
|
695
|
-
|
|
696
|
-
ignore: ["node_modules", ".git"],
|
|
758
|
+
function createWatcherCallbacks(directory, source, prefix) {
|
|
759
|
+
return {
|
|
697
760
|
onRouteAdded: (addedRoutes) => {
|
|
698
761
|
console.log(
|
|
699
|
-
`${addedRoutes.length} route(s) added:`,
|
|
762
|
+
`${addedRoutes.length} route(s) added from ${directory}:`,
|
|
700
763
|
addedRoutes.map((r) => r.path)
|
|
701
764
|
);
|
|
702
|
-
addedRoutes.forEach((route) =>
|
|
765
|
+
addedRoutes.forEach((route) => {
|
|
766
|
+
const finalRoute = prefix ? { ...route, path: `${prefix}${route.path}` } : route;
|
|
767
|
+
addRouteWithSource(finalRoute, source);
|
|
768
|
+
});
|
|
703
769
|
},
|
|
704
770
|
onRouteChanged: (changedRoutes) => {
|
|
705
771
|
console.log(
|
|
706
|
-
`${changedRoutes.length} route(s) changed:`,
|
|
772
|
+
`${changedRoutes.length} route(s) changed in ${directory}:`,
|
|
707
773
|
changedRoutes.map((r) => r.path)
|
|
708
774
|
);
|
|
709
775
|
changedRoutes.forEach((route) => {
|
|
710
|
-
const
|
|
776
|
+
const finalPath = prefix ? `${prefix}${route.path}` : route.path;
|
|
777
|
+
const index = routes.findIndex((r) => r.path === finalPath);
|
|
711
778
|
if (index >= 0) {
|
|
712
779
|
routes.splice(index, 1);
|
|
780
|
+
const sources = routeSources.get(finalPath) || [];
|
|
781
|
+
const filteredSources = sources.filter((s) => s !== source);
|
|
782
|
+
if (filteredSources.length > 0) {
|
|
783
|
+
routeSources.set(finalPath, filteredSources);
|
|
784
|
+
} else {
|
|
785
|
+
routeSources.delete(finalPath);
|
|
786
|
+
}
|
|
713
787
|
}
|
|
714
|
-
|
|
788
|
+
const finalRoute = prefix ? { ...route, path: finalPath } : route;
|
|
789
|
+
addRouteWithSource(finalRoute, source);
|
|
715
790
|
});
|
|
716
791
|
},
|
|
717
792
|
onRouteRemoved: (filePath, removedRoutes) => {
|
|
718
|
-
console.log("-----------------------Routes before removal:", routes);
|
|
719
793
|
console.log(
|
|
720
|
-
`File removed: ${filePath} with ${removedRoutes.length} route(s):`,
|
|
794
|
+
`File removed from ${directory}: ${filePath} with ${removedRoutes.length} route(s):`,
|
|
721
795
|
removedRoutes.map((r) => r.path)
|
|
722
796
|
);
|
|
723
797
|
removedRoutes.forEach((route) => {
|
|
724
|
-
const
|
|
798
|
+
const finalPath = prefix ? `${prefix}${route.path}` : route.path;
|
|
799
|
+
const index = routes.findIndex((r) => r.path === finalPath);
|
|
725
800
|
if (index >= 0) {
|
|
726
801
|
routes.splice(index, 1);
|
|
727
802
|
}
|
|
803
|
+
const sources = routeSources.get(finalPath) || [];
|
|
804
|
+
const filteredSources = sources.filter((s) => s !== source);
|
|
805
|
+
if (filteredSources.length > 0) {
|
|
806
|
+
routeSources.set(finalPath, filteredSources);
|
|
807
|
+
} else {
|
|
808
|
+
routeSources.delete(finalPath);
|
|
809
|
+
}
|
|
728
810
|
});
|
|
729
|
-
console.log("-----------------------Routes after removal:", routes);
|
|
730
811
|
},
|
|
731
812
|
onError: (error) => {
|
|
732
|
-
console.error(
|
|
813
|
+
console.error(`Route watcher error for ${directory}:`, error);
|
|
733
814
|
}
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
function setupWatcherForDirectory(directory, source, prefix) {
|
|
818
|
+
const callbacks = createWatcherCallbacks(directory, source, prefix);
|
|
819
|
+
const watcher = watchRoutes(directory, {
|
|
820
|
+
ignore: ["node_modules", ".git"],
|
|
821
|
+
...callbacks
|
|
734
822
|
});
|
|
823
|
+
if (!_watchers) {
|
|
824
|
+
_watchers = /* @__PURE__ */ new Map();
|
|
825
|
+
}
|
|
826
|
+
_watchers.set(directory, watcher);
|
|
827
|
+
return watcher;
|
|
828
|
+
}
|
|
829
|
+
function setupWatcherForAllDirectories() {
|
|
830
|
+
for (const directory of routeDirectories) {
|
|
831
|
+
setupWatcherForDirectory(directory, directory);
|
|
832
|
+
}
|
|
735
833
|
}
|
|
736
834
|
initialize().catch((error) => {
|
|
737
835
|
console.error("Failed to initialize router on creation:", error);
|
|
@@ -781,6 +879,34 @@ function createRouter(options) {
|
|
|
781
879
|
*/
|
|
782
880
|
addRoute(route) {
|
|
783
881
|
addRouteInternal(route);
|
|
882
|
+
},
|
|
883
|
+
/**
|
|
884
|
+
* Add a route directory (for plugins)
|
|
885
|
+
*/
|
|
886
|
+
async addRouteDirectory(directory, options2 = {}) {
|
|
887
|
+
if (routeDirectories.has(directory)) {
|
|
888
|
+
console.warn(`Route directory ${directory} already registered`);
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
routeDirectories.add(directory);
|
|
892
|
+
if (initialized) {
|
|
893
|
+
await loadRoutesFromDirectory(directory, directory, options2.prefix);
|
|
894
|
+
if (routerOptions.watchMode) {
|
|
895
|
+
setupWatcherForDirectory(directory, directory, options2.prefix);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
/**
|
|
900
|
+
* Get route conflicts
|
|
901
|
+
*/
|
|
902
|
+
getRouteConflicts() {
|
|
903
|
+
const conflicts = [];
|
|
904
|
+
for (const [path5, sources] of routeSources.entries()) {
|
|
905
|
+
if (sources.length > 1) {
|
|
906
|
+
conflicts.push({ path: path5, sources });
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return conflicts;
|
|
784
910
|
}
|
|
785
911
|
};
|
|
786
912
|
}
|
|
@@ -803,7 +929,7 @@ function getCallerFilePath() {
|
|
|
803
929
|
try {
|
|
804
930
|
Error.prepareStackTrace = (_, stack2) => stack2;
|
|
805
931
|
const stack = new Error().stack;
|
|
806
|
-
const callerFrame = stack[
|
|
932
|
+
const callerFrame = stack[3];
|
|
807
933
|
if (!callerFrame || typeof callerFrame.getFileName !== "function") {
|
|
808
934
|
throw new Error("Unable to determine caller file frame");
|
|
809
935
|
}
|
|
@@ -817,9 +943,11 @@ function getCallerFilePath() {
|
|
|
817
943
|
}
|
|
818
944
|
}
|
|
819
945
|
function getRoutePath() {
|
|
946
|
+
console.log("getRoutePath called");
|
|
820
947
|
const callerPath = getCallerFilePath();
|
|
821
948
|
const routesDir = getRoutesDir();
|
|
822
949
|
const parsedRoute = parseRoutePath(callerPath, routesDir);
|
|
950
|
+
console.log(`Parsed route path: ${parsedRoute.routePath} from file: ${callerPath}`);
|
|
823
951
|
return parsedRoute.routePath;
|
|
824
952
|
}
|
|
825
953
|
var createGetRoute = (config2) => {
|
|
@@ -1508,12 +1636,12 @@ async function stopServer(serverInstance, options = {}) {
|
|
|
1508
1636
|
return;
|
|
1509
1637
|
}
|
|
1510
1638
|
const timeout = options.timeout || 3e4;
|
|
1511
|
-
const plugins = serverInstance.plugins;
|
|
1512
1639
|
try {
|
|
1513
1640
|
if (options.onStopping) {
|
|
1514
1641
|
await options.onStopping();
|
|
1515
1642
|
}
|
|
1516
1643
|
events.emit("stopping");
|
|
1644
|
+
await serverInstance.pluginManager.onServerStop(serverInstance, server);
|
|
1517
1645
|
const timeoutPromise = new Promise((_, reject) => {
|
|
1518
1646
|
setTimeout(() => {
|
|
1519
1647
|
reject(new Error("Server shutdown timed out waiting for requests to complete"));
|
|
@@ -1528,13 +1656,7 @@ async function stopServer(serverInstance, options = {}) {
|
|
|
1528
1656
|
});
|
|
1529
1657
|
});
|
|
1530
1658
|
await Promise.race([closePromise, timeoutPromise]);
|
|
1531
|
-
|
|
1532
|
-
for (const plugin of [...plugins].reverse()) {
|
|
1533
|
-
if (plugin.terminate) {
|
|
1534
|
-
await plugin.terminate();
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1659
|
+
await serverInstance.pluginManager.terminatePlugins(serverInstance);
|
|
1538
1660
|
if (options.onStopped) {
|
|
1539
1661
|
await options.onStopped();
|
|
1540
1662
|
}
|
|
@@ -1609,6 +1731,158 @@ function validateServerOptions(options) {
|
|
|
1609
1731
|
}
|
|
1610
1732
|
}
|
|
1611
1733
|
|
|
1734
|
+
// src/plugins/lifecycle.ts
|
|
1735
|
+
function createPluginLifecycleManager(options = {}) {
|
|
1736
|
+
const { continueOnError = true, debug = false, onError } = options;
|
|
1737
|
+
function log(message, ...args) {
|
|
1738
|
+
if (debug) {
|
|
1739
|
+
console.log(`[PluginLifecycle] ${message}`, ...args);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function handleError(plugin, phase, error) {
|
|
1743
|
+
const errorMessage = `Plugin ${plugin.name} failed during ${phase}: ${error.message}`;
|
|
1744
|
+
if (onError) {
|
|
1745
|
+
onError(plugin, phase, error);
|
|
1746
|
+
} else {
|
|
1747
|
+
console.error(errorMessage, error);
|
|
1748
|
+
}
|
|
1749
|
+
if (!continueOnError) {
|
|
1750
|
+
throw new Error(errorMessage);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
return {
|
|
1754
|
+
/**
|
|
1755
|
+
* Initialize all plugins
|
|
1756
|
+
*/
|
|
1757
|
+
async initializePlugins(server) {
|
|
1758
|
+
log("Initializing plugins...");
|
|
1759
|
+
for (const plugin of server.plugins) {
|
|
1760
|
+
if (plugin.initialize) {
|
|
1761
|
+
try {
|
|
1762
|
+
log(`Initializing plugin: ${plugin.name}`);
|
|
1763
|
+
await plugin.initialize(server);
|
|
1764
|
+
} catch (error) {
|
|
1765
|
+
handleError(plugin, "initialize", error);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
log(`Initialized ${server.plugins.length} plugins`);
|
|
1770
|
+
},
|
|
1771
|
+
/**
|
|
1772
|
+
* Terminate all plugins in reverse order
|
|
1773
|
+
*/
|
|
1774
|
+
async terminatePlugins(server) {
|
|
1775
|
+
log("Terminating plugins...");
|
|
1776
|
+
const pluginsToTerminate = [...server.plugins].reverse();
|
|
1777
|
+
for (const plugin of pluginsToTerminate) {
|
|
1778
|
+
if (plugin.terminate) {
|
|
1779
|
+
try {
|
|
1780
|
+
log(`Terminating plugin: ${plugin.name}`);
|
|
1781
|
+
await plugin.terminate(server);
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
handleError(plugin, "terminate", error);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
log(`Terminated ${pluginsToTerminate.length} plugins`);
|
|
1788
|
+
},
|
|
1789
|
+
/**
|
|
1790
|
+
* Notify plugins that the server has started
|
|
1791
|
+
*/
|
|
1792
|
+
async onServerStart(server, httpServer) {
|
|
1793
|
+
log("Notifying plugins of server start...");
|
|
1794
|
+
for (const plugin of server.plugins) {
|
|
1795
|
+
if (plugin.onServerStart) {
|
|
1796
|
+
try {
|
|
1797
|
+
log(`Notifying plugin of server start: ${plugin.name}`);
|
|
1798
|
+
await plugin.onServerStart(httpServer);
|
|
1799
|
+
} catch (error) {
|
|
1800
|
+
handleError(plugin, "onServerStart", error);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
},
|
|
1805
|
+
/**
|
|
1806
|
+
* Notify plugins that the server is stopping
|
|
1807
|
+
*/
|
|
1808
|
+
async onServerStop(server, httpServer) {
|
|
1809
|
+
log("Notifying plugins of server stop...");
|
|
1810
|
+
const pluginsToNotify = [...server.plugins].reverse();
|
|
1811
|
+
for (const plugin of pluginsToNotify) {
|
|
1812
|
+
if (plugin.onServerStop) {
|
|
1813
|
+
try {
|
|
1814
|
+
log(`Notifying plugin of server stop: ${plugin.name}`);
|
|
1815
|
+
await plugin.onServerStop(httpServer);
|
|
1816
|
+
} catch (error) {
|
|
1817
|
+
handleError(plugin, "onServerStop", error);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// src/plugins/errors.ts
|
|
1826
|
+
var PluginValidationError = class extends Error {
|
|
1827
|
+
constructor(pluginName, message) {
|
|
1828
|
+
super(`Plugin validation error${pluginName ? ` for "${pluginName}"` : ""}: ${message}`);
|
|
1829
|
+
this.pluginName = pluginName;
|
|
1830
|
+
this.name = "PluginValidationError";
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
|
|
1834
|
+
// src/plugins/validation.ts
|
|
1835
|
+
var RESERVED_NAMES = /* @__PURE__ */ new Set([
|
|
1836
|
+
"core",
|
|
1837
|
+
"server",
|
|
1838
|
+
"router",
|
|
1839
|
+
"middleware",
|
|
1840
|
+
"context",
|
|
1841
|
+
"blaize",
|
|
1842
|
+
"blaizejs"
|
|
1843
|
+
]);
|
|
1844
|
+
var VALID_NAME_PATTERN = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
1845
|
+
var VALID_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[a-zA-Z0-9-.]+)?(?:\+[a-zA-Z0-9-.]+)?$/;
|
|
1846
|
+
function validatePlugin(plugin, options = {}) {
|
|
1847
|
+
const { requireVersion = true, validateNameFormat = true, checkReservedNames = true } = options;
|
|
1848
|
+
if (!plugin || typeof plugin !== "object") {
|
|
1849
|
+
throw new PluginValidationError("", "Plugin must be an object");
|
|
1850
|
+
}
|
|
1851
|
+
const p = plugin;
|
|
1852
|
+
if (!p.name || typeof p.name !== "string") {
|
|
1853
|
+
throw new PluginValidationError("", "Plugin must have a name (string)");
|
|
1854
|
+
}
|
|
1855
|
+
if (validateNameFormat && !VALID_NAME_PATTERN.test(p.name)) {
|
|
1856
|
+
throw new PluginValidationError(
|
|
1857
|
+
p.name,
|
|
1858
|
+
"Plugin name must be lowercase letters, numbers, and hyphens only"
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
if (checkReservedNames && RESERVED_NAMES.has(p.name.toLowerCase())) {
|
|
1862
|
+
throw new PluginValidationError(p.name, `Plugin name "${p.name}" is reserved`);
|
|
1863
|
+
}
|
|
1864
|
+
if (requireVersion) {
|
|
1865
|
+
if (!p.version || typeof p.version !== "string") {
|
|
1866
|
+
throw new PluginValidationError(p.name, "Plugin must have a version (string)");
|
|
1867
|
+
}
|
|
1868
|
+
if (!VALID_VERSION_PATTERN.test(p.version)) {
|
|
1869
|
+
throw new PluginValidationError(
|
|
1870
|
+
p.name,
|
|
1871
|
+
'Plugin version must follow semantic versioning (e.g., "1.0.0")'
|
|
1872
|
+
);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
if (!p.register || typeof p.register !== "function") {
|
|
1876
|
+
throw new PluginValidationError(p.name, "Plugin must have a register method (function)");
|
|
1877
|
+
}
|
|
1878
|
+
const lifecycleMethods = ["initialize", "terminate", "onServerStart", "onServerStop"];
|
|
1879
|
+
for (const method of lifecycleMethods) {
|
|
1880
|
+
if (p[method] && typeof p[method] !== "function") {
|
|
1881
|
+
throw new PluginValidationError(p.name, `Plugin ${method} must be a function if provided`);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1612
1886
|
// src/server/create.ts
|
|
1613
1887
|
var DEFAULT_OPTIONS = {
|
|
1614
1888
|
port: 3e3,
|
|
@@ -1639,7 +1913,9 @@ function createServerOptions(options = {}) {
|
|
|
1639
1913
|
function createListenMethod(serverInstance, validatedOptions, initialMiddleware, initialPlugins) {
|
|
1640
1914
|
return async () => {
|
|
1641
1915
|
await initializeComponents(serverInstance, initialMiddleware, initialPlugins);
|
|
1916
|
+
await serverInstance.pluginManager.initializePlugins(serverInstance);
|
|
1642
1917
|
await startServer(serverInstance, validatedOptions);
|
|
1918
|
+
await serverInstance.pluginManager.onServerStart(serverInstance, serverInstance.server);
|
|
1643
1919
|
setupServerLifecycle(serverInstance);
|
|
1644
1920
|
return serverInstance;
|
|
1645
1921
|
};
|
|
@@ -1685,13 +1961,6 @@ function createRegisterMethod(serverInstance) {
|
|
|
1685
1961
|
return serverInstance;
|
|
1686
1962
|
};
|
|
1687
1963
|
}
|
|
1688
|
-
function validatePlugin(plugin) {
|
|
1689
|
-
if (!plugin || typeof plugin !== "object" || !("register" in plugin) || typeof plugin.register !== "function") {
|
|
1690
|
-
throw new Error(
|
|
1691
|
-
"Invalid plugin. Must be a valid BlaizeJS plugin object with a register method."
|
|
1692
|
-
);
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
1964
|
function create3(options = {}) {
|
|
1696
1965
|
const mergedOptions = createServerOptions(options);
|
|
1697
1966
|
let validatedOptions;
|
|
@@ -1711,6 +1980,10 @@ function create3(options = {}) {
|
|
|
1711
1980
|
routesDir: validatedOptions.routesDir,
|
|
1712
1981
|
watchMode: process.env.NODE_ENV === "development"
|
|
1713
1982
|
});
|
|
1983
|
+
const pluginManager = createPluginLifecycleManager({
|
|
1984
|
+
debug: process.env.NODE_ENV === "development",
|
|
1985
|
+
continueOnError: true
|
|
1986
|
+
});
|
|
1714
1987
|
const events = new EventEmitter();
|
|
1715
1988
|
const serverInstance = {
|
|
1716
1989
|
server: null,
|
|
@@ -1727,7 +2000,8 @@ function create3(options = {}) {
|
|
|
1727
2000
|
listen: async () => serverInstance,
|
|
1728
2001
|
close: async () => {
|
|
1729
2002
|
},
|
|
1730
|
-
router
|
|
2003
|
+
router,
|
|
2004
|
+
pluginManager
|
|
1731
2005
|
};
|
|
1732
2006
|
serverInstance.listen = createListenMethod(
|
|
1733
2007
|
serverInstance,
|