@kuckit/app-server 2.0.1
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.d.ts +202 -0
- package/dist/index.js +341 -0
- package/package.json +63 -0
- package/src/index.ts +37 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { CoreConfig, CoreCradle, ModuleSpec } from "@kuckit/sdk";
|
|
2
|
+
import express, { Express, NextFunction, Request, Response } from "express";
|
|
3
|
+
import { AwilixContainer } from "awilix";
|
|
4
|
+
import { Pool } from "pg";
|
|
5
|
+
import { Logger, UserRepository } from "@kuckit/domain";
|
|
6
|
+
import { StructuredLogger } from "@kuckit/infrastructure";
|
|
7
|
+
|
|
8
|
+
//#region src/types.d.ts
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extended configuration for Kuckit server applications
|
|
12
|
+
* Extends CoreConfig with server-specific settings
|
|
13
|
+
*/
|
|
14
|
+
interface KuckitServerConfig extends CoreConfig {
|
|
15
|
+
port: number;
|
|
16
|
+
corsOrigin: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extended cradle for Kuckit server applications
|
|
20
|
+
* Includes core services plus commonly-used server dependencies
|
|
21
|
+
*/
|
|
22
|
+
interface KuckitServerCradle extends CoreCradle {
|
|
23
|
+
config: KuckitServerConfig;
|
|
24
|
+
dbPool: Pool;
|
|
25
|
+
logger: Logger & StructuredLogger;
|
|
26
|
+
userRepository: UserRepository;
|
|
27
|
+
}
|
|
28
|
+
type KuckitContainer = AwilixContainer<KuckitServerCradle>;
|
|
29
|
+
/**
|
|
30
|
+
* Options for configuring the Kuckit server
|
|
31
|
+
*/
|
|
32
|
+
interface KuckitServerOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Load server configuration from environment variables.
|
|
35
|
+
* Must return a config object extending KuckitServerConfig.
|
|
36
|
+
*/
|
|
37
|
+
loadConfig: () => KuckitServerConfig;
|
|
38
|
+
/**
|
|
39
|
+
* Get module specifications for the application.
|
|
40
|
+
* Can be async for dynamic module loading.
|
|
41
|
+
*/
|
|
42
|
+
getModuleSpecs: () => ModuleSpec[] | Promise<ModuleSpec[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Optional hooks for customizing server behavior
|
|
45
|
+
*/
|
|
46
|
+
hooks?: KuckitServerHooks;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Hooks for customizing server behavior at various lifecycle points
|
|
50
|
+
*/
|
|
51
|
+
interface KuckitServerHooks {
|
|
52
|
+
/**
|
|
53
|
+
* Called after the Express app is created, before any middleware is added.
|
|
54
|
+
* Use this to add custom middleware that should run first.
|
|
55
|
+
*/
|
|
56
|
+
onAppCreated?: (app: Express) => void | Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Called after the DI container is built and modules are loaded.
|
|
59
|
+
* Use this to register additional dependencies or perform post-load setup.
|
|
60
|
+
*/
|
|
61
|
+
onContainerReady?: (container: KuckitContainer) => void | Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Called after all routes are set up, before the server starts listening.
|
|
64
|
+
* Use this to add custom routes or final middleware.
|
|
65
|
+
*/
|
|
66
|
+
onRoutesReady?: (app: Express, container: KuckitContainer) => void | Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Called after the server starts listening.
|
|
69
|
+
* Use this for post-startup tasks like logging or health checks.
|
|
70
|
+
*/
|
|
71
|
+
onServerReady?: (port: number, container: KuckitContainer) => void | Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Called during graceful shutdown.
|
|
74
|
+
* Use this to clean up custom resources before the container is disposed.
|
|
75
|
+
*/
|
|
76
|
+
onShutdown?: (container: KuckitContainer) => void | Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Result of creating a Kuckit server without starting it
|
|
80
|
+
*/
|
|
81
|
+
interface KuckitServer {
|
|
82
|
+
app: Express;
|
|
83
|
+
container: KuckitContainer;
|
|
84
|
+
start: () => Promise<{
|
|
85
|
+
port: number;
|
|
86
|
+
close: () => Promise<void>;
|
|
87
|
+
}>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Context for oRPC procedures
|
|
91
|
+
*/
|
|
92
|
+
interface KuckitContext {
|
|
93
|
+
di: KuckitContainer;
|
|
94
|
+
session: unknown;
|
|
95
|
+
requestId: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Extended Express Request with DI scope
|
|
99
|
+
*/
|
|
100
|
+
interface KuckitRequest extends Request {
|
|
101
|
+
scope: AwilixContainer<KuckitServerCradle>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Middleware function type for Kuckit
|
|
105
|
+
*/
|
|
106
|
+
type KuckitMiddleware = (req: KuckitRequest, res: Response, next: NextFunction) => void | Promise<void>;
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/express/server.d.ts
|
|
109
|
+
/**
|
|
110
|
+
* Cleanup container resources
|
|
111
|
+
*/
|
|
112
|
+
declare const disposeContainer: (container: KuckitContainer) => Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Create a Kuckit server without starting it.
|
|
115
|
+
* Useful for testing or when you need access to the app/container before listening.
|
|
116
|
+
*/
|
|
117
|
+
declare const createKuckitServer: (options: KuckitServerOptions) => Promise<KuckitServer>;
|
|
118
|
+
/**
|
|
119
|
+
* Run a Kuckit server - the main entry point for server applications.
|
|
120
|
+
* Creates the server and immediately starts listening.
|
|
121
|
+
*/
|
|
122
|
+
declare const runKuckitServer: (options: KuckitServerOptions) => Promise<{
|
|
123
|
+
port: number;
|
|
124
|
+
close: () => Promise<void>;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Create a headless Kuckit context (DI container only, no Express app).
|
|
128
|
+
* Useful for CLI tools, scripts, testing, or background workers.
|
|
129
|
+
*/
|
|
130
|
+
declare const createKuckitContext: (options: KuckitServerOptions) => Promise<{
|
|
131
|
+
container: KuckitContainer;
|
|
132
|
+
dispose: () => Promise<void>;
|
|
133
|
+
}>;
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/express/app.d.ts
|
|
136
|
+
interface CreateAppOptions {
|
|
137
|
+
config: KuckitServerConfig;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Create and configure Express app with CORS
|
|
141
|
+
*/
|
|
142
|
+
declare const createKuckitApp: (options: CreateAppOptions) => Express;
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/express/middleware.d.ts
|
|
145
|
+
/**
|
|
146
|
+
* Setup container middleware that creates per-request DI scopes
|
|
147
|
+
*/
|
|
148
|
+
declare const setupContainerMiddleware: (app: Express, rootContainer: KuckitContainer) => void;
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/express/routes.d.ts
|
|
151
|
+
type AnyRouter = any;
|
|
152
|
+
/**
|
|
153
|
+
* Options for setting up routes
|
|
154
|
+
*/
|
|
155
|
+
interface SetupRoutesOptions {
|
|
156
|
+
/**
|
|
157
|
+
* The RPC router object. Module routers should already be wired into this.
|
|
158
|
+
*/
|
|
159
|
+
rpcRouter: AnyRouter;
|
|
160
|
+
/**
|
|
161
|
+
* REST router entries from modules (for streaming endpoints)
|
|
162
|
+
*/
|
|
163
|
+
restRouters?: Array<{
|
|
164
|
+
name: string;
|
|
165
|
+
router: express.Router;
|
|
166
|
+
basePath: string;
|
|
167
|
+
}>;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Setup Better-Auth routes
|
|
171
|
+
*/
|
|
172
|
+
declare const setupAuth: (app: Express) => void;
|
|
173
|
+
/**
|
|
174
|
+
* Setup oRPC handler
|
|
175
|
+
*/
|
|
176
|
+
declare const setupRPC: (app: Express, options: SetupRoutesOptions) => void;
|
|
177
|
+
/**
|
|
178
|
+
* Setup OpenAPI documentation
|
|
179
|
+
*/
|
|
180
|
+
declare const setupAPIReference: (app: Express, options: SetupRoutesOptions) => void;
|
|
181
|
+
/**
|
|
182
|
+
* Setup REST routers from modules
|
|
183
|
+
*/
|
|
184
|
+
declare const setupModuleRestRouters: (app: Express, options: SetupRoutesOptions) => void;
|
|
185
|
+
/**
|
|
186
|
+
* Setup health check endpoint
|
|
187
|
+
*/
|
|
188
|
+
declare const setupHealth: (app: Express, container: KuckitContainer) => void;
|
|
189
|
+
/**
|
|
190
|
+
* Setup Prometheus metrics endpoint
|
|
191
|
+
*/
|
|
192
|
+
declare const setupMetrics: (app: Express, container: KuckitContainer) => void;
|
|
193
|
+
/**
|
|
194
|
+
* Setup static file serving (production only)
|
|
195
|
+
*/
|
|
196
|
+
declare const setupStaticFiles: (app: Express, publicDir?: string) => void;
|
|
197
|
+
/**
|
|
198
|
+
* Setup error handling middleware (must be last)
|
|
199
|
+
*/
|
|
200
|
+
declare const setupErrorMiddleware: (app: Express) => void;
|
|
201
|
+
//#endregion
|
|
202
|
+
export { type CreateAppOptions, type KuckitContainer, type KuckitContext, type KuckitMiddleware, type KuckitRequest, type KuckitServer, type KuckitServerConfig, type KuckitServerCradle, type KuckitServerHooks, type KuckitServerOptions, type SetupRoutesOptions, createKuckitApp, createKuckitContext, createKuckitServer, disposeContainer, runKuckitServer, setupAPIReference, setupAuth, setupContainerMiddleware, setupErrorMiddleware, setupHealth, setupMetrics, setupModuleRestRouters, setupRPC, setupStaticFiles };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { createKuckitContainer, loadKuckitModules } from "@kuckit/sdk";
|
|
2
|
+
import { appRouter } from "@kuckit/api/routers/index";
|
|
3
|
+
import express from "express";
|
|
4
|
+
import cors from "cors";
|
|
5
|
+
import { asValue } from "awilix";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { RPCHandler } from "@orpc/server/node";
|
|
8
|
+
import { OpenAPIHandler } from "@orpc/openapi/node";
|
|
9
|
+
import { OpenAPIReferencePlugin } from "@orpc/openapi/plugins";
|
|
10
|
+
import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
|
|
11
|
+
import { onError } from "@orpc/server";
|
|
12
|
+
import { fromNodeHeaders, toNodeHandler } from "better-auth/node";
|
|
13
|
+
import { createContext } from "@kuckit/api/context";
|
|
14
|
+
|
|
15
|
+
//#region src/express/app.ts
|
|
16
|
+
/**
|
|
17
|
+
* Create and configure Express app with CORS
|
|
18
|
+
*/
|
|
19
|
+
const createKuckitApp = (options) => {
|
|
20
|
+
const { config } = options;
|
|
21
|
+
const app = express();
|
|
22
|
+
app.use(cors({
|
|
23
|
+
origin: config.corsOrigin || "",
|
|
24
|
+
methods: [
|
|
25
|
+
"GET",
|
|
26
|
+
"POST",
|
|
27
|
+
"OPTIONS"
|
|
28
|
+
],
|
|
29
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
30
|
+
credentials: true
|
|
31
|
+
}));
|
|
32
|
+
return app;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/express/middleware.ts
|
|
37
|
+
/**
|
|
38
|
+
* Setup container middleware that creates per-request DI scopes
|
|
39
|
+
*/
|
|
40
|
+
const setupContainerMiddleware = (app, rootContainer) => {
|
|
41
|
+
app.use((req, res, next) => {
|
|
42
|
+
req.scope = rootContainer.createScope();
|
|
43
|
+
req.scope.register({ requestId: asValue(crypto.randomUUID()) });
|
|
44
|
+
res.on("finish", () => {
|
|
45
|
+
req.scope?.dispose();
|
|
46
|
+
});
|
|
47
|
+
res.on("close", () => {
|
|
48
|
+
req.scope?.dispose();
|
|
49
|
+
});
|
|
50
|
+
next();
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/express/routes.ts
|
|
56
|
+
/**
|
|
57
|
+
* Middleware to add session to request scope for REST routes.
|
|
58
|
+
*/
|
|
59
|
+
const createSessionMiddleware = () => {
|
|
60
|
+
return async (req, _res, next) => {
|
|
61
|
+
if (!req.scope) return next(/* @__PURE__ */ new Error("Request scope not initialized"));
|
|
62
|
+
const auth = req.scope.cradle.auth;
|
|
63
|
+
try {
|
|
64
|
+
if (!auth) throw new Error("Auth not available in request scope");
|
|
65
|
+
const session = await auth.api.getSession({ headers: fromNodeHeaders(req.headers) });
|
|
66
|
+
req.scope.register({ session: asValue(session) });
|
|
67
|
+
next();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error("[REST Auth] Failed to get session:", error);
|
|
70
|
+
req.scope.register({ session: asValue(null) });
|
|
71
|
+
next();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Setup Better-Auth routes
|
|
77
|
+
*/
|
|
78
|
+
const setupAuth = (app) => {
|
|
79
|
+
app.all("/api/auth{/*path}", (req, res, _next) => {
|
|
80
|
+
const auth = req.scope?.cradle.auth;
|
|
81
|
+
if (!auth) return res.status(500).json({ error: "Auth not initialized" });
|
|
82
|
+
return toNodeHandler(auth)(req, res);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Setup oRPC handler
|
|
87
|
+
*/
|
|
88
|
+
const setupRPC = (app, options) => {
|
|
89
|
+
const rpcHandler = new RPCHandler(options.rpcRouter, { interceptors: [onError((error) => {
|
|
90
|
+
console.error("[RPC Error]", error);
|
|
91
|
+
})] });
|
|
92
|
+
app.use(async (req, res, next) => {
|
|
93
|
+
if ((await rpcHandler.handle(req, res, {
|
|
94
|
+
prefix: "/rpc",
|
|
95
|
+
context: await createContext({ req })
|
|
96
|
+
})).matched) return;
|
|
97
|
+
next();
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Setup OpenAPI documentation
|
|
102
|
+
*/
|
|
103
|
+
const setupAPIReference = (app, options) => {
|
|
104
|
+
const apiHandler = new OpenAPIHandler(options.rpcRouter, {
|
|
105
|
+
plugins: [new OpenAPIReferencePlugin({ schemaConverters: [new ZodToJsonSchemaConverter()] })],
|
|
106
|
+
interceptors: [onError((error) => {
|
|
107
|
+
console.error("[API Reference Error]", error);
|
|
108
|
+
})]
|
|
109
|
+
});
|
|
110
|
+
app.use(async (req, res, next) => {
|
|
111
|
+
if ((await apiHandler.handle(req, res, {
|
|
112
|
+
prefix: "/api-reference",
|
|
113
|
+
context: await createContext({ req })
|
|
114
|
+
})).matched) return;
|
|
115
|
+
next();
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Setup REST routers from modules
|
|
120
|
+
*/
|
|
121
|
+
const setupModuleRestRouters = (app, options) => {
|
|
122
|
+
const routers = options.restRouters ?? [];
|
|
123
|
+
const sessionMiddleware = createSessionMiddleware();
|
|
124
|
+
for (const { name, router, basePath } of routers) {
|
|
125
|
+
app.use(`/api${basePath}`, express.json(), sessionMiddleware, router);
|
|
126
|
+
console.log(`[REST] Mounted module router: /api${basePath} (${name})`);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Setup health check endpoint
|
|
131
|
+
*/
|
|
132
|
+
const setupHealth = (app, container) => {
|
|
133
|
+
app.get("/", (_req, res) => {
|
|
134
|
+
res.status(200).send("OK");
|
|
135
|
+
});
|
|
136
|
+
app.get("/health", async (_req, res) => {
|
|
137
|
+
const { dbPool } = container.cradle;
|
|
138
|
+
try {
|
|
139
|
+
const client = await dbPool.connect();
|
|
140
|
+
await client.query("SELECT 1");
|
|
141
|
+
client.release();
|
|
142
|
+
res.status(200).json({
|
|
143
|
+
status: "healthy",
|
|
144
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
145
|
+
checks: { database: "ok" }
|
|
146
|
+
});
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error("[Health] Database check failed:", error);
|
|
149
|
+
res.status(503).json({
|
|
150
|
+
status: "unhealthy",
|
|
151
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
152
|
+
checks: { database: "failed" }
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Setup Prometheus metrics endpoint
|
|
159
|
+
*/
|
|
160
|
+
const setupMetrics = (app, container) => {
|
|
161
|
+
app.get("/metrics", (_req, res) => {
|
|
162
|
+
const { logger } = container.cradle;
|
|
163
|
+
const metricsText = logger.getMetrics?.() ?? "";
|
|
164
|
+
res.set("Content-Type", "text/plain; version=0.0.4; charset=utf-8");
|
|
165
|
+
res.send(metricsText);
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Setup static file serving (production only)
|
|
170
|
+
*/
|
|
171
|
+
const setupStaticFiles = (app, publicDir) => {
|
|
172
|
+
if (process.env.NODE_ENV !== "production") return;
|
|
173
|
+
const dir = publicDir ?? path.join(__dirname, "public");
|
|
174
|
+
app.use(express.static(dir, {
|
|
175
|
+
maxAge: "1d",
|
|
176
|
+
etag: true,
|
|
177
|
+
setHeaders: (res) => {
|
|
178
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
179
|
+
}
|
|
180
|
+
}));
|
|
181
|
+
app.get("{/*path}", (req, res, next) => {
|
|
182
|
+
if (req.path.startsWith("/api") || req.path.startsWith("/rpc") || req.path.startsWith("/auth") || req.path.startsWith("/health") || req.path.startsWith("/metrics")) return next();
|
|
183
|
+
res.sendFile(path.join(dir, "index.html"));
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Setup error handling middleware (must be last)
|
|
188
|
+
*/
|
|
189
|
+
const setupErrorMiddleware = (app) => {
|
|
190
|
+
app.use((err, req, res, _next) => {
|
|
191
|
+
const errorHandler = req.scope?.cradle.errorHandler;
|
|
192
|
+
if (!errorHandler) {
|
|
193
|
+
res.status(500).json({
|
|
194
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
195
|
+
message: "Internal server error"
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const serialized = errorHandler.handle(err, {
|
|
200
|
+
path: req.path,
|
|
201
|
+
method: req.method,
|
|
202
|
+
requestId: req.scope?.cradle.requestId
|
|
203
|
+
});
|
|
204
|
+
res.status(serialized.statusCode).json(serialized);
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/express/server.ts
|
|
210
|
+
/**
|
|
211
|
+
* Build the root container with all modules loaded
|
|
212
|
+
*/
|
|
213
|
+
const buildContainer = async (options) => {
|
|
214
|
+
const config = options.loadConfig();
|
|
215
|
+
const moduleSpecs = await options.getModuleSpecs();
|
|
216
|
+
const rpcRouter = { ...appRouter };
|
|
217
|
+
const restRouters = [];
|
|
218
|
+
const container = await createKuckitContainer({ config });
|
|
219
|
+
await loadKuckitModules({
|
|
220
|
+
container,
|
|
221
|
+
env: config.env,
|
|
222
|
+
modules: moduleSpecs,
|
|
223
|
+
onApiRegistrations: (registrations) => {
|
|
224
|
+
for (const reg of registrations) if (reg.type === "rpc-router") {
|
|
225
|
+
if (rpcRouter[reg.name]) throw new Error(`Duplicate RPC router name: "${reg.name}"`);
|
|
226
|
+
rpcRouter[reg.name] = reg.router;
|
|
227
|
+
} else if (reg.type === "rest-router") {
|
|
228
|
+
const basePath = reg.prefix ?? `/${reg.name}`;
|
|
229
|
+
restRouters.push({
|
|
230
|
+
name: reg.name,
|
|
231
|
+
router: reg.router,
|
|
232
|
+
basePath
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
container.resolve("logger").info(`Loaded ${registrations.length} API registrations from modules`);
|
|
236
|
+
},
|
|
237
|
+
onComplete: () => {
|
|
238
|
+
container.resolve("logger").info("All modules loaded successfully");
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
container,
|
|
243
|
+
rpcRouter,
|
|
244
|
+
restRouters
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* Cleanup container resources
|
|
249
|
+
*/
|
|
250
|
+
const disposeContainer = async (container) => {
|
|
251
|
+
const { dbPool } = container.cradle;
|
|
252
|
+
if (dbPool && typeof dbPool.end === "function") await dbPool.end();
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Create a Kuckit server without starting it.
|
|
256
|
+
* Useful for testing or when you need access to the app/container before listening.
|
|
257
|
+
*/
|
|
258
|
+
const createKuckitServer = async (options) => {
|
|
259
|
+
const config = options.loadConfig();
|
|
260
|
+
const { container, rpcRouter, restRouters } = await buildContainer(options);
|
|
261
|
+
const app = createKuckitApp({ config });
|
|
262
|
+
await options.hooks?.onAppCreated?.(app);
|
|
263
|
+
setupContainerMiddleware(app, container);
|
|
264
|
+
await options.hooks?.onContainerReady?.(container);
|
|
265
|
+
const routeOptions = {
|
|
266
|
+
rpcRouter,
|
|
267
|
+
restRouters
|
|
268
|
+
};
|
|
269
|
+
setupAuth(app);
|
|
270
|
+
setupRPC(app, routeOptions);
|
|
271
|
+
setupAPIReference(app, routeOptions);
|
|
272
|
+
setupModuleRestRouters(app, routeOptions);
|
|
273
|
+
setupMetrics(app, container);
|
|
274
|
+
setupHealth(app, container);
|
|
275
|
+
setupStaticFiles(app);
|
|
276
|
+
await options.hooks?.onRoutesReady?.(app, container);
|
|
277
|
+
setupErrorMiddleware(app);
|
|
278
|
+
const start = async () => {
|
|
279
|
+
const port = config.port;
|
|
280
|
+
return new Promise((resolve) => {
|
|
281
|
+
const server = app.listen(port, () => {
|
|
282
|
+
const logger = container.resolve("logger");
|
|
283
|
+
logger.info(`Server is running on port ${port}`);
|
|
284
|
+
options.hooks?.onServerReady?.(port, container);
|
|
285
|
+
const shutdown = async () => {
|
|
286
|
+
logger.info("Shutting down gracefully...");
|
|
287
|
+
await options.hooks?.onShutdown?.(container);
|
|
288
|
+
server.close(async () => {
|
|
289
|
+
await disposeContainer(container);
|
|
290
|
+
logger.info("Server closed");
|
|
291
|
+
process.exit(0);
|
|
292
|
+
});
|
|
293
|
+
setTimeout(() => {
|
|
294
|
+
logger.error("Forced shutdown");
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}, 1e4);
|
|
297
|
+
};
|
|
298
|
+
process.on("SIGTERM", shutdown);
|
|
299
|
+
process.on("SIGINT", shutdown);
|
|
300
|
+
resolve({
|
|
301
|
+
port,
|
|
302
|
+
close: async () => {
|
|
303
|
+
await options.hooks?.onShutdown?.(container);
|
|
304
|
+
return new Promise((res) => {
|
|
305
|
+
server.close(async () => {
|
|
306
|
+
await disposeContainer(container);
|
|
307
|
+
res();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
return {
|
|
316
|
+
app,
|
|
317
|
+
container,
|
|
318
|
+
start
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* Run a Kuckit server - the main entry point for server applications.
|
|
323
|
+
* Creates the server and immediately starts listening.
|
|
324
|
+
*/
|
|
325
|
+
const runKuckitServer = async (options) => {
|
|
326
|
+
return (await createKuckitServer(options)).start();
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Create a headless Kuckit context (DI container only, no Express app).
|
|
330
|
+
* Useful for CLI tools, scripts, testing, or background workers.
|
|
331
|
+
*/
|
|
332
|
+
const createKuckitContext = async (options) => {
|
|
333
|
+
const { container } = await buildContainer(options);
|
|
334
|
+
return {
|
|
335
|
+
container,
|
|
336
|
+
dispose: () => disposeContainer(container)
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
//#endregion
|
|
341
|
+
export { createKuckitApp, createKuckitContext, createKuckitServer, disposeContainer, runKuckitServer, setupAPIReference, setupAuth, setupContainerMiddleware, setupErrorMiddleware, setupHealth, setupMetrics, setupModuleRestRouters, setupRPC, setupStaticFiles };
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kuckit/app-server",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./src/index.ts",
|
|
13
|
+
"default": "./src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"./*": {
|
|
16
|
+
"types": "./src/*.ts",
|
|
17
|
+
"default": "./src/*.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"main": "dist/index.js",
|
|
22
|
+
"types": "dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./*": {
|
|
29
|
+
"types": "./dist/*.d.ts",
|
|
30
|
+
"default": "./dist/*.js"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsdown",
|
|
36
|
+
"check-types": "tsc -b"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@kuckit/sdk": "workspace:*",
|
|
40
|
+
"@kuckit/api": "workspace:*",
|
|
41
|
+
"@kuckit/db": "workspace:*",
|
|
42
|
+
"@kuckit/domain": "workspace:*",
|
|
43
|
+
"@kuckit/infrastructure": "workspace:*",
|
|
44
|
+
"@kuckit/auth": "workspace:*",
|
|
45
|
+
"express": "^5.1.0",
|
|
46
|
+
"cors": "^2.8.5",
|
|
47
|
+
"awilix": "^12.0.5",
|
|
48
|
+
"@orpc/server": "catalog:",
|
|
49
|
+
"@orpc/openapi": "catalog:",
|
|
50
|
+
"better-auth": "catalog:",
|
|
51
|
+
"pg": "^8.11.3"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"typescript": "catalog:",
|
|
55
|
+
"@types/express": "catalog:",
|
|
56
|
+
"@types/cors": "^2.8.17",
|
|
57
|
+
"@types/pg": "^8.10.9",
|
|
58
|
+
"tsdown": "catalog:"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"typescript": "^5"
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Main entry points
|
|
2
|
+
export {
|
|
3
|
+
runKuckitServer,
|
|
4
|
+
createKuckitServer,
|
|
5
|
+
createKuckitContext,
|
|
6
|
+
disposeContainer,
|
|
7
|
+
} from './express/server'
|
|
8
|
+
|
|
9
|
+
// Express utilities
|
|
10
|
+
export { createKuckitApp, type CreateAppOptions } from './express/app'
|
|
11
|
+
export { setupContainerMiddleware } from './express/middleware'
|
|
12
|
+
|
|
13
|
+
// Route setup functions (for custom wiring)
|
|
14
|
+
export {
|
|
15
|
+
setupAuth,
|
|
16
|
+
setupRPC,
|
|
17
|
+
setupAPIReference,
|
|
18
|
+
setupModuleRestRouters,
|
|
19
|
+
setupHealth,
|
|
20
|
+
setupMetrics,
|
|
21
|
+
setupStaticFiles,
|
|
22
|
+
setupErrorMiddleware,
|
|
23
|
+
type SetupRoutesOptions,
|
|
24
|
+
} from './express/routes'
|
|
25
|
+
|
|
26
|
+
// Types
|
|
27
|
+
export type {
|
|
28
|
+
KuckitServerConfig,
|
|
29
|
+
KuckitServerCradle,
|
|
30
|
+
KuckitContainer,
|
|
31
|
+
KuckitServerOptions,
|
|
32
|
+
KuckitServerHooks,
|
|
33
|
+
KuckitServer,
|
|
34
|
+
KuckitContext,
|
|
35
|
+
KuckitRequest,
|
|
36
|
+
KuckitMiddleware,
|
|
37
|
+
} from './types'
|