@venizia/ignis 0.4.4 → 0.4.5
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/esm/base/controllers/grpc/adapter.d.ts +6 -1
- package/dist/esm/base/controllers/grpc/adapter.d.ts.map +1 -1
- package/dist/esm/base/controllers/grpc/adapter.js +18 -21
- package/dist/esm/base/controllers/grpc/adapter.js.map +1 -1
- package/dist/esm/base/controllers/grpc/common/types.d.ts +3 -3
- package/dist/esm/base/controllers/grpc/common/types.js.map +1 -1
- package/dist/esm/testing/assert-application-boots.d.ts +6 -4
- package/dist/esm/testing/assert-application-boots.d.ts.map +1 -1
- package/dist/esm/testing/assert-application-boots.js +53 -8
- package/dist/esm/testing/assert-application-boots.js.map +1 -1
- package/dist/presets/app/files/src/boot.test.ts +2 -1
- package/dist/presets/app/preset.json +5 -5
- package/dist/presets/lib/preset.json +4 -4
- package/package.json +6 -6
|
@@ -3,13 +3,18 @@ import { BaseHelper } from '@venizia/ignis-helpers/core';
|
|
|
3
3
|
import type { Env, Schema } from 'hono';
|
|
4
4
|
import type { AbstractGrpcController } from './abstract.js';
|
|
5
5
|
import type { IConnectAdapterResult, IConnectRpcModule } from './common/types.js';
|
|
6
|
-
/** Bridges IGNIS gRPC controllers with ConnectRPC: loads the peer deps
|
|
6
|
+
/** Bridges IGNIS gRPC controllers with ConnectRPC: loads the peer deps with `ModuleUtility.load` (the ES module copy an application's own import gets), wraps handlers into ConnectRPC's signature, dispatches through a Hono middleware, and isolates request-scoped context with AsyncLocalStorage. */
|
|
7
7
|
export declare class GrpcRequestAdapter<RouteEnv extends Env = Env, RouteSchema extends Schema = {}, BasePath extends string = '/', ServiceType = Parameters<ConnectRouter['service']>[0], ConfigurableOptions extends object = {}> extends BaseHelper {
|
|
8
8
|
private createConnectRouter;
|
|
9
9
|
private protocol;
|
|
10
10
|
private controller;
|
|
11
11
|
private interceptors?;
|
|
12
12
|
private constructor();
|
|
13
|
+
/**
|
|
14
|
+
* The ConnectRPC peers, loaded as ES modules from the project root (or taken from `ModuleUtility.register`,
|
|
15
|
+
* which is what a compiled binary has). A missing peer rejects with the error that names what to install.
|
|
16
|
+
*/
|
|
17
|
+
private static loadModule;
|
|
13
18
|
/** Wraps IGNIS RPC registrations into ConnectRPC's (request, context) => response signature. */
|
|
14
19
|
private buildConnectHandlers;
|
|
15
20
|
/** Loose signature on purpose - the IGNIS `ServiceType` is opaque (`unknown`) while ConnectRPC expects `DescService`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../../src/base/controllers/grpc/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,qBAAqB,CAAC;AAOzE,OAAO,EAAE,UAAU,EAAY,MAAM,6BAA6B,CAAC;AAGnE,OAAO,KAAK,EAAE,GAAG,EAA4B,MAAM,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../../src/base/controllers/grpc/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,qBAAqB,CAAC;AAOzE,OAAO,EAAE,UAAU,EAAY,MAAM,6BAA6B,CAAC;AAGnE,OAAO,KAAK,EAAE,GAAG,EAA4B,MAAM,EAAE,MAAM,MAAM,CAAC;AAGlE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,iBAAiB,EAAoB,MAAM,gBAAgB,CAAC;AAUjG,ySAAyS;AACzS,qBAAa,kBAAkB,CAC7B,QAAQ,SAAS,GAAG,GAAG,GAAG,EAC1B,WAAW,SAAS,MAAM,GAAG,EAAE,EAC/B,QAAQ,SAAS,MAAM,GAAG,GAAG,EAC7B,WAAW,GAAG,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EACrD,mBAAmB,SAAS,MAAM,GAAG,EAAE,CACvC,SAAQ,UAAU;IAClB,OAAO,CAAC,mBAAmB,CAAoD;IAC/E,OAAO,CAAC,QAAQ,CAGd;IAEF,OAAO,CAAC,UAAU,CAMhB;IACF,OAAO,CAAC,YAAY,CAAC,CAAY;IAEjC,OAAO;IAmBP;;;OAGG;mBACkB,UAAU;IAW/B,gGAAgG;IAChG,OAAO,CAAC,oBAAoB;IA8B5B,yHAAyH;IACzH,OAAO,CAAC,eAAe;IAQvB,oFAAoF;IACpF,OAAO,CAAC,eAAe;IA8DvB,4EAA4E;WAC/D,KAAK,CAChB,QAAQ,SAAS,GAAG,GAAG,GAAG,EAC1B,WAAW,SAAS,MAAM,GAAG,EAAE,EAC/B,QAAQ,SAAS,MAAM,GAAG,GAAG,EAC7B,WAAW,GAAG,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EACrD,mBAAmB,SAAS,MAAM,GAAG,EAAE,EACvC,IAAI,EAAE;QACN,UAAU,EAAE,sBAAsB,CAChC,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,WAAW,EACX,mBAAmB,CACpB,CAAC;QACF,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC;QACzB,MAAM,CAAC,EAAE,iBAAiB,CAAC;KAC5B,GAAG,OAAO,CAAC,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;CAmDvD"}
|
|
@@ -2,28 +2,28 @@ import { BaseHelper, getError } from '@venizia/ignis-helpers/core';
|
|
|
2
2
|
import { GRPC, HTTP } from '@venizia/ignis-helpers/common';
|
|
3
3
|
import { ModuleUtility } from '@venizia/ignis-helpers';
|
|
4
4
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
5
|
-
import { createRequire } from 'node:module';
|
|
6
|
-
import path from 'node:path';
|
|
7
5
|
const CONNECT_RPC_MODULES = ['@connectrpc/connect'];
|
|
8
|
-
|
|
6
|
+
const CONNECT_RPC_PROTOCOL_MODULE = '@connectrpc/connect/protocol';
|
|
7
|
+
/** Bridges IGNIS gRPC controllers with ConnectRPC: loads the peer deps with `ModuleUtility.load` (the ES module copy an application's own import gets), wraps handlers into ConnectRPC's signature, dispatches through a Hono middleware, and isolates request-scoped context with AsyncLocalStorage. */
|
|
9
8
|
export class GrpcRequestAdapter extends BaseHelper {
|
|
10
9
|
constructor(opts) {
|
|
11
10
|
super({ scope: GrpcRequestAdapter.name });
|
|
12
11
|
this.controller = opts.controller;
|
|
13
12
|
this.interceptors = opts.interceptors;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
this.createConnectRouter = opts.module.connect.createConnectRouter;
|
|
14
|
+
this.protocol = opts.module.protocol;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The ConnectRPC peers, loaded as ES modules from the project root (or taken from `ModuleUtility.register`,
|
|
18
|
+
* which is what a compiled binary has). A missing peer rejects with the error that names what to install.
|
|
19
|
+
*/
|
|
20
|
+
static async loadModule() {
|
|
21
|
+
ModuleUtility.assertInstalled({ modules: CONNECT_RPC_MODULES, allowRegistered: true });
|
|
22
|
+
const [connect, protocol] = await Promise.all([
|
|
23
|
+
ModuleUtility.load({ module: CONNECT_RPC_MODULES[0] }),
|
|
24
|
+
ModuleUtility.load({ module: CONNECT_RPC_PROTOCOL_MODULE }),
|
|
25
|
+
]);
|
|
26
|
+
return { connect, protocol };
|
|
27
27
|
}
|
|
28
28
|
/** Wraps IGNIS RPC registrations into ConnectRPC's (request, context) => response signature. */
|
|
29
29
|
buildConnectHandlers(opts) {
|
|
@@ -104,11 +104,8 @@ export class GrpcRequestAdapter extends BaseHelper {
|
|
|
104
104
|
}
|
|
105
105
|
/** Validates peer deps, creates adapter, and returns paths + middleware. */
|
|
106
106
|
static async build(opts) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
ModuleUtility.assertInstalled({ modules: CONNECT_RPC_MODULES });
|
|
110
|
-
}
|
|
111
|
-
const adapter = new GrpcRequestAdapter(opts);
|
|
107
|
+
const module = opts.module ?? (await GrpcRequestAdapter.loadModule());
|
|
108
|
+
const adapter = new GrpcRequestAdapter({ ...opts, module });
|
|
112
109
|
const routerOpts = {};
|
|
113
110
|
if (adapter.interceptors?.length) {
|
|
114
111
|
routerOpts.interceptors = adapter.interceptors;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../../../src/base/controllers/grpc/adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConnectRouter, HandlerContext } from '@connectrpc/connect';\nimport type {\n UniversalHandler,\n UniversalServerRequest,\n UniversalServerResponse,\n} from '@connectrpc/connect/protocol';\nimport type { ValueOrPromise } from '@venizia/ignis-helpers/common';\nimport { BaseHelper, getError } from '@venizia/ignis-helpers/core';\nimport { GRPC, HTTP } from '@venizia/ignis-helpers/common';\nimport { ModuleUtility } from '@venizia/ignis-helpers';\nimport type { Env, Input, MiddlewareHandler, Schema } from 'hono';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type { TRouteContext } from '@venizia/ignis-kernel';\nimport type { AbstractGrpcController } from './abstract';\nimport type { IConnectAdapterResult, IConnectRpcModule, IRpcRegistration } from './common/types';\n\nconst CONNECT_RPC_MODULES = ['@connectrpc/connect'];\n\ntype TConnectHandler<RequestType = unknown, ResponseType = unknown> = (\n request: RequestType,\n context: HandlerContext,\n) => ValueOrPromise<ResponseType>;\n\n/** Bridges IGNIS gRPC controllers with ConnectRPC: loads the peer deps via createRequire, wraps handlers into ConnectRPC's signature, dispatches through a Hono middleware, and isolates request-scoped context with AsyncLocalStorage. */\nexport class GrpcRequestAdapter<\n RouteEnv extends Env = Env,\n RouteSchema extends Schema = {},\n BasePath extends string = '/',\n ServiceType = Parameters<ConnectRouter['service']>[0],\n ConfigurableOptions extends object = {},\n> extends BaseHelper {\n private createConnectRouter: (opts?: Record<string, unknown>) => ConnectRouter;\n private protocol: {\n universalServerRequestFromFetch: (req: Request, ctx: object) => UniversalServerRequest;\n universalServerResponseToFetch: (res: UniversalServerResponse) => Response;\n };\n\n private controller: AbstractGrpcController<\n RouteEnv,\n RouteSchema,\n BasePath,\n ServiceType,\n ConfigurableOptions\n >;\n private interceptors?: unknown[];\n\n private constructor(opts: {\n controller: AbstractGrpcController<\n RouteEnv,\n RouteSchema,\n BasePath,\n ServiceType,\n ConfigurableOptions\n >;\n interceptors?: unknown[];\n module?: IConnectRpcModule;\n }) {\n super({ scope: GrpcRequestAdapter.name });\n\n this.controller = opts.controller;\n this.interceptors = opts.interceptors;\n\n if (opts.module) {\n this.createConnectRouter = opts.module.connect.createConnectRouter;\n this.protocol = opts.module.protocol;\n return;\n }\n\n /** Resolves peer deps under the application's project root at runtime - nothing to resolve against in a compiled binary, which is what `module` is for. */\n const appRequire = createRequire(path.join(ModuleUtility.getProjectRoot(), 'node_modules'));\n\n this.createConnectRouter = appRequire('@connectrpc/connect').createConnectRouter;\n const proto = appRequire('@connectrpc/connect/protocol');\n\n this.protocol = {\n universalServerRequestFromFetch: proto.universalServerRequestFromFetch,\n universalServerResponseToFetch: proto.universalServerResponseToFetch,\n };\n }\n\n /** Wraps IGNIS RPC registrations into ConnectRPC's (request, context) => response signature. */\n private buildConnectHandlers(opts: {\n definitions: Record<string, IRpcRegistration<RouteEnv>>;\n storage: AsyncLocalStorage<TRouteContext<RouteEnv>>;\n }): Record<string, TConnectHandler> {\n const handlers: Record<string, TConnectHandler> = {};\n\n for (const name in opts.definitions) {\n const { handler, middlewares } = opts.definitions[name];\n\n handlers[name] = async (request: unknown, _connectContext: HandlerContext) => {\n const context = opts.storage.getStore();\n if (!context) {\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n message: `[GrpcRequestAdapter] Missing Hono context for RPC \"${name}\"`,\n });\n }\n\n // Run pre-built auth middlewares (built by AbstractGrpcController.buildRpcMiddlewares)\n for (const mw of middlewares) {\n await mw(context, async () => {});\n }\n\n return handler({ request, context });\n };\n }\n\n return handlers;\n }\n\n /** Loose signature on purpose - the IGNIS `ServiceType` is opaque (`unknown`) while ConnectRPC expects `DescService`. */\n private registerService(opts: {\n router: ConnectRouter;\n handlers: Record<string, TConnectHandler>;\n }) {\n const { router, handlers } = opts;\n (router.service as Function)(this.controller.service, handlers);\n }\n\n /** Creates the Hono middleware that dispatches to ConnectRPC universal handlers. */\n private buildMiddleware(opts: {\n handlerMap: Map<string, UniversalHandler>;\n controllerPath: string;\n storage: AsyncLocalStorage<TRouteContext<RouteEnv>>;\n }): MiddlewareHandler<RouteEnv, BasePath, Input> {\n const { handlerMap, controllerPath, storage } = opts;\n const { universalServerRequestFromFetch, universalServerResponseToFetch } = this.protocol;\n\n return async (context, next) => {\n let pathname = context.req.path;\n\n // context.req.path is the full original URL path, so strip the basePath + controllerPath prefix to get the ConnectRPC handler path (/package.Service/Method).\n if (controllerPath && pathname.startsWith(controllerPath)) {\n pathname = pathname.slice(controllerPath.length) || '/';\n }\n\n const handler = handlerMap.get(pathname);\n if (!handler) {\n return next();\n }\n\n // `context` is Hono's real Context<RouteEnv>; TRouteContext is a lightweight custom shape (different `json`/`req.valid` signatures) built on it, not a subtype - hence this bridge.\n return storage.run(context as TRouteContext<RouteEnv>, async () => {\n try {\n const body = await context.req.arrayBuffer();\n const freshRequest = new Request(context.req.url, {\n method: context.req.method,\n headers: context.req.raw.headers,\n body: body.byteLength > 0 ? body : undefined,\n });\n\n const uReq = universalServerRequestFromFetch(freshRequest, {});\n const uRes = await handler(uReq);\n return universalServerResponseToFetch(uRes);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Internal gRPC error';\n\n // Preserve gRPC status code from ConnectError (duck-type check avoids peer dep coupling)\n const code =\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n typeof error.code === 'number'\n ? error.code\n : GRPC.ResultCodes.INTERNAL;\n\n return new Response(JSON.stringify({ message, code }), {\n status:\n code === GRPC.ResultCodes.OK\n ? HTTP.ResultCodes.RS_2.Ok\n : HTTP.ResultCodes.RS_5.InternalServerError,\n headers: {\n [HTTP.Headers.CONTENT_TYPE]: HTTP.HeaderValues.APPLICATION_JSON,\n [GRPC.Headers.GRPC_STATUS]: String(code),\n [GRPC.Headers.GRPC_MESSAGE]: encodeURIComponent(message),\n },\n });\n }\n });\n };\n }\n\n /** Validates peer deps, creates adapter, and returns paths + middleware. */\n static async build<\n RouteEnv extends Env = Env,\n RouteSchema extends Schema = {},\n BasePath extends string = '/',\n ServiceType = Parameters<ConnectRouter['service']>[0],\n ConfigurableOptions extends object = {},\n >(opts: {\n controller: AbstractGrpcController<\n RouteEnv,\n RouteSchema,\n BasePath,\n ServiceType,\n ConfigurableOptions\n >;\n interceptors?: unknown[];\n module?: IConnectRpcModule;\n }): Promise<IConnectAdapterResult<RouteEnv, BasePath>> {\n // The adapter resolves the peer itself, so a `ModuleUtility.register` entry could not satisfy it - only a real install or a handed-over `module` can.\n if (!opts.module) {\n ModuleUtility.assertInstalled({ modules: CONNECT_RPC_MODULES });\n }\n\n const adapter = new GrpcRequestAdapter<\n RouteEnv,\n RouteSchema,\n BasePath,\n ServiceType,\n ConfigurableOptions\n >(opts);\n\n const routerOpts: Record<string, unknown> = {};\n if (adapter.interceptors?.length) {\n routerOpts.interceptors = adapter.interceptors;\n }\n\n const connectRouter = adapter.createConnectRouter(routerOpts);\n\n // Per-controller async storage - each concurrent request gets its own isolated context.\n const storage = new AsyncLocalStorage<TRouteContext<RouteEnv>>();\n\n if (adapter.controller.service) {\n adapter.registerService({\n router: connectRouter,\n handlers: adapter.buildConnectHandlers({\n definitions: adapter.controller.definitions,\n storage,\n }),\n });\n }\n\n const handlerMap = new Map<string, UniversalHandler>();\n const paths: string[] = [];\n\n for (const handler of connectRouter.handlers) {\n handlerMap.set(handler.requestPath, handler);\n paths.push(handler.requestPath);\n }\n\n return {\n paths,\n middleware: adapter.buildMiddleware({\n handlerMap,\n controllerPath: [\n adapter.controller.basePath.replace(/\\/$/, ''),\n adapter.controller.path,\n ].join(''),\n storage,\n }),\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../../../src/base/controllers/grpc/adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConnectRouter, HandlerContext } from '@connectrpc/connect';\nimport type {\n UniversalHandler,\n UniversalServerRequest,\n UniversalServerResponse,\n} from '@connectrpc/connect/protocol';\nimport type { ValueOrPromise } from '@venizia/ignis-helpers/common';\nimport { BaseHelper, getError } from '@venizia/ignis-helpers/core';\nimport { GRPC, HTTP } from '@venizia/ignis-helpers/common';\nimport { ModuleUtility } from '@venizia/ignis-helpers';\nimport type { Env, Input, MiddlewareHandler, Schema } from 'hono';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { TRouteContext } from '@venizia/ignis-kernel';\nimport type { AbstractGrpcController } from './abstract';\nimport type { IConnectAdapterResult, IConnectRpcModule, IRpcRegistration } from './common/types';\n\nconst CONNECT_RPC_MODULES = ['@connectrpc/connect'];\nconst CONNECT_RPC_PROTOCOL_MODULE = '@connectrpc/connect/protocol';\n\ntype TConnectHandler<RequestType = unknown, ResponseType = unknown> = (\n request: RequestType,\n context: HandlerContext,\n) => ValueOrPromise<ResponseType>;\n\n/** Bridges IGNIS gRPC controllers with ConnectRPC: loads the peer deps with `ModuleUtility.load` (the ES module copy an application's own import gets), wraps handlers into ConnectRPC's signature, dispatches through a Hono middleware, and isolates request-scoped context with AsyncLocalStorage. */\nexport class GrpcRequestAdapter<\n RouteEnv extends Env = Env,\n RouteSchema extends Schema = {},\n BasePath extends string = '/',\n ServiceType = Parameters<ConnectRouter['service']>[0],\n ConfigurableOptions extends object = {},\n> extends BaseHelper {\n private createConnectRouter: (opts?: Record<string, unknown>) => ConnectRouter;\n private protocol: {\n universalServerRequestFromFetch: (req: Request, ctx: object) => UniversalServerRequest;\n universalServerResponseToFetch: (res: UniversalServerResponse) => Response;\n };\n\n private controller: AbstractGrpcController<\n RouteEnv,\n RouteSchema,\n BasePath,\n ServiceType,\n ConfigurableOptions\n >;\n private interceptors?: unknown[];\n\n private constructor(opts: {\n controller: AbstractGrpcController<\n RouteEnv,\n RouteSchema,\n BasePath,\n ServiceType,\n ConfigurableOptions\n >;\n interceptors?: unknown[];\n module: IConnectRpcModule;\n }) {\n super({ scope: GrpcRequestAdapter.name });\n\n this.controller = opts.controller;\n this.interceptors = opts.interceptors;\n this.createConnectRouter = opts.module.connect.createConnectRouter;\n this.protocol = opts.module.protocol;\n }\n\n /**\n * The ConnectRPC peers, loaded as ES modules from the project root (or taken from `ModuleUtility.register`,\n * which is what a compiled binary has). A missing peer rejects with the error that names what to install.\n */\n private static async loadModule(): Promise<IConnectRpcModule> {\n ModuleUtility.assertInstalled({ modules: CONNECT_RPC_MODULES, allowRegistered: true });\n\n const [connect, protocol] = await Promise.all([\n ModuleUtility.load<IConnectRpcModule['connect']>({ module: CONNECT_RPC_MODULES[0] }),\n ModuleUtility.load<IConnectRpcModule['protocol']>({ module: CONNECT_RPC_PROTOCOL_MODULE }),\n ]);\n\n return { connect, protocol };\n }\n\n /** Wraps IGNIS RPC registrations into ConnectRPC's (request, context) => response signature. */\n private buildConnectHandlers(opts: {\n definitions: Record<string, IRpcRegistration<RouteEnv>>;\n storage: AsyncLocalStorage<TRouteContext<RouteEnv>>;\n }): Record<string, TConnectHandler> {\n const handlers: Record<string, TConnectHandler> = {};\n\n for (const name in opts.definitions) {\n const { handler, middlewares } = opts.definitions[name];\n\n handlers[name] = async (request: unknown, _connectContext: HandlerContext) => {\n const context = opts.storage.getStore();\n if (!context) {\n throw getError({\n statusCode: HTTP.ResultCodes.RS_5.InternalServerError,\n message: `[GrpcRequestAdapter] Missing Hono context for RPC \"${name}\"`,\n });\n }\n\n // Run pre-built auth middlewares (built by AbstractGrpcController.buildRpcMiddlewares)\n for (const mw of middlewares) {\n await mw(context, async () => {});\n }\n\n return handler({ request, context });\n };\n }\n\n return handlers;\n }\n\n /** Loose signature on purpose - the IGNIS `ServiceType` is opaque (`unknown`) while ConnectRPC expects `DescService`. */\n private registerService(opts: {\n router: ConnectRouter;\n handlers: Record<string, TConnectHandler>;\n }) {\n const { router, handlers } = opts;\n (router.service as Function)(this.controller.service, handlers);\n }\n\n /** Creates the Hono middleware that dispatches to ConnectRPC universal handlers. */\n private buildMiddleware(opts: {\n handlerMap: Map<string, UniversalHandler>;\n controllerPath: string;\n storage: AsyncLocalStorage<TRouteContext<RouteEnv>>;\n }): MiddlewareHandler<RouteEnv, BasePath, Input> {\n const { handlerMap, controllerPath, storage } = opts;\n const { universalServerRequestFromFetch, universalServerResponseToFetch } = this.protocol;\n\n return async (context, next) => {\n let pathname = context.req.path;\n\n // context.req.path is the full original URL path, so strip the basePath + controllerPath prefix to get the ConnectRPC handler path (/package.Service/Method).\n if (controllerPath && pathname.startsWith(controllerPath)) {\n pathname = pathname.slice(controllerPath.length) || '/';\n }\n\n const handler = handlerMap.get(pathname);\n if (!handler) {\n return next();\n }\n\n // `context` is Hono's real Context<RouteEnv>; TRouteContext is a lightweight custom shape (different `json`/`req.valid` signatures) built on it, not a subtype - hence this bridge.\n return storage.run(context as TRouteContext<RouteEnv>, async () => {\n try {\n const body = await context.req.arrayBuffer();\n const freshRequest = new Request(context.req.url, {\n method: context.req.method,\n headers: context.req.raw.headers,\n body: body.byteLength > 0 ? body : undefined,\n });\n\n const uReq = universalServerRequestFromFetch(freshRequest, {});\n const uRes = await handler(uReq);\n return universalServerResponseToFetch(uRes);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Internal gRPC error';\n\n // Preserve gRPC status code from ConnectError (duck-type check avoids peer dep coupling)\n const code =\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n typeof error.code === 'number'\n ? error.code\n : GRPC.ResultCodes.INTERNAL;\n\n return new Response(JSON.stringify({ message, code }), {\n status:\n code === GRPC.ResultCodes.OK\n ? HTTP.ResultCodes.RS_2.Ok\n : HTTP.ResultCodes.RS_5.InternalServerError,\n headers: {\n [HTTP.Headers.CONTENT_TYPE]: HTTP.HeaderValues.APPLICATION_JSON,\n [GRPC.Headers.GRPC_STATUS]: String(code),\n [GRPC.Headers.GRPC_MESSAGE]: encodeURIComponent(message),\n },\n });\n }\n });\n };\n }\n\n /** Validates peer deps, creates adapter, and returns paths + middleware. */\n static async build<\n RouteEnv extends Env = Env,\n RouteSchema extends Schema = {},\n BasePath extends string = '/',\n ServiceType = Parameters<ConnectRouter['service']>[0],\n ConfigurableOptions extends object = {},\n >(opts: {\n controller: AbstractGrpcController<\n RouteEnv,\n RouteSchema,\n BasePath,\n ServiceType,\n ConfigurableOptions\n >;\n interceptors?: unknown[];\n module?: IConnectRpcModule;\n }): Promise<IConnectAdapterResult<RouteEnv, BasePath>> {\n const module = opts.module ?? (await GrpcRequestAdapter.loadModule());\n\n const adapter = new GrpcRequestAdapter<\n RouteEnv,\n RouteSchema,\n BasePath,\n ServiceType,\n ConfigurableOptions\n >({ ...opts, module });\n\n const routerOpts: Record<string, unknown> = {};\n if (adapter.interceptors?.length) {\n routerOpts.interceptors = adapter.interceptors;\n }\n\n const connectRouter = adapter.createConnectRouter(routerOpts);\n\n // Per-controller async storage - each concurrent request gets its own isolated context.\n const storage = new AsyncLocalStorage<TRouteContext<RouteEnv>>();\n\n if (adapter.controller.service) {\n adapter.registerService({\n router: connectRouter,\n handlers: adapter.buildConnectHandlers({\n definitions: adapter.controller.definitions,\n storage,\n }),\n });\n }\n\n const handlerMap = new Map<string, UniversalHandler>();\n const paths: string[] = [];\n\n for (const handler of connectRouter.handlers) {\n handlerMap.set(handler.requestPath, handler);\n paths.push(handler.requestPath);\n }\n\n return {\n paths,\n middleware: adapter.buildMiddleware({\n handlerMap,\n controllerPath: [\n adapter.controller.basePath.replace(/\\/$/, ''),\n adapter.controller.path,\n ].join(''),\n storage,\n }),\n };\n }\n}\n"]}
|
|
@@ -6,9 +6,9 @@ import type { IConfigurable, ValueOrPromise } from '@venizia/ignis-helpers/commo
|
|
|
6
6
|
import type { Env, Hono, Input, MiddlewareHandler, Next, Schema } from 'hono';
|
|
7
7
|
/**
|
|
8
8
|
* The ConnectRPC peer itself, handed over by an application that already holds it. A
|
|
9
|
-
* `bun build --compile` binary carries no `node_modules`, so the
|
|
10
|
-
*
|
|
11
|
-
* component options is what a compiled application does instead. Both entry points are needed: the
|
|
9
|
+
* `bun build --compile` binary carries no `node_modules`, so the lookup {@link GrpcRequestAdapter} makes with
|
|
10
|
+
* `ModuleUtility.load` has nothing to resolve against - passing the modules through the gRPC
|
|
11
|
+
* component options (or `ModuleUtility.register`) is what a compiled application does instead. Both entry points are needed: the
|
|
12
12
|
* adapter builds its router from `@connectrpc/connect` and converts requests with
|
|
13
13
|
* `@connectrpc/connect/protocol`. Typed as the shape the adapter actually calls, so a wrong module
|
|
14
14
|
* is a compile error rather than a boot crash.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../../src/base/controllers/grpc/common/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { TRouteContext } from '@venizia/ignis-kernel';\nimport type { IRpcMetadata } from '@venizia/ignis-kernel';\nimport type { ConnectRouter } from '@connectrpc/connect';\nimport type { UniversalServerRequest, UniversalServerResponse } from '@connectrpc/connect/protocol';\nimport type { IConfigurable, ValueOrPromise } from '@venizia/ignis-helpers/common';\nimport type { Env, Hono, Input, MiddlewareHandler, Next, Schema } from 'hono';\n\n/**\n * The ConnectRPC peer itself, handed over by an application that already holds it. A\n * `bun build --compile` binary carries no `node_modules`, so the
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../../src/base/controllers/grpc/common/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { TRouteContext } from '@venizia/ignis-kernel';\nimport type { IRpcMetadata } from '@venizia/ignis-kernel';\nimport type { ConnectRouter } from '@connectrpc/connect';\nimport type { UniversalServerRequest, UniversalServerResponse } from '@connectrpc/connect/protocol';\nimport type { IConfigurable, ValueOrPromise } from '@venizia/ignis-helpers/common';\nimport type { Env, Hono, Input, MiddlewareHandler, Next, Schema } from 'hono';\n\n/**\n * The ConnectRPC peer itself, handed over by an application that already holds it. A\n * `bun build --compile` binary carries no `node_modules`, so the lookup {@link GrpcRequestAdapter} makes with\n * `ModuleUtility.load` has nothing to resolve against - passing the modules through the gRPC\n * component options (or `ModuleUtility.register`) is what a compiled application does instead. Both entry points are needed: the\n * adapter builds its router from `@connectrpc/connect` and converts requests with\n * `@connectrpc/connect/protocol`. Typed as the shape the adapter actually calls, so a wrong module\n * is a compile error rather than a boot crash.\n */\nexport interface IConnectRpcModule {\n connect: {\n createConnectRouter: (opts?: Record<string, unknown>) => ConnectRouter;\n };\n protocol: {\n universalServerRequestFromFetch: (request: Request, context: object) => UniversalServerRequest;\n universalServerResponseToFetch: (response: UniversalServerResponse) => Response;\n };\n}\n\n/** Configuration options for gRPC controller instantiation. */\nexport interface IGrpcControllerOptions {\n scope: string;\n /** Falls back to @controller decorator path if not provided. */\n path?: string;\n}\n\n/** Typed handler signature for gRPC RPC methods. */\nexport type TRpcHandler<\n RequestType = unknown,\n ResponseType = unknown,\n RouteEnv extends Env = Env,\n> = (opts: {\n request: RequestType;\n context: TRouteContext<RouteEnv>;\n}) => ValueOrPromise<ResponseType>;\n\n/** Pre-built middleware function for gRPC auth enforcement. */\nexport type TRpcMiddleware<RouteEnv extends Env = Env> = (\n context: TRouteContext<RouteEnv>,\n next: Next,\n) => ValueOrPromise<void | Response>;\n\n/** Unified registration entry: metadata + handler + pre-built middlewares stored together. */\nexport interface IRpcRegistration<RouteEnv extends Env = Env> {\n configs: IRpcMetadata;\n handler: TRpcHandler<unknown, unknown, RouteEnv>;\n middlewares: TRpcMiddleware<RouteEnv>[];\n}\n\n/** Return type from defineRoute - contains processed configs. */\nexport interface IGrpcDefineRouteOptions {\n configs: IRpcMetadata;\n}\n\n/** Fluent binding for two-step RPC registration: bindRoute({ configs }).to({ handler }). */\nexport interface IGrpcBindRouteOptions<RouteEnv extends Env = Env> {\n configs: IRpcMetadata;\n to: (opts: { handler: TRpcHandler<unknown, unknown, RouteEnv> }) => IGrpcDefineRouteOptions;\n}\n\n/** gRPC controller interface defining RPC registration and configuration contract. */\nexport interface IGrpcController<\n RouteEnv extends Env = Env,\n RouteSchema extends Schema = {},\n BasePath extends string = '/',\n ServiceType = unknown,\n ConfigurableOptions extends object = {},\n> extends IConfigurable<ConfigurableOptions> {\n service: ServiceType;\n router: Hono<RouteEnv, RouteSchema, BasePath>;\n\n definitions: Record<string, IRpcRegistration<RouteEnv>>;\n\n getRouter(): Hono<RouteEnv, RouteSchema, BasePath>;\n\n bindRoute(opts: { configs: IRpcMetadata }): IGrpcBindRouteOptions<RouteEnv>;\n defineRoute(opts: {\n configs: IRpcMetadata;\n handler: TRpcHandler<unknown, unknown, RouteEnv>;\n }): IGrpcDefineRouteOptions;\n}\n\n/** Result from GrpcRequestAdapter.build(). */\nexport interface IConnectAdapterResult<\n RouteEnv extends Env = Env,\n BasePath extends string = '/',\n RouteInput extends Input = {},\n> {\n paths: string[];\n middleware: MiddlewareHandler<RouteEnv, BasePath, RouteInput>;\n}\n"]}
|
|
@@ -38,12 +38,14 @@ export interface IAssertApplicationBootsOptions {
|
|
|
38
38
|
routes?: boolean;
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
|
-
* Proves an IGNIS application boots, without
|
|
42
|
-
* sequence (`init()` then `initialize()`) and never calls `start()`,
|
|
43
|
-
* post-start hook runs.
|
|
41
|
+
* Proves an IGNIS application boots, without a datasource connection, a socket or a secrets provider:
|
|
42
|
+
* it runs the application's own boot sequence (`init()` then `initialize()`) and never calls `start()`,
|
|
43
|
+
* so no socket is opened and no post-start hook runs.
|
|
44
44
|
*
|
|
45
45
|
* Datasources are bound and resolved but not configured, unless named in `keepDataSources`; the
|
|
46
|
-
* secrets steps are skipped; every other step and every component runs as in a real boot.
|
|
46
|
+
* secrets steps are skipped; every other step and every component runs as in a real boot. A component
|
|
47
|
+
* that opens Redis, Kafka or S3 in `binding()` therefore runs for real: name it in `skipComponents`.
|
|
48
|
+
* When the boot fails inside a component, one warning names it; the rejection is still the original error. When the
|
|
47
49
|
* boot fails, the rejection is the original error. The application stays booted and is returned;
|
|
48
50
|
* a failed boot leaves a half-booted instance, so build a new one rather than reuse it.
|
|
49
51
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assert-application-boots.d.ts","sourceRoot":"","sources":["../../../src/testing/assert-application-boots.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAarE,4HAA4H;AAC5H,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC;AAExD,MAAM,WAAW,8BAA8B;IAC7C;;;OAGG;IACH,eAAe,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAErC;;;;OAIG;IACH,cAAc,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAEpC;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAE9B;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAElC,mIAAmI;IACnI,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,yHAAyH;IACzH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"assert-application-boots.d.ts","sourceRoot":"","sources":["../../../src/testing/assert-application-boots.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAarE,4HAA4H;AAC5H,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC;AAExD,MAAM,WAAW,8BAA8B;IAC7C;;;OAGG;IACH,eAAe,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAErC;;;;OAIG;IACH,cAAc,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAEpC;;;;OAIG;IACH,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAE9B;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,YAAY,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAElC,mIAAmI;IACnI,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,yHAAyH;IACzH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAiJD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,GAAU,WAAW,SAAS,eAAe,EAC9E,aAAa,WAAW,GAAG,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,EACrE,UAAS,8BAAmC,KAC3C,OAAO,CAAC,WAAW,CAoMrB,CAAC"}
|
|
@@ -43,7 +43,13 @@ const normalizePath = (path) => {
|
|
|
43
43
|
/** The helper's own check: IGNIS mounts a method and path twice without complaint, and the first one wins. */
|
|
44
44
|
const assertNoDuplicateRoutes = (opts) => {
|
|
45
45
|
const { application } = opts;
|
|
46
|
+
// signature -> controller key -> how many times that controller declares it
|
|
46
47
|
const mounted = new Map();
|
|
48
|
+
const record = (signature, key, declarations) => {
|
|
49
|
+
const owners = mounted.get(signature) ?? new Map();
|
|
50
|
+
owners.set(key, Math.max(owners.get(key) ?? 0, declarations));
|
|
51
|
+
mounted.set(signature, owners);
|
|
52
|
+
};
|
|
47
53
|
for (const binding of application.findByTag({
|
|
48
54
|
tag: BindingNamespaces.CONTROLLER,
|
|
49
55
|
})) {
|
|
@@ -58,28 +64,45 @@ const assertNoDuplicateRoutes = (opts) => {
|
|
|
58
64
|
key: binding.key,
|
|
59
65
|
isOptional: true,
|
|
60
66
|
});
|
|
67
|
+
// Hono's `routes` has one entry PER HANDLER (a validator, an authenticate and the handler of one route are three
|
|
68
|
+
// entries with the same method and path), so an entry is not a declaration: a controller counts once per signature.
|
|
61
69
|
for (const route of instance?.getRouter().routes ?? []) {
|
|
62
70
|
if (route.method === NON_ROUTE_METHOD) {
|
|
63
71
|
continue;
|
|
64
72
|
}
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
record(`${route.method} ${normalizePath(`${metadata.path}/${route.path}`)}`, binding.key, 1);
|
|
74
|
+
}
|
|
75
|
+
// What the router cannot show: one controller declaring a method and path twice. Decorated routes are one
|
|
76
|
+
// metadata entry per declaration, so those are counted there.
|
|
77
|
+
const declared = new Map();
|
|
78
|
+
for (const route of MetadataRegistry.getInstance()
|
|
79
|
+
.getRoutes({ target: target.prototype })
|
|
80
|
+
?.values() ?? []) {
|
|
81
|
+
const signature = `${String(route.method).toUpperCase()} ${normalizePath(`${metadata.path}/${route.path}`)}`;
|
|
82
|
+
declared.set(signature, (declared.get(signature) ?? 0) + 1);
|
|
83
|
+
}
|
|
84
|
+
for (const [signature, declarations] of declared) {
|
|
85
|
+
record(signature, binding.key, declarations);
|
|
67
86
|
}
|
|
68
87
|
}
|
|
69
|
-
const duplicates = [...mounted.entries()].filter(([,
|
|
88
|
+
const duplicates = [...mounted.entries()].filter(([, owners]) => owners.size > 1 || [...owners.values()].some((declarations) => declarations > 1));
|
|
70
89
|
if (duplicates.length === 0) {
|
|
71
90
|
return;
|
|
72
91
|
}
|
|
73
|
-
const lines = duplicates.map(([signature,
|
|
92
|
+
const lines = duplicates.map(([signature, owners]) => `${signature} | controllers: ${[...owners]
|
|
93
|
+
.map(([key, declarations]) => declarations > 1 ? `${key} (declared ${declarations} times)` : key)
|
|
94
|
+
.join(', ')}`);
|
|
74
95
|
fail(`${duplicates.length} route(s) are mounted more than once, and only the first one answers | ${lines.join(' ; ')} | This is the helper's own check (IGNIS boot does not detect it); pass { routes: false } to turn it off`);
|
|
75
96
|
};
|
|
76
97
|
/**
|
|
77
|
-
* Proves an IGNIS application boots, without
|
|
78
|
-
* sequence (`init()` then `initialize()`) and never calls `start()`,
|
|
79
|
-
* post-start hook runs.
|
|
98
|
+
* Proves an IGNIS application boots, without a datasource connection, a socket or a secrets provider:
|
|
99
|
+
* it runs the application's own boot sequence (`init()` then `initialize()`) and never calls `start()`,
|
|
100
|
+
* so no socket is opened and no post-start hook runs.
|
|
80
101
|
*
|
|
81
102
|
* Datasources are bound and resolved but not configured, unless named in `keepDataSources`; the
|
|
82
|
-
* secrets steps are skipped; every other step and every component runs as in a real boot.
|
|
103
|
+
* secrets steps are skipped; every other step and every component runs as in a real boot. A component
|
|
104
|
+
* that opens Redis, Kafka or S3 in `binding()` therefore runs for real: name it in `skipComponents`.
|
|
105
|
+
* When the boot fails inside a component, one warning names it; the rejection is still the original error. When the
|
|
83
106
|
* boot fails, the rejection is the original error. The application stays booted and is returned;
|
|
84
107
|
* a failed boot leaves a half-booted instance, so build a new one rather than reuse it.
|
|
85
108
|
*
|
|
@@ -107,6 +130,8 @@ export const assertApplicationBoots = async (application, options = {}) => {
|
|
|
107
130
|
const skippedSteps = new Set(skipSteps);
|
|
108
131
|
const patches = [];
|
|
109
132
|
let sweepsSeen = 0;
|
|
133
|
+
// The components whose `configure()` is running, innermost last: when the boot rejects, the last one is where it failed.
|
|
134
|
+
const configuringComponents = [];
|
|
110
135
|
const neutralise = async (opts) => {
|
|
111
136
|
const { namespace, binding } = opts;
|
|
112
137
|
const isDataSource = namespace === BindingNamespaces.DATASOURCE;
|
|
@@ -131,12 +156,23 @@ export const assertApplicationBoots = async (application, options = {}) => {
|
|
|
131
156
|
const wrappedRegister = async (opts) => {
|
|
132
157
|
sweepsSeen += 1;
|
|
133
158
|
const userBefore = opts.onBeforeConfigure;
|
|
159
|
+
const userAfter = opts.onAfterConfigure;
|
|
134
160
|
return originalRegisterDynamicBindings.call(target, {
|
|
135
161
|
...opts,
|
|
136
162
|
onBeforeConfigure: async ({ binding }) => {
|
|
137
163
|
await neutralise({ namespace: opts.namespace, binding });
|
|
164
|
+
if (opts.namespace === BindingNamespaces.COMPONENT) {
|
|
165
|
+
const instance = target.get({ key: binding.key, isOptional: true });
|
|
166
|
+
configuringComponents.push(String(instance?.constructor?.name ?? binding.key));
|
|
167
|
+
}
|
|
138
168
|
await userBefore?.({ binding });
|
|
139
169
|
},
|
|
170
|
+
onAfterConfigure: async ({ binding, instance }) => {
|
|
171
|
+
if (opts.namespace === BindingNamespaces.COMPONENT) {
|
|
172
|
+
configuringComponents.pop();
|
|
173
|
+
}
|
|
174
|
+
await userAfter?.({ binding, instance });
|
|
175
|
+
},
|
|
140
176
|
});
|
|
141
177
|
};
|
|
142
178
|
patches.push(patchProperty({ object: target, name: 'registerDynamicBindings', value: wrappedRegister }));
|
|
@@ -190,6 +226,15 @@ export const assertApplicationBoots = async (application, options = {}) => {
|
|
|
190
226
|
}
|
|
191
227
|
return target;
|
|
192
228
|
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
const failing = configuringComponents.at(-1);
|
|
231
|
+
if (failing) {
|
|
232
|
+
target.logger
|
|
233
|
+
.for('assertApplicationBoots')
|
|
234
|
+
.warn(`The boot failed inside component ${failing} | If it needs infrastructure, skipComponents: ['${failing}'] skips it, and skipBindings lists the services that depend on it`);
|
|
235
|
+
}
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
193
238
|
finally {
|
|
194
239
|
for (const patch of patches.reverse()) {
|
|
195
240
|
patch.restore();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assert-application-boots.js","sourceRoot":"","sources":["../../../src/testing/assert-application-boots.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BaseApplication } from '@/base/applications';\nimport { ServerBootSteps, type TServerBootStep } from '@/base/applications';\nimport type { AnyType, TClass } from '@venizia/ignis-helpers/common';\nimport { getError } from '@venizia/ignis-helpers/core';\nimport { assertBindingsResolve } from '@venizia/ignis-inversion';\nimport {\n type AbstractRestController,\n type Binding,\n BindingNamespaces,\n BindingValueTypes,\n ControllerTransports,\n CoreBindings,\n MetadataRegistry,\n} from '@venizia/ignis-kernel';\n\n/** A class (matched against the instance's constructor) or a string (matched against the binding key or the class name). */\nexport type TBootProbeTarget = TClass<AnyType> | string;\n\nexport interface IAssertApplicationBootsOptions {\n /**\n * Datasources whose real `configure()` runs. Every other datasource is bound and resolved but not\n * configured, so no connection is made. Close a kept one with `await application.stop()`.\n */\n keepDataSources?: TBootProbeTarget[];\n\n /**\n * Components whose `configure()` (and so their `binding()`) is skipped, for the few that need\n * infrastructure. Components run by default. What a skipped component would have registered is\n * not registered.\n */\n skipComponents?: TBootProbeTarget[];\n\n /**\n * Boot steps that do not run. Default: `hydrateSecrets` and `wireSecretRotatables`, so a remote\n * secrets provider is never contacted. A value REPLACES the default; an unknown name rejects\n * before anything boots.\n */\n skipSteps?: TServerBootStep[];\n\n /**\n * Default `true`: after boot, resolves every service and repository with the shared core of\n * `@venizia/ignis-inversion`, so an injection nobody references at boot still fails. An explicit value\n * wins over the application's own `bootChecks`, whose boot step is off for the run.\n */\n verifyBindings?: boolean;\n\n /**\n * Services or repositories the verification leaves unresolved, for one whose constructor needs\n * infrastructure. A class (exact), a class name or a binding key. A name that matches nothing rejects.\n */\n skipBindings?: TBootProbeTarget[];\n\n /** Default `false`: an application that registered no controller proves nothing, for example when the artifact index was empty. */\n allowNoControllers?: boolean;\n\n /** Default `true`: this helper's own check that no method and path is mounted twice. IGNIS boot does not detect that. */\n routes?: boolean;\n}\n\n/** The boot sequence is the contract; these two are what the helper wraps on one instance. */\nconst WRAPPED_METHODS = ['registerDynamicBindings', 'getBootSequence'] as const;\n\nconst DEFAULT_SKIPPED_STEPS: TServerBootStep[] = [\n ServerBootSteps.HYDRATE_SECRETS,\n ServerBootSteps.WIRE_SECRET_ROTATABLES,\n];\n\n/** Methods a route entry may carry that are middleware, not a mounted handler. */\nconst NON_ROUTE_METHOD = 'ALL';\n\nconst bootedApplications = new WeakSet<object>();\n\ninterface IProbeBinding {\n key: string;\n}\n\ninterface IDynamicBindingsOptions {\n namespace: string;\n onBeforeConfigure?: (opts: { binding: Binding<AnyType> }) => Promise<void>;\n onAfterConfigure?: (opts: { binding: Binding<AnyType>; instance: AnyType }) => Promise<void>;\n}\n\n/** What the helper replaced, so every path puts it back. */\ninterface IPatch {\n restore: () => void;\n}\n\nconst matchesTarget = (opts: {\n targets: TBootProbeTarget[];\n binding: IProbeBinding;\n instance: AnyType;\n}): boolean => {\n const { targets, binding, instance } = opts;\n const className: string | undefined = instance?.constructor?.name;\n\n return targets.some((target) =>\n typeof target === 'string'\n ? target === binding.key || target === className\n : instance?.constructor === target,\n );\n};\n\nconst fail = (message: string): never => {\n throw getError({ message: `[assertApplicationBoots] ${message}` });\n};\n\n/** Replaces `object[name]` and returns the way back, even when `name` was inherited. */\nconst patchProperty = (opts: { object: AnyType; name: string; value: unknown }): IPatch => {\n const { object, name, value } = opts;\n const descriptor = Object.getOwnPropertyDescriptor(object, name);\n\n Object.defineProperty(object, name, { value, configurable: true, writable: true });\n\n return {\n restore: () => {\n if (descriptor) {\n Object.defineProperty(object, name, descriptor);\n return;\n }\n\n delete object[name];\n },\n };\n};\n\nconst normalizePath = (path: string): string => {\n const withParams = path.replace(/:[A-Za-z0-9_]+/g, ':param').replace(/\\/{2,}/g, '/');\n return withParams.length > 1 ? withParams.replace(/\\/$/, '') : withParams;\n};\n\n/** The helper's own check: IGNIS mounts a method and path twice without complaint, and the first one wins. */\nconst assertNoDuplicateRoutes = (opts: { application: BaseApplication }): void => {\n const { application } = opts;\n const mounted = new Map<string, string[]>();\n\n for (const binding of application.findByTag<AbstractRestController>({\n tag: BindingNamespaces.CONTROLLER,\n })) {\n const target = binding.getBindingMeta({ type: BindingValueTypes.CLASS });\n const metadata = target\n ? MetadataRegistry.getInstance().getControllerMetadata({ target })\n : undefined;\n\n if (!metadata?.path || metadata.transport === ControllerTransports.GRPC) {\n continue;\n }\n\n const instance = application.get<AbstractRestController>({\n key: binding.key,\n isOptional: true,\n });\n for (const route of instance?.getRouter().routes ?? []) {\n if (route.method === NON_ROUTE_METHOD) {\n continue;\n }\n\n const signature = `${route.method} ${normalizePath(`${metadata.path}/${route.path}`)}`;\n mounted.set(signature, [...(mounted.get(signature) ?? []), binding.key]);\n }\n }\n\n const duplicates = [...mounted.entries()].filter(([, keys]) => keys.length > 1);\n if (duplicates.length === 0) {\n return;\n }\n\n const lines = duplicates.map(\n ([signature, keys]) => `${signature} | controllers: ${keys.join(', ')}`,\n );\n fail(\n `${duplicates.length} route(s) are mounted more than once, and only the first one answers | ${lines.join(' ; ')} | This is the helper's own check (IGNIS boot does not detect it); pass { routes: false } to turn it off`,\n );\n};\n\n/**\n * Proves an IGNIS application boots, without real infrastructure: it runs the application's own boot\n * sequence (`init()` then `initialize()`) and never calls `start()`, so no socket is opened and no\n * post-start hook runs.\n *\n * Datasources are bound and resolved but not configured, unless named in `keepDataSources`; the\n * secrets steps are skipped; every other step and every component runs as in a real boot. When the\n * boot fails, the rejection is the original error. The application stays booted and is returned;\n * a failed boot leaves a half-booted instance, so build a new one rather than reuse it.\n *\n * It wraps `registerDynamicBindings` and `getBootSequence` on THIS instance only (an application's\n * own override of either still runs) and restores them whatever happens.\n */\nexport const assertApplicationBoots = async <Application extends BaseApplication>(\n application: Application | (() => Application | Promise<Application>),\n options: IAssertApplicationBootsOptions = {},\n): Promise<Application> => {\n const {\n keepDataSources = [],\n skipComponents = [],\n skipSteps = DEFAULT_SKIPPED_STEPS,\n verifyBindings = true,\n skipBindings = [],\n allowNoControllers = false,\n routes = true,\n } = options;\n\n for (const step of skipSteps) {\n if (!ServerBootSteps.isValid(step)) {\n fail(\n `Unknown boot step in skipSteps: '${String(step)}' | Expected one of: ${[...ServerBootSteps.SCHEME_SET].join(', ')}`,\n );\n }\n }\n\n const target = typeof application === 'function' ? await application() : application;\n\n if (\n bootedApplications.has(target) ||\n Object.keys(target['registeredBindings'] ?? {}).length > 0\n ) {\n fail(\n `This application instance was already booted | A failed or finished boot leaves registrations behind, so build a new instance (pass a factory) for each call`,\n );\n }\n bootedApplications.add(target);\n\n for (const name of WRAPPED_METHODS) {\n if (typeof target[name] !== 'function') {\n fail(\n `The kernel method '${name}' is missing on this application | The helper cannot keep a boot offline without it, so it refuses to boot`,\n );\n }\n }\n\n const skippedSteps = new Set<string>(skipSteps);\n const patches: IPatch[] = [];\n let sweepsSeen = 0;\n\n const neutralise = async (opts: {\n namespace: string;\n binding: Binding<AnyType>;\n }): Promise<void> => {\n const { namespace, binding } = opts;\n const isDataSource = namespace === BindingNamespaces.DATASOURCE;\n const isComponent = namespace === BindingNamespaces.COMPONENT;\n if (!isDataSource && !isComponent) {\n return;\n }\n\n const instance = target.get<AnyType>({ key: binding.key, isOptional: false });\n if (typeof instance?.configure !== 'function') {\n return;\n }\n\n const shouldRun = isDataSource\n ? matchesTarget({ targets: keepDataSources, binding, instance })\n : !matchesTarget({ targets: skipComponents, binding, instance });\n if (shouldRun) {\n return;\n }\n\n patches.push(patchProperty({ object: instance, name: 'configure', value: async () => {} }));\n };\n\n const originalRegisterDynamicBindings = target['registerDynamicBindings'] as unknown as (\n opts: IDynamicBindingsOptions,\n ) => Promise<void>;\n const originalGetBootSequence = target['getBootSequence'] as unknown as () => Array<{\n name: string;\n }>;\n\n const wrappedRegister = async (opts: IDynamicBindingsOptions): Promise<void> => {\n sweepsSeen += 1;\n const userBefore = opts.onBeforeConfigure;\n\n return originalRegisterDynamicBindings.call(target, {\n ...opts,\n onBeforeConfigure: async ({ binding }) => {\n await neutralise({ namespace: opts.namespace, binding });\n await userBefore?.({ binding });\n },\n });\n };\n\n patches.push(\n patchProperty({ object: target, name: 'registerDynamicBindings', value: wrappedRegister }),\n );\n patches.push(\n patchProperty({\n object: target,\n name: 'getBootSequence',\n value: () =>\n originalGetBootSequence.call(target).filter((step) => !skippedSteps.has(step.name)),\n }),\n );\n\n if ((target['registerDynamicBindings'] as unknown) !== wrappedRegister) {\n patches.forEach((patch) => patch.restore());\n fail(\n `The wrap of 'registerDynamicBindings' did not take effect | Refusing to boot against real infrastructure`,\n );\n }\n\n const configs = target['configs'];\n const originalBootChecks = configs.bootChecks;\n const originalBinding = originalBootChecks?.binding;\n configs.bootChecks = {\n ...originalBootChecks,\n binding: {\n allowManual: originalBinding?.allowManual ?? true,\n allowOverride: originalBinding?.allowOverride ?? true,\n doVerify: false,\n },\n };\n\n try {\n if (!target.isBound({ key: CoreBindings.APPLICATION_INSTANCE })) {\n target.init();\n }\n\n await target.initialize();\n\n const sweepsExpected = [\n ServerBootSteps.REGISTER_DATA_SOURCES,\n ServerBootSteps.REGISTER_COMPONENTS,\n ServerBootSteps.REGISTER_CONTRIBUTED_DATA_SOURCES,\n ].some((step) => !skippedSteps.has(step));\n if (sweepsExpected && sweepsSeen === 0) {\n fail(\n `The boot never went through 'registerDynamicBindings' | The wrap did not take effect, so datasources may have been configured`,\n );\n }\n\n if (verifyBindings) {\n assertBindingsResolve({\n container: target,\n namespaces: [BindingNamespaces.SERVICE, BindingNamespaces.REPOSITORY],\n skip: skipBindings,\n scope: 'verifyBindings',\n });\n }\n\n if (\n !allowNoControllers &&\n target.findByTag({ tag: BindingNamespaces.CONTROLLER }).length === 0\n ) {\n fail(\n `The application registered no controller | A boot that registers nothing proves nothing: check the artifact index (for example the AutoArtifacts plugin under 'bun test'), or pass { allowNoControllers: true }`,\n );\n }\n\n if (routes) {\n assertNoDuplicateRoutes({ application: target });\n }\n\n return target;\n } finally {\n for (const patch of patches.reverse()) {\n patch.restore();\n }\n\n if (originalBootChecks === undefined) {\n delete configs.bootChecks;\n } else {\n configs.bootChecks = originalBootChecks;\n }\n }\n};\n"]}
|
|
1
|
+
{"version":3,"file":"assert-application-boots.js","sourceRoot":"","sources":["../../../src/testing/assert-application-boots.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BaseApplication } from '@/base/applications';\nimport { ServerBootSteps, type TServerBootStep } from '@/base/applications';\nimport type { AnyType, TClass } from '@venizia/ignis-helpers/common';\nimport { getError } from '@venizia/ignis-helpers/core';\nimport { assertBindingsResolve } from '@venizia/ignis-inversion';\nimport {\n type AbstractRestController,\n type Binding,\n BindingNamespaces,\n BindingValueTypes,\n ControllerTransports,\n CoreBindings,\n MetadataRegistry,\n} from '@venizia/ignis-kernel';\n\n/** A class (matched against the instance's constructor) or a string (matched against the binding key or the class name). */\nexport type TBootProbeTarget = TClass<AnyType> | string;\n\nexport interface IAssertApplicationBootsOptions {\n /**\n * Datasources whose real `configure()` runs. Every other datasource is bound and resolved but not\n * configured, so no connection is made. Close a kept one with `await application.stop()`.\n */\n keepDataSources?: TBootProbeTarget[];\n\n /**\n * Components whose `configure()` (and so their `binding()`) is skipped, for the few that need\n * infrastructure. Components run by default. What a skipped component would have registered is\n * not registered.\n */\n skipComponents?: TBootProbeTarget[];\n\n /**\n * Boot steps that do not run. Default: `hydrateSecrets` and `wireSecretRotatables`, so a remote\n * secrets provider is never contacted. A value REPLACES the default; an unknown name rejects\n * before anything boots.\n */\n skipSteps?: TServerBootStep[];\n\n /**\n * Default `true`: after boot, resolves every service and repository with the shared core of\n * `@venizia/ignis-inversion`, so an injection nobody references at boot still fails. An explicit value\n * wins over the application's own `bootChecks`, whose boot step is off for the run.\n */\n verifyBindings?: boolean;\n\n /**\n * Services or repositories the verification leaves unresolved, for one whose constructor needs\n * infrastructure. A class (exact), a class name or a binding key. A name that matches nothing rejects.\n */\n skipBindings?: TBootProbeTarget[];\n\n /** Default `false`: an application that registered no controller proves nothing, for example when the artifact index was empty. */\n allowNoControllers?: boolean;\n\n /** Default `true`: this helper's own check that no method and path is mounted twice. IGNIS boot does not detect that. */\n routes?: boolean;\n}\n\n/** The boot sequence is the contract; these two are what the helper wraps on one instance. */\nconst WRAPPED_METHODS = ['registerDynamicBindings', 'getBootSequence'] as const;\n\nconst DEFAULT_SKIPPED_STEPS: TServerBootStep[] = [\n ServerBootSteps.HYDRATE_SECRETS,\n ServerBootSteps.WIRE_SECRET_ROTATABLES,\n];\n\n/** Methods a route entry may carry that are middleware, not a mounted handler. */\nconst NON_ROUTE_METHOD = 'ALL';\n\nconst bootedApplications = new WeakSet<object>();\n\ninterface IProbeBinding {\n key: string;\n}\n\ninterface IDynamicBindingsOptions {\n namespace: string;\n onBeforeConfigure?: (opts: { binding: Binding<AnyType> }) => Promise<void>;\n onAfterConfigure?: (opts: { binding: Binding<AnyType>; instance: AnyType }) => Promise<void>;\n}\n\n/** What the helper replaced, so every path puts it back. */\ninterface IPatch {\n restore: () => void;\n}\n\nconst matchesTarget = (opts: {\n targets: TBootProbeTarget[];\n binding: IProbeBinding;\n instance: AnyType;\n}): boolean => {\n const { targets, binding, instance } = opts;\n const className: string | undefined = instance?.constructor?.name;\n\n return targets.some((target) =>\n typeof target === 'string'\n ? target === binding.key || target === className\n : instance?.constructor === target,\n );\n};\n\nconst fail = (message: string): never => {\n throw getError({ message: `[assertApplicationBoots] ${message}` });\n};\n\n/** Replaces `object[name]` and returns the way back, even when `name` was inherited. */\nconst patchProperty = (opts: { object: AnyType; name: string; value: unknown }): IPatch => {\n const { object, name, value } = opts;\n const descriptor = Object.getOwnPropertyDescriptor(object, name);\n\n Object.defineProperty(object, name, { value, configurable: true, writable: true });\n\n return {\n restore: () => {\n if (descriptor) {\n Object.defineProperty(object, name, descriptor);\n return;\n }\n\n delete object[name];\n },\n };\n};\n\nconst normalizePath = (path: string): string => {\n const withParams = path.replace(/:[A-Za-z0-9_]+/g, ':param').replace(/\\/{2,}/g, '/');\n return withParams.length > 1 ? withParams.replace(/\\/$/, '') : withParams;\n};\n\n/** The helper's own check: IGNIS mounts a method and path twice without complaint, and the first one wins. */\nconst assertNoDuplicateRoutes = (opts: { application: BaseApplication }): void => {\n const { application } = opts;\n // signature -> controller key -> how many times that controller declares it\n const mounted = new Map<string, Map<string, number>>();\n const record = (signature: string, key: string, declarations: number): void => {\n const owners = mounted.get(signature) ?? new Map<string, number>();\n owners.set(key, Math.max(owners.get(key) ?? 0, declarations));\n mounted.set(signature, owners);\n };\n\n for (const binding of application.findByTag<AbstractRestController>({\n tag: BindingNamespaces.CONTROLLER,\n })) {\n const target = binding.getBindingMeta({ type: BindingValueTypes.CLASS });\n const metadata = target\n ? MetadataRegistry.getInstance().getControllerMetadata({ target })\n : undefined;\n\n if (!metadata?.path || metadata.transport === ControllerTransports.GRPC) {\n continue;\n }\n\n const instance = application.get<AbstractRestController>({\n key: binding.key,\n isOptional: true,\n });\n // Hono's `routes` has one entry PER HANDLER (a validator, an authenticate and the handler of one route are three\n // entries with the same method and path), so an entry is not a declaration: a controller counts once per signature.\n for (const route of instance?.getRouter().routes ?? []) {\n if (route.method === NON_ROUTE_METHOD) {\n continue;\n }\n\n record(`${route.method} ${normalizePath(`${metadata.path}/${route.path}`)}`, binding.key, 1);\n }\n\n // What the router cannot show: one controller declaring a method and path twice. Decorated routes are one\n // metadata entry per declaration, so those are counted there.\n const declared = new Map<string, number>();\n for (const route of MetadataRegistry.getInstance()\n .getRoutes({ target: (target as TClass<AnyType>).prototype })\n ?.values() ?? []) {\n const signature = `${String(route.method).toUpperCase()} ${normalizePath(`${metadata.path}/${route.path}`)}`;\n declared.set(signature, (declared.get(signature) ?? 0) + 1);\n }\n for (const [signature, declarations] of declared) {\n record(signature, binding.key, declarations);\n }\n }\n\n const duplicates = [...mounted.entries()].filter(\n ([, owners]) =>\n owners.size > 1 || [...owners.values()].some((declarations) => declarations > 1),\n );\n if (duplicates.length === 0) {\n return;\n }\n\n const lines = duplicates.map(\n ([signature, owners]) =>\n `${signature} | controllers: ${[...owners]\n .map(([key, declarations]) =>\n declarations > 1 ? `${key} (declared ${declarations} times)` : key,\n )\n .join(', ')}`,\n );\n fail(\n `${duplicates.length} route(s) are mounted more than once, and only the first one answers | ${lines.join(' ; ')} | This is the helper's own check (IGNIS boot does not detect it); pass { routes: false } to turn it off`,\n );\n};\n\n/**\n * Proves an IGNIS application boots, without a datasource connection, a socket or a secrets provider:\n * it runs the application's own boot sequence (`init()` then `initialize()`) and never calls `start()`,\n * so no socket is opened and no post-start hook runs.\n *\n * Datasources are bound and resolved but not configured, unless named in `keepDataSources`; the\n * secrets steps are skipped; every other step and every component runs as in a real boot. A component\n * that opens Redis, Kafka or S3 in `binding()` therefore runs for real: name it in `skipComponents`.\n * When the boot fails inside a component, one warning names it; the rejection is still the original error. When the\n * boot fails, the rejection is the original error. The application stays booted and is returned;\n * a failed boot leaves a half-booted instance, so build a new one rather than reuse it.\n *\n * It wraps `registerDynamicBindings` and `getBootSequence` on THIS instance only (an application's\n * own override of either still runs) and restores them whatever happens.\n */\nexport const assertApplicationBoots = async <Application extends BaseApplication>(\n application: Application | (() => Application | Promise<Application>),\n options: IAssertApplicationBootsOptions = {},\n): Promise<Application> => {\n const {\n keepDataSources = [],\n skipComponents = [],\n skipSteps = DEFAULT_SKIPPED_STEPS,\n verifyBindings = true,\n skipBindings = [],\n allowNoControllers = false,\n routes = true,\n } = options;\n\n for (const step of skipSteps) {\n if (!ServerBootSteps.isValid(step)) {\n fail(\n `Unknown boot step in skipSteps: '${String(step)}' | Expected one of: ${[...ServerBootSteps.SCHEME_SET].join(', ')}`,\n );\n }\n }\n\n const target = typeof application === 'function' ? await application() : application;\n\n if (\n bootedApplications.has(target) ||\n Object.keys(target['registeredBindings'] ?? {}).length > 0\n ) {\n fail(\n `This application instance was already booted | A failed or finished boot leaves registrations behind, so build a new instance (pass a factory) for each call`,\n );\n }\n bootedApplications.add(target);\n\n for (const name of WRAPPED_METHODS) {\n if (typeof target[name] !== 'function') {\n fail(\n `The kernel method '${name}' is missing on this application | The helper cannot keep a boot offline without it, so it refuses to boot`,\n );\n }\n }\n\n const skippedSteps = new Set<string>(skipSteps);\n const patches: IPatch[] = [];\n let sweepsSeen = 0;\n // The components whose `configure()` is running, innermost last: when the boot rejects, the last one is where it failed.\n const configuringComponents: string[] = [];\n\n const neutralise = async (opts: {\n namespace: string;\n binding: Binding<AnyType>;\n }): Promise<void> => {\n const { namespace, binding } = opts;\n const isDataSource = namespace === BindingNamespaces.DATASOURCE;\n const isComponent = namespace === BindingNamespaces.COMPONENT;\n if (!isDataSource && !isComponent) {\n return;\n }\n\n const instance = target.get<AnyType>({ key: binding.key, isOptional: false });\n if (typeof instance?.configure !== 'function') {\n return;\n }\n\n const shouldRun = isDataSource\n ? matchesTarget({ targets: keepDataSources, binding, instance })\n : !matchesTarget({ targets: skipComponents, binding, instance });\n if (shouldRun) {\n return;\n }\n\n patches.push(patchProperty({ object: instance, name: 'configure', value: async () => {} }));\n };\n\n const originalRegisterDynamicBindings = target['registerDynamicBindings'] as unknown as (\n opts: IDynamicBindingsOptions,\n ) => Promise<void>;\n const originalGetBootSequence = target['getBootSequence'] as unknown as () => Array<{\n name: string;\n }>;\n\n const wrappedRegister = async (opts: IDynamicBindingsOptions): Promise<void> => {\n sweepsSeen += 1;\n const userBefore = opts.onBeforeConfigure;\n const userAfter = opts.onAfterConfigure;\n\n return originalRegisterDynamicBindings.call(target, {\n ...opts,\n onBeforeConfigure: async ({ binding }) => {\n await neutralise({ namespace: opts.namespace, binding });\n if (opts.namespace === BindingNamespaces.COMPONENT) {\n const instance = target.get<AnyType>({ key: binding.key, isOptional: true });\n configuringComponents.push(String(instance?.constructor?.name ?? binding.key));\n }\n await userBefore?.({ binding });\n },\n onAfterConfigure: async ({ binding, instance }) => {\n if (opts.namespace === BindingNamespaces.COMPONENT) {\n configuringComponents.pop();\n }\n await userAfter?.({ binding, instance });\n },\n });\n };\n\n patches.push(\n patchProperty({ object: target, name: 'registerDynamicBindings', value: wrappedRegister }),\n );\n patches.push(\n patchProperty({\n object: target,\n name: 'getBootSequence',\n value: () =>\n originalGetBootSequence.call(target).filter((step) => !skippedSteps.has(step.name)),\n }),\n );\n\n if ((target['registerDynamicBindings'] as unknown) !== wrappedRegister) {\n patches.forEach((patch) => patch.restore());\n fail(\n `The wrap of 'registerDynamicBindings' did not take effect | Refusing to boot against real infrastructure`,\n );\n }\n\n const configs = target['configs'];\n const originalBootChecks = configs.bootChecks;\n const originalBinding = originalBootChecks?.binding;\n configs.bootChecks = {\n ...originalBootChecks,\n binding: {\n allowManual: originalBinding?.allowManual ?? true,\n allowOverride: originalBinding?.allowOverride ?? true,\n doVerify: false,\n },\n };\n\n try {\n if (!target.isBound({ key: CoreBindings.APPLICATION_INSTANCE })) {\n target.init();\n }\n\n await target.initialize();\n\n const sweepsExpected = [\n ServerBootSteps.REGISTER_DATA_SOURCES,\n ServerBootSteps.REGISTER_COMPONENTS,\n ServerBootSteps.REGISTER_CONTRIBUTED_DATA_SOURCES,\n ].some((step) => !skippedSteps.has(step));\n if (sweepsExpected && sweepsSeen === 0) {\n fail(\n `The boot never went through 'registerDynamicBindings' | The wrap did not take effect, so datasources may have been configured`,\n );\n }\n\n if (verifyBindings) {\n assertBindingsResolve({\n container: target,\n namespaces: [BindingNamespaces.SERVICE, BindingNamespaces.REPOSITORY],\n skip: skipBindings,\n scope: 'verifyBindings',\n });\n }\n\n if (\n !allowNoControllers &&\n target.findByTag({ tag: BindingNamespaces.CONTROLLER }).length === 0\n ) {\n fail(\n `The application registered no controller | A boot that registers nothing proves nothing: check the artifact index (for example the AutoArtifacts plugin under 'bun test'), or pass { allowNoControllers: true }`,\n );\n }\n\n if (routes) {\n assertNoDuplicateRoutes({ application: target });\n }\n\n return target;\n } catch (error) {\n const failing = configuringComponents.at(-1);\n if (failing) {\n target.logger\n .for('assertApplicationBoots')\n .warn(\n `The boot failed inside component ${failing} | If it needs infrastructure, skipComponents: ['${failing}'] skips it, and skipBindings lists the services that depend on it`,\n );\n }\n\n throw error;\n } finally {\n for (const patch of patches.reverse()) {\n patch.restore();\n }\n\n if (originalBootChecks === undefined) {\n delete configs.bootChecks;\n } else {\n configs.bootChecks = originalBootChecks;\n }\n }\n};\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Boot test: runs the application's own boot (every binding, every controller injection) without a
|
|
2
|
-
// port
|
|
2
|
+
// port or a database connection. A datasource added later is skipped unless you name it in `keepDataSources`.
|
|
3
|
+
// Components run for real: one that needs Redis, Kafka or S3 goes in `skipComponents`.
|
|
3
4
|
import { describe, expect, test } from 'bun:test';
|
|
4
5
|
import { assertApplicationBoots } from '@venizia/ignis/testing';
|
|
5
6
|
import { application } from '#src/application';
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@hono/zod-openapi": "^1.6.3",
|
|
16
16
|
"@scalar/hono-api-reference": "^0.12.13",
|
|
17
|
-
"@venizia/ignis": "0.4.
|
|
18
|
-
"@venizia/ignis-boot": "0.4.
|
|
19
|
-
"@venizia/ignis-helpers": "0.4.
|
|
17
|
+
"@venizia/ignis": "0.4.5",
|
|
18
|
+
"@venizia/ignis-boot": "0.4.5",
|
|
19
|
+
"@venizia/ignis-helpers": "0.4.5",
|
|
20
20
|
"hono": "^4.13.13",
|
|
21
21
|
"winston": "^3.19.0",
|
|
22
22
|
"winston-daily-rotate-file": "^5.0.0",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/bun": "^1.4.3",
|
|
27
|
-
"@venizia/dev-configs": "0.4.
|
|
27
|
+
"@venizia/dev-configs": "0.4.5",
|
|
28
28
|
"eslint": "^10.13.0",
|
|
29
29
|
"oxlint": "1.89.0",
|
|
30
30
|
"oxlint-tsgolint": "7.0.2004",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"typecheck": "bun check",
|
|
38
38
|
"lint": "devz lint",
|
|
39
39
|
"lint:fix": "devz lint --fix",
|
|
40
|
-
"test": "bun test",
|
|
40
|
+
"test": "bun test --preload @venizia/dev-configs/testing/tmpdir-preload",
|
|
41
41
|
"clean": "devz clean"
|
|
42
42
|
},
|
|
43
43
|
"checks": [
|
|
@@ -4,21 +4,21 @@
|
|
|
4
4
|
"dependencies": {},
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@types/bun": "^1.4.3",
|
|
7
|
-
"@venizia/dev-configs": "0.4.
|
|
8
|
-
"@venizia/ignis": "0.4.
|
|
7
|
+
"@venizia/dev-configs": "0.4.5",
|
|
8
|
+
"@venizia/ignis": "0.4.5",
|
|
9
9
|
"eslint": "^10.13.0",
|
|
10
10
|
"oxlint": "1.89.0",
|
|
11
11
|
"oxlint-tsgolint": "7.0.2004",
|
|
12
12
|
"typescript": "^6.0.3"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"@venizia/ignis": "0.4.
|
|
15
|
+
"@venizia/ignis": "0.4.5"
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"typecheck": "bun check",
|
|
19
19
|
"lint": "devz lint",
|
|
20
20
|
"lint:fix": "devz lint --fix",
|
|
21
|
-
"test": "bun test",
|
|
21
|
+
"test": "bun test --preload @venizia/dev-configs/testing/tmpdir-preload",
|
|
22
22
|
"clean": "devz clean"
|
|
23
23
|
},
|
|
24
24
|
"checks": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@venizia/ignis",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "TypeScript server framework with decorator-based DI, the repository pattern and a component system, on Hono. Features auto-generated OpenAPI docs, Drizzle ORM type-safe SQL, JWT/Basic authentication, Casbin authorization, convention-based bootstrapping, and pluggable components for health checks, Swagger UI, mail, Socket.IO, and static assets. Built for Bun and Node.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"access-control",
|
|
@@ -187,11 +187,11 @@
|
|
|
187
187
|
"prepublishOnly": "make rebuild"
|
|
188
188
|
},
|
|
189
189
|
"dependencies": {
|
|
190
|
-
"@venizia/ignis-connectors": "0.4.
|
|
190
|
+
"@venizia/ignis-connectors": "0.4.5",
|
|
191
191
|
"@venizia/ignis-filter": "0.4.4",
|
|
192
|
-
"@venizia/ignis-helpers": "0.4.
|
|
192
|
+
"@venizia/ignis-helpers": "0.4.5",
|
|
193
193
|
"@venizia/ignis-inversion": "0.4.4",
|
|
194
|
-
"@venizia/ignis-kernel": "0.4.
|
|
194
|
+
"@venizia/ignis-kernel": "0.4.5",
|
|
195
195
|
"reflect-metadata": "^0.2.2"
|
|
196
196
|
},
|
|
197
197
|
"devDependencies": {
|
|
@@ -204,8 +204,8 @@
|
|
|
204
204
|
"@types/bun": "^1.4.3",
|
|
205
205
|
"@types/nodemailer": "^8.0.2",
|
|
206
206
|
"@types/pg": "^8.23.1",
|
|
207
|
-
"@venizia/dev-configs": "0.4.
|
|
208
|
-
"@venizia/ignis-boot": "0.4.
|
|
207
|
+
"@venizia/dev-configs": "0.4.5",
|
|
208
|
+
"@venizia/ignis-boot": "0.4.5",
|
|
209
209
|
"casbin": "^5.51.1",
|
|
210
210
|
"eslint": "^10.13.0",
|
|
211
211
|
"meilisearch": "^0.62.0",
|