@pattern-stack/codegen 0.3.2 → 0.4.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/CHANGELOG.md +14 -0
- package/README.md +2 -1
- package/dist/runtime/shared/openapi/error-response.dto.d.ts +33 -0
- package/dist/runtime/shared/openapi/error-response.dto.js +13 -0
- package/dist/runtime/shared/openapi/error-response.dto.js.map +1 -0
- package/dist/runtime/shared/openapi/errors.d.ts +30 -0
- package/dist/runtime/shared/openapi/errors.js +24 -0
- package/dist/runtime/shared/openapi/errors.js.map +1 -0
- package/dist/runtime/shared/openapi/index.d.ts +5 -0
- package/dist/runtime/shared/openapi/index.js +115 -0
- package/dist/runtime/shared/openapi/index.js.map +1 -0
- package/dist/runtime/shared/openapi/registry.d.ts +82 -0
- package/dist/runtime/shared/openapi/registry.js +107 -0
- package/dist/runtime/shared/openapi/registry.js.map +1 -0
- package/dist/runtime/shared/openapi/registry.tokens.d.ts +15 -0
- package/dist/runtime/shared/openapi/registry.tokens.js +6 -0
- package/dist/runtime/shared/openapi/registry.tokens.js.map +1 -0
- package/dist/runtime/subsystems/sync/sync-audit.schema.d.ts +2 -2
- package/dist/src/cli/index.js +1888 -1074
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +10 -1
- package/templates/entity/new/backend/application/schemas/dto.ejs.t +31 -0
- package/templates/entity/new/backend/modules/core/module.ejs.t +21 -2
- package/templates/entity/new/backend/presentation/controller.ejs.t +74 -0
- package/templates/entity/new/clean-lite-ps/controller.ejs.t +48 -0
- package/templates/entity/new/clean-lite-ps/module.ejs.t +24 -2
- package/templates/entity/new/prompt.js +2 -0
- package/templates/subsystem/openapi-config/codegen-config-openapi-block.ejs.t +35 -0
- package/templates/subsystem/openapi-config/prompt.js +23 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.4.1] — 2026-04-21
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`codegen project upgrade-openapi`** — surgical AST-based codemod that brings an existing consumer `src/app.module.ts` + `src/main.ts` up to the OPENAPI-4 shape `project init` emits on a fresh project. Merges `@nestjs/common` imports, adds `OpenApiModule` (the `@Global()` wrapper around `OPENAPI_REGISTRY`), wires it into `AppModule.imports`, injects the two-pass Swagger bootstrap into `main.ts`, and vendors `src/shared/openapi/*`. Idempotent; `--dry-run` and `--path` supported. Proof-of-concept for issue #188 (additive subsystem install, targeted for 0.5.0).
|
|
11
|
+
- **`src/cli/shared/ast-patch.ts`** — ts-morph patching primitives (`ensureImport`, `ensureClassDeclaration`, `ensureModuleImportEntry`, `ensureMainSwaggerBlock`). Each is idempotent and bails cleanly on exotic shapes (factory-based modules, non-array `imports`, missing `@Module()` decorator).
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- **`project init` skip reasons** — when `app.module.ts` or `main.ts` exist, the skip message now points at `codegen project upgrade-openapi` as the automated path (previously instructed manual wiring only).
|
|
15
|
+
|
|
16
|
+
### Dependencies
|
|
17
|
+
- Added `ts-morph` (runtime dep; CLI-side) for AST manipulation.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
7
21
|
### Added
|
|
8
22
|
- **Noun-verb CLI** — Clipanion-based CLI replacing the legacy single-file handler. Commands: `entity`, `subsystem`, `project`, `dev`. Each noun has a summary pane with dynamic hints. (ADR-015)
|
|
9
23
|
- **UI toolkit** — chalk theme tokens, icons with ASCII fallback, Ora spinners, pane/hints rendering, `--json` mode across all commands. (ADR-016)
|
package/README.md
CHANGED
|
@@ -96,12 +96,13 @@ codegen subsystem install cache # key-value cache with TTL
|
|
|
96
96
|
codegen subsystem install storage # file storage (local filesystem)
|
|
97
97
|
codegen subsystem install sync # external-system sync engine (IChangeSource + orchestrator + audit log)
|
|
98
98
|
codegen subsystem install bridge # event-to-job bridge (durable async fanout via @JobHandler.triggers)
|
|
99
|
+
codegen subsystem install openapi-config # OpenAPI/Swagger — Zod DTOs as /docs-json + Swagger UI. See docs/CONSUMER-SETUP.md §OpenAPI
|
|
99
100
|
codegen subsystem list # show installed + available
|
|
100
101
|
|
|
101
102
|
codegen events consumers <type> # list all Tier 1/2/3 consumers of an event type
|
|
102
103
|
```
|
|
103
104
|
|
|
104
|
-
Each subsystem generates a protocol (interface), Drizzle backend (Postgres), memory backend (tests), and a NestJS module with `forRoot({ backend })` factory.
|
|
105
|
+
Each subsystem generates a protocol (interface), Drizzle backend (Postgres), memory backend (tests), and a NestJS module with `forRoot({ backend })` factory. The `openapi-config` subsystem is config-only — the runtime helpers ship with `codegen project init`.
|
|
105
106
|
|
|
106
107
|
### Project Commands
|
|
107
108
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared error response schema (OPENAPI-3).
|
|
5
|
+
*
|
|
6
|
+
* Generated controllers `@ApiResponse(...)` decorators reference this
|
|
7
|
+
* schema by `$ref` (name `ErrorResponseDto`) for non-success status codes
|
|
8
|
+
* (400, 401, 404, etc.). Shape matches NestJS's default `HttpException`
|
|
9
|
+
* JSON body — see `packages/common/src/exceptions/http.exception.ts`.
|
|
10
|
+
*
|
|
11
|
+
* The registry auto-registers this schema on construction so every
|
|
12
|
+
* consumer project exposes `components.schemas.ErrorResponseDto` on
|
|
13
|
+
* `/docs-json` without per-entity duplication.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
declare const errorResponseSchema: z.ZodObject<{
|
|
17
|
+
statusCode: z.ZodNumber;
|
|
18
|
+
message: z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>;
|
|
19
|
+
error: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, "strip", z.ZodTypeAny, {
|
|
21
|
+
statusCode: number;
|
|
22
|
+
message: string | string[];
|
|
23
|
+
error?: string | undefined;
|
|
24
|
+
}, {
|
|
25
|
+
statusCode: number;
|
|
26
|
+
message: string | string[];
|
|
27
|
+
error?: string | undefined;
|
|
28
|
+
}>;
|
|
29
|
+
type ErrorResponseDto = z.infer<typeof errorResponseSchema>;
|
|
30
|
+
/** Canonical name used across `$ref` URIs in generated controllers. */
|
|
31
|
+
declare const ERROR_RESPONSE_SCHEMA_NAME = "ErrorResponseDto";
|
|
32
|
+
|
|
33
|
+
export { ERROR_RESPONSE_SCHEMA_NAME, type ErrorResponseDto, errorResponseSchema };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// runtime/shared/openapi/error-response.dto.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var errorResponseSchema = z.object({
|
|
4
|
+
statusCode: z.number().int(),
|
|
5
|
+
message: z.union([z.string(), z.array(z.string())]),
|
|
6
|
+
error: z.string().optional()
|
|
7
|
+
});
|
|
8
|
+
var ERROR_RESPONSE_SCHEMA_NAME = "ErrorResponseDto";
|
|
9
|
+
export {
|
|
10
|
+
ERROR_RESPONSE_SCHEMA_NAME,
|
|
11
|
+
errorResponseSchema
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=error-response.dto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/shared/openapi/error-response.dto.ts"],"sourcesContent":["/**\n * Shared error response schema (OPENAPI-3).\n *\n * Generated controllers `@ApiResponse(...)` decorators reference this\n * schema by `$ref` (name `ErrorResponseDto`) for non-success status codes\n * (400, 401, 404, etc.). Shape matches NestJS's default `HttpException`\n * JSON body — see `packages/common/src/exceptions/http.exception.ts`.\n *\n * The registry auto-registers this schema on construction so every\n * consumer project exposes `components.schemas.ErrorResponseDto` on\n * `/docs-json` without per-entity duplication.\n */\nimport { z } from 'zod';\n\nexport const errorResponseSchema = z.object({\n statusCode: z.number().int(),\n message: z.union([z.string(), z.array(z.string())]),\n error: z.string().optional(),\n});\n\nexport type ErrorResponseDto = z.infer<typeof errorResponseSchema>;\n\n/** Canonical name used across `$ref` URIs in generated controllers. */\nexport const ERROR_RESPONSE_SCHEMA_NAME = 'ErrorResponseDto';\n"],"mappings":";AAYA,SAAS,SAAS;AAEX,IAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,YAAY,EAAE,OAAO,EAAE,IAAI;AAAA,EAC3B,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,EAClD,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAKM,IAAM,6BAA6B;","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors for the OpenAPI registry (OPENAPI-1).
|
|
3
|
+
*
|
|
4
|
+
* Same shape as `runtime/subsystems/bridge/bridge-errors.ts` so consumers
|
|
5
|
+
* can catch them with the same exception-filter pattern used elsewhere.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Thrown by `OpenApiRegistry.build()` when `@anatine/zod-openapi` is not
|
|
9
|
+
* resolvable. The peer is declared optional (`peerDependenciesMeta`) so
|
|
10
|
+
* consumer apps that don't care about OpenAPI still boot; the cost is a
|
|
11
|
+
* deferred failure here on first `build()`.
|
|
12
|
+
*/
|
|
13
|
+
declare class OpenApiPeerDepMissingError extends Error {
|
|
14
|
+
readonly name = "OpenApiPeerDepMissingError";
|
|
15
|
+
constructor(message?: string);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Thrown by `OpenApiRegistry.registerSchema(name, ...)` when `name` is
|
|
19
|
+
* already registered. Silent overwrite would make debugging
|
|
20
|
+
* double-registration bugs (e.g. two entity pipelines both emitting a
|
|
21
|
+
* `User` DTO) painful; loud failure lets the mismatch surface at module
|
|
22
|
+
* init where the stack trace is clear.
|
|
23
|
+
*/
|
|
24
|
+
declare class DuplicateSchemaError extends Error {
|
|
25
|
+
readonly schemaName: string;
|
|
26
|
+
readonly name = "DuplicateSchemaError";
|
|
27
|
+
constructor(schemaName: string);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { DuplicateSchemaError, OpenApiPeerDepMissingError };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// runtime/shared/openapi/errors.ts
|
|
2
|
+
var OpenApiPeerDepMissingError = class extends Error {
|
|
3
|
+
name = "OpenApiPeerDepMissingError";
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(
|
|
6
|
+
message ?? "OpenApiRegistry requires @anatine/zod-openapi. Install it: bun add @anatine/zod-openapi"
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var DuplicateSchemaError = class extends Error {
|
|
11
|
+
constructor(schemaName) {
|
|
12
|
+
super(
|
|
13
|
+
`DuplicateSchemaError: schema '${schemaName}' is already registered. Each schema name must be unique within the OpenApiRegistry.`
|
|
14
|
+
);
|
|
15
|
+
this.schemaName = schemaName;
|
|
16
|
+
}
|
|
17
|
+
schemaName;
|
|
18
|
+
name = "DuplicateSchemaError";
|
|
19
|
+
};
|
|
20
|
+
export {
|
|
21
|
+
DuplicateSchemaError,
|
|
22
|
+
OpenApiPeerDepMissingError
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/shared/openapi/errors.ts"],"sourcesContent":["/**\n * Typed errors for the OpenAPI registry (OPENAPI-1).\n *\n * Same shape as `runtime/subsystems/bridge/bridge-errors.ts` so consumers\n * can catch them with the same exception-filter pattern used elsewhere.\n */\n\n/**\n * Thrown by `OpenApiRegistry.build()` when `@anatine/zod-openapi` is not\n * resolvable. The peer is declared optional (`peerDependenciesMeta`) so\n * consumer apps that don't care about OpenAPI still boot; the cost is a\n * deferred failure here on first `build()`.\n */\nexport class OpenApiPeerDepMissingError extends Error {\n override readonly name = 'OpenApiPeerDepMissingError';\n constructor(message?: string) {\n super(\n message ??\n 'OpenApiRegistry requires @anatine/zod-openapi. Install it: bun add @anatine/zod-openapi',\n );\n }\n}\n\n/**\n * Thrown by `OpenApiRegistry.registerSchema(name, ...)` when `name` is\n * already registered. Silent overwrite would make debugging\n * double-registration bugs (e.g. two entity pipelines both emitting a\n * `User` DTO) painful; loud failure lets the mismatch surface at module\n * init where the stack trace is clear.\n */\nexport class DuplicateSchemaError extends Error {\n override readonly name = 'DuplicateSchemaError';\n constructor(public readonly schemaName: string) {\n super(\n `DuplicateSchemaError: schema '${schemaName}' is already registered. ` +\n `Each schema name must be unique within the OpenApiRegistry.`,\n );\n }\n}\n"],"mappings":";AAaO,IAAM,6BAAN,cAAyC,MAAM;AAAA,EAClC,OAAO;AAAA,EACzB,YAAY,SAAkB;AAC5B;AAAA,MACE,WACE;AAAA,IACJ;AAAA,EACF;AACF;AASO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAE9C,YAA4B,YAAoB;AAC9C;AAAA,MACE,iCAAiC,UAAU;AAAA,IAE7C;AAJ0B;AAAA,EAK5B;AAAA,EAL4B;AAAA,EADV,OAAO;AAO3B;","names":[]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { HttpMethod, OpenAPIInfo, OpenAPIObject, OpenApiRegistry, PathSpec } from './registry.js';
|
|
2
|
+
export { OPENAPI_REGISTRY } from './registry.tokens.js';
|
|
3
|
+
export { DuplicateSchemaError, OpenApiPeerDepMissingError } from './errors.js';
|
|
4
|
+
export { ERROR_RESPONSE_SCHEMA_NAME, ErrorResponseDto, errorResponseSchema } from './error-response.dto.js';
|
|
5
|
+
import 'zod';
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// runtime/shared/openapi/error-response.dto.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var errorResponseSchema = z.object({
|
|
4
|
+
statusCode: z.number().int(),
|
|
5
|
+
message: z.union([z.string(), z.array(z.string())]),
|
|
6
|
+
error: z.string().optional()
|
|
7
|
+
});
|
|
8
|
+
var ERROR_RESPONSE_SCHEMA_NAME = "ErrorResponseDto";
|
|
9
|
+
|
|
10
|
+
// runtime/shared/openapi/errors.ts
|
|
11
|
+
var OpenApiPeerDepMissingError = class extends Error {
|
|
12
|
+
name = "OpenApiPeerDepMissingError";
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(
|
|
15
|
+
message ?? "OpenApiRegistry requires @anatine/zod-openapi. Install it: bun add @anatine/zod-openapi"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var DuplicateSchemaError = class extends Error {
|
|
20
|
+
constructor(schemaName) {
|
|
21
|
+
super(
|
|
22
|
+
`DuplicateSchemaError: schema '${schemaName}' is already registered. Each schema name must be unique within the OpenApiRegistry.`
|
|
23
|
+
);
|
|
24
|
+
this.schemaName = schemaName;
|
|
25
|
+
}
|
|
26
|
+
schemaName;
|
|
27
|
+
name = "DuplicateSchemaError";
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// runtime/shared/openapi/registry.ts
|
|
31
|
+
var OpenApiRegistry = class {
|
|
32
|
+
zodSchemas = /* @__PURE__ */ new Map();
|
|
33
|
+
pathEntries = /* @__PURE__ */ new Map();
|
|
34
|
+
peer = null;
|
|
35
|
+
constructor() {
|
|
36
|
+
this.zodSchemas.set(ERROR_RESPONSE_SCHEMA_NAME, errorResponseSchema);
|
|
37
|
+
}
|
|
38
|
+
registerSchema(name, schema) {
|
|
39
|
+
if (this.zodSchemas.has(name)) {
|
|
40
|
+
throw new DuplicateSchemaError(name);
|
|
41
|
+
}
|
|
42
|
+
this.zodSchemas.set(name, schema);
|
|
43
|
+
}
|
|
44
|
+
registerPath(path, method, spec) {
|
|
45
|
+
let methods = this.pathEntries.get(path);
|
|
46
|
+
if (!methods) {
|
|
47
|
+
methods = /* @__PURE__ */ new Map();
|
|
48
|
+
this.pathEntries.set(path, methods);
|
|
49
|
+
}
|
|
50
|
+
methods.set(method, spec);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Emit the full OpenAPI document. Lazy-imports `@anatine/zod-openapi`
|
|
54
|
+
* on first call; failure to resolve raises `OpenApiPeerDepMissingError`
|
|
55
|
+
* (matches the `CubeAnalyticsBackend.onModuleInit` precedent).
|
|
56
|
+
*
|
|
57
|
+
* OpenAPI version is pinned to `3.0.3` — Swagger UI tooling is most
|
|
58
|
+
* stable on 3.0.x (see OPENAPI-PHASE-1-PLAN §Four locked decisions).
|
|
59
|
+
*/
|
|
60
|
+
async build(info) {
|
|
61
|
+
const peer = await this.loadPeer();
|
|
62
|
+
const schemas = {};
|
|
63
|
+
for (const [name, zodSchema] of this.zodSchemas) {
|
|
64
|
+
schemas[name] = peer.generateSchema(zodSchema, false, "3.0");
|
|
65
|
+
}
|
|
66
|
+
const paths = {};
|
|
67
|
+
for (const [path, methods] of this.pathEntries) {
|
|
68
|
+
const methodMap = {};
|
|
69
|
+
for (const [method, spec] of methods) {
|
|
70
|
+
methodMap[method] = spec;
|
|
71
|
+
}
|
|
72
|
+
paths[path] = methodMap;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
openapi: "3.0.3",
|
|
76
|
+
info,
|
|
77
|
+
paths,
|
|
78
|
+
components: { schemas }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Test helper — clears registered schemas and paths, then re-seeds the
|
|
83
|
+
* core `ErrorResponseDto` entry so post-reset state matches the
|
|
84
|
+
* invariant established in the constructor.
|
|
85
|
+
*/
|
|
86
|
+
reset() {
|
|
87
|
+
this.zodSchemas.clear();
|
|
88
|
+
this.pathEntries.clear();
|
|
89
|
+
this.peer = null;
|
|
90
|
+
this.zodSchemas.set(ERROR_RESPONSE_SCHEMA_NAME, errorResponseSchema);
|
|
91
|
+
}
|
|
92
|
+
async loadPeer() {
|
|
93
|
+
if (this.peer) return this.peer;
|
|
94
|
+
try {
|
|
95
|
+
const specifier = "@anatine/zod-openapi";
|
|
96
|
+
const mod = await import(specifier);
|
|
97
|
+
this.peer = mod;
|
|
98
|
+
return mod;
|
|
99
|
+
} catch {
|
|
100
|
+
throw new OpenApiPeerDepMissingError();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// runtime/shared/openapi/registry.tokens.ts
|
|
106
|
+
var OPENAPI_REGISTRY = "OPENAPI_REGISTRY";
|
|
107
|
+
export {
|
|
108
|
+
DuplicateSchemaError,
|
|
109
|
+
ERROR_RESPONSE_SCHEMA_NAME,
|
|
110
|
+
OPENAPI_REGISTRY,
|
|
111
|
+
OpenApiPeerDepMissingError,
|
|
112
|
+
OpenApiRegistry,
|
|
113
|
+
errorResponseSchema
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/shared/openapi/error-response.dto.ts","../../../../runtime/shared/openapi/errors.ts","../../../../runtime/shared/openapi/registry.ts","../../../../runtime/shared/openapi/registry.tokens.ts"],"sourcesContent":["/**\n * Shared error response schema (OPENAPI-3).\n *\n * Generated controllers `@ApiResponse(...)` decorators reference this\n * schema by `$ref` (name `ErrorResponseDto`) for non-success status codes\n * (400, 401, 404, etc.). Shape matches NestJS's default `HttpException`\n * JSON body — see `packages/common/src/exceptions/http.exception.ts`.\n *\n * The registry auto-registers this schema on construction so every\n * consumer project exposes `components.schemas.ErrorResponseDto` on\n * `/docs-json` without per-entity duplication.\n */\nimport { z } from 'zod';\n\nexport const errorResponseSchema = z.object({\n statusCode: z.number().int(),\n message: z.union([z.string(), z.array(z.string())]),\n error: z.string().optional(),\n});\n\nexport type ErrorResponseDto = z.infer<typeof errorResponseSchema>;\n\n/** Canonical name used across `$ref` URIs in generated controllers. */\nexport const ERROR_RESPONSE_SCHEMA_NAME = 'ErrorResponseDto';\n","/**\n * Typed errors for the OpenAPI registry (OPENAPI-1).\n *\n * Same shape as `runtime/subsystems/bridge/bridge-errors.ts` so consumers\n * can catch them with the same exception-filter pattern used elsewhere.\n */\n\n/**\n * Thrown by `OpenApiRegistry.build()` when `@anatine/zod-openapi` is not\n * resolvable. The peer is declared optional (`peerDependenciesMeta`) so\n * consumer apps that don't care about OpenAPI still boot; the cost is a\n * deferred failure here on first `build()`.\n */\nexport class OpenApiPeerDepMissingError extends Error {\n override readonly name = 'OpenApiPeerDepMissingError';\n constructor(message?: string) {\n super(\n message ??\n 'OpenApiRegistry requires @anatine/zod-openapi. Install it: bun add @anatine/zod-openapi',\n );\n }\n}\n\n/**\n * Thrown by `OpenApiRegistry.registerSchema(name, ...)` when `name` is\n * already registered. Silent overwrite would make debugging\n * double-registration bugs (e.g. two entity pipelines both emitting a\n * `User` DTO) painful; loud failure lets the mismatch surface at module\n * init where the stack trace is clear.\n */\nexport class DuplicateSchemaError extends Error {\n override readonly name = 'DuplicateSchemaError';\n constructor(public readonly schemaName: string) {\n super(\n `DuplicateSchemaError: schema '${schemaName}' is already registered. ` +\n `Each schema name must be unique within the OpenApiRegistry.`,\n );\n }\n}\n","/**\n * OpenApiRegistry — collects Zod schemas and path specs, emits a\n * complete `OpenAPIObject` on `build()` (OPENAPI-1).\n *\n * Wraps `@anatine/zod-openapi` as an **optional peer dependency** using\n * the lazy-import pattern from `runtime/subsystems/analytics/cube-backend.ts`\n * — consumer apps that never call `build()` still boot even if\n * `@anatine/zod-openapi` isn't installed.\n *\n * The registry is the single source of truth consumed by OPENAPI-2\n * (generated DTOs register their Zod schemas at module init), OPENAPI-3\n * (controller decorators reference those schemas), and OPENAPI-4\n * (Swagger UI bootstrap calls `build()` once at startup).\n */\nimport type { z } from 'zod';\n\nimport { ERROR_RESPONSE_SCHEMA_NAME, errorResponseSchema } from './error-response.dto';\nimport { OpenApiPeerDepMissingError, DuplicateSchemaError } from './errors';\n\nexport type HttpMethod = 'get' | 'post' | 'patch' | 'delete' | 'put';\n\n/**\n * OpenAPI path spec. Structurally compatible with `openapi3-ts`'s\n * `OperationObject` but typed loosely here because the peer type package\n * isn't installed as a direct dep — consumers supply whatever their\n * codegen emits.\n */\nexport interface PathSpec {\n summary?: string;\n description?: string;\n operationId?: string;\n tags?: string[];\n parameters?: unknown[];\n requestBody?: unknown;\n responses?: Record<string, unknown>;\n security?: unknown[];\n [key: string]: unknown;\n}\n\nexport interface OpenAPIInfo {\n title: string;\n version: string;\n description?: string;\n}\n\n/**\n * Minimal OpenAPIObject shape. We redeclare rather than pull\n * `openapi3-ts` types through the peer — the peer's `generateSchema`\n * returns a `SchemaObject`, but the final document assembly is ours.\n */\nexport interface OpenAPIObject {\n openapi: string;\n info: OpenAPIInfo;\n paths: Record<string, Record<string, PathSpec>>;\n components: {\n schemas: Record<string, unknown>;\n };\n}\n\ninterface PeerModule {\n generateSchema: (zodRef: unknown, useOutput?: boolean, version?: '3.0' | '3.1') => unknown;\n}\n\nexport class OpenApiRegistry {\n private zodSchemas = new Map<string, z.ZodType>();\n private pathEntries = new Map<string, Map<HttpMethod, PathSpec>>();\n private peer: PeerModule | null = null;\n\n constructor() {\n // Auto-register the shared error response schema so controllers that\n // reference `#/components/schemas/ErrorResponseDto` always resolve\n // (OPENAPI-3). Consumers can `reset()` + re-register in tests.\n this.zodSchemas.set(ERROR_RESPONSE_SCHEMA_NAME, errorResponseSchema);\n }\n\n registerSchema(name: string, schema: z.ZodType): void {\n if (this.zodSchemas.has(name)) {\n throw new DuplicateSchemaError(name);\n }\n this.zodSchemas.set(name, schema);\n }\n\n registerPath(path: string, method: HttpMethod, spec: PathSpec): void {\n let methods = this.pathEntries.get(path);\n if (!methods) {\n methods = new Map();\n this.pathEntries.set(path, methods);\n }\n methods.set(method, spec);\n }\n\n /**\n * Emit the full OpenAPI document. Lazy-imports `@anatine/zod-openapi`\n * on first call; failure to resolve raises `OpenApiPeerDepMissingError`\n * (matches the `CubeAnalyticsBackend.onModuleInit` precedent).\n *\n * OpenAPI version is pinned to `3.0.3` — Swagger UI tooling is most\n * stable on 3.0.x (see OPENAPI-PHASE-1-PLAN §Four locked decisions).\n */\n async build(info: OpenAPIInfo): Promise<OpenAPIObject> {\n const peer = await this.loadPeer();\n\n const schemas: Record<string, unknown> = {};\n for (const [name, zodSchema] of this.zodSchemas) {\n schemas[name] = peer.generateSchema(zodSchema, false, '3.0');\n }\n\n const paths: Record<string, Record<string, PathSpec>> = {};\n for (const [path, methods] of this.pathEntries) {\n const methodMap: Record<string, PathSpec> = {};\n for (const [method, spec] of methods) {\n methodMap[method] = spec;\n }\n paths[path] = methodMap;\n }\n\n return {\n openapi: '3.0.3',\n info,\n paths,\n components: { schemas },\n };\n }\n\n /**\n * Test helper — clears registered schemas and paths, then re-seeds the\n * core `ErrorResponseDto` entry so post-reset state matches the\n * invariant established in the constructor.\n */\n reset(): void {\n this.zodSchemas.clear();\n this.pathEntries.clear();\n this.peer = null;\n this.zodSchemas.set(ERROR_RESPONSE_SCHEMA_NAME, errorResponseSchema);\n }\n\n protected async loadPeer(): Promise<PeerModule> {\n if (this.peer) return this.peer;\n try {\n // Computed specifier: prevents tsc from resolving this import at\n // typecheck time. Consumers vendor this file but may not install\n // @anatine/zod-openapi (optional peer).\n const specifier: string = '@anatine/zod-openapi';\n const mod = (await import(specifier)) as PeerModule;\n this.peer = mod;\n return mod;\n } catch {\n throw new OpenApiPeerDepMissingError();\n }\n }\n}\n","/**\n * Injection token for the OpenAPI registry (OPENAPI-1).\n *\n * String constant (not a Symbol) so it matches by value across import\n * boundaries — same convention as `ANALYTICS_QUERY` in analytics and\n * `EVENT_BUS` / `BRIDGE_DELIVERY_REPO` in events / bridge. The OPENAPI-1\n * spec sketched a Symbol, but the repo-wide convention wins — codebase\n * consistency matters more than the spec's initial guess.\n *\n * Consumed by generated DTO providers (OPENAPI-2), controllers\n * (OPENAPI-3), and the Swagger bootstrap (OPENAPI-4).\n */\nexport const OPENAPI_REGISTRY = 'OPENAPI_REGISTRY' as const;\n"],"mappings":";AAYA,SAAS,SAAS;AAEX,IAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,YAAY,EAAE,OAAO,EAAE,IAAI;AAAA,EAC3B,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,EAClD,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAKM,IAAM,6BAA6B;;;ACVnC,IAAM,6BAAN,cAAyC,MAAM;AAAA,EAClC,OAAO;AAAA,EACzB,YAAY,SAAkB;AAC5B;AAAA,MACE,WACE;AAAA,IACJ;AAAA,EACF;AACF;AASO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAE9C,YAA4B,YAAoB;AAC9C;AAAA,MACE,iCAAiC,UAAU;AAAA,IAE7C;AAJ0B;AAAA,EAK5B;AAAA,EAL4B;AAAA,EADV,OAAO;AAO3B;;;ACyBO,IAAM,kBAAN,MAAsB;AAAA,EACnB,aAAa,oBAAI,IAAuB;AAAA,EACxC,cAAc,oBAAI,IAAuC;AAAA,EACzD,OAA0B;AAAA,EAElC,cAAc;AAIZ,SAAK,WAAW,IAAI,4BAA4B,mBAAmB;AAAA,EACrE;AAAA,EAEA,eAAe,MAAc,QAAyB;AACpD,QAAI,KAAK,WAAW,IAAI,IAAI,GAAG;AAC7B,YAAM,IAAI,qBAAqB,IAAI;AAAA,IACrC;AACA,SAAK,WAAW,IAAI,MAAM,MAAM;AAAA,EAClC;AAAA,EAEA,aAAa,MAAc,QAAoB,MAAsB;AACnE,QAAI,UAAU,KAAK,YAAY,IAAI,IAAI;AACvC,QAAI,CAAC,SAAS;AACZ,gBAAU,oBAAI,IAAI;AAClB,WAAK,YAAY,IAAI,MAAM,OAAO;AAAA,IACpC;AACA,YAAQ,IAAI,QAAQ,IAAI;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,MAAM,MAA2C;AACrD,UAAM,OAAO,MAAM,KAAK,SAAS;AAEjC,UAAM,UAAmC,CAAC;AAC1C,eAAW,CAAC,MAAM,SAAS,KAAK,KAAK,YAAY;AAC/C,cAAQ,IAAI,IAAI,KAAK,eAAe,WAAW,OAAO,KAAK;AAAA,IAC7D;AAEA,UAAM,QAAkD,CAAC;AACzD,eAAW,CAAC,MAAM,OAAO,KAAK,KAAK,aAAa;AAC9C,YAAM,YAAsC,CAAC;AAC7C,iBAAW,CAAC,QAAQ,IAAI,KAAK,SAAS;AACpC,kBAAU,MAAM,IAAI;AAAA,MACtB;AACA,YAAM,IAAI,IAAI;AAAA,IAChB;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA,YAAY,EAAE,QAAQ;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,SAAK,WAAW,MAAM;AACtB,SAAK,YAAY,MAAM;AACvB,SAAK,OAAO;AACZ,SAAK,WAAW,IAAI,4BAA4B,mBAAmB;AAAA,EACrE;AAAA,EAEA,MAAgB,WAAgC;AAC9C,QAAI,KAAK,KAAM,QAAO,KAAK;AAC3B,QAAI;AAIF,YAAM,YAAoB;AAC1B,YAAM,MAAO,MAAM,OAAO;AAC1B,WAAK,OAAO;AACZ,aAAO;AAAA,IACT,QAAQ;AACN,YAAM,IAAI,2BAA2B;AAAA,IACvC;AAAA,EACF;AACF;;;AC1IO,IAAM,mBAAmB;","names":[]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OpenApiRegistry — collects Zod schemas and path specs, emits a
|
|
5
|
+
* complete `OpenAPIObject` on `build()` (OPENAPI-1).
|
|
6
|
+
*
|
|
7
|
+
* Wraps `@anatine/zod-openapi` as an **optional peer dependency** using
|
|
8
|
+
* the lazy-import pattern from `runtime/subsystems/analytics/cube-backend.ts`
|
|
9
|
+
* — consumer apps that never call `build()` still boot even if
|
|
10
|
+
* `@anatine/zod-openapi` isn't installed.
|
|
11
|
+
*
|
|
12
|
+
* The registry is the single source of truth consumed by OPENAPI-2
|
|
13
|
+
* (generated DTOs register their Zod schemas at module init), OPENAPI-3
|
|
14
|
+
* (controller decorators reference those schemas), and OPENAPI-4
|
|
15
|
+
* (Swagger UI bootstrap calls `build()` once at startup).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
type HttpMethod = 'get' | 'post' | 'patch' | 'delete' | 'put';
|
|
19
|
+
/**
|
|
20
|
+
* OpenAPI path spec. Structurally compatible with `openapi3-ts`'s
|
|
21
|
+
* `OperationObject` but typed loosely here because the peer type package
|
|
22
|
+
* isn't installed as a direct dep — consumers supply whatever their
|
|
23
|
+
* codegen emits.
|
|
24
|
+
*/
|
|
25
|
+
interface PathSpec {
|
|
26
|
+
summary?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
operationId?: string;
|
|
29
|
+
tags?: string[];
|
|
30
|
+
parameters?: unknown[];
|
|
31
|
+
requestBody?: unknown;
|
|
32
|
+
responses?: Record<string, unknown>;
|
|
33
|
+
security?: unknown[];
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
interface OpenAPIInfo {
|
|
37
|
+
title: string;
|
|
38
|
+
version: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Minimal OpenAPIObject shape. We redeclare rather than pull
|
|
43
|
+
* `openapi3-ts` types through the peer — the peer's `generateSchema`
|
|
44
|
+
* returns a `SchemaObject`, but the final document assembly is ours.
|
|
45
|
+
*/
|
|
46
|
+
interface OpenAPIObject {
|
|
47
|
+
openapi: string;
|
|
48
|
+
info: OpenAPIInfo;
|
|
49
|
+
paths: Record<string, Record<string, PathSpec>>;
|
|
50
|
+
components: {
|
|
51
|
+
schemas: Record<string, unknown>;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
interface PeerModule {
|
|
55
|
+
generateSchema: (zodRef: unknown, useOutput?: boolean, version?: '3.0' | '3.1') => unknown;
|
|
56
|
+
}
|
|
57
|
+
declare class OpenApiRegistry {
|
|
58
|
+
private zodSchemas;
|
|
59
|
+
private pathEntries;
|
|
60
|
+
private peer;
|
|
61
|
+
constructor();
|
|
62
|
+
registerSchema(name: string, schema: z.ZodType): void;
|
|
63
|
+
registerPath(path: string, method: HttpMethod, spec: PathSpec): void;
|
|
64
|
+
/**
|
|
65
|
+
* Emit the full OpenAPI document. Lazy-imports `@anatine/zod-openapi`
|
|
66
|
+
* on first call; failure to resolve raises `OpenApiPeerDepMissingError`
|
|
67
|
+
* (matches the `CubeAnalyticsBackend.onModuleInit` precedent).
|
|
68
|
+
*
|
|
69
|
+
* OpenAPI version is pinned to `3.0.3` — Swagger UI tooling is most
|
|
70
|
+
* stable on 3.0.x (see OPENAPI-PHASE-1-PLAN §Four locked decisions).
|
|
71
|
+
*/
|
|
72
|
+
build(info: OpenAPIInfo): Promise<OpenAPIObject>;
|
|
73
|
+
/**
|
|
74
|
+
* Test helper — clears registered schemas and paths, then re-seeds the
|
|
75
|
+
* core `ErrorResponseDto` entry so post-reset state matches the
|
|
76
|
+
* invariant established in the constructor.
|
|
77
|
+
*/
|
|
78
|
+
reset(): void;
|
|
79
|
+
protected loadPeer(): Promise<PeerModule>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { type HttpMethod, type OpenAPIInfo, type OpenAPIObject, OpenApiRegistry, type PathSpec };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// runtime/shared/openapi/error-response.dto.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var errorResponseSchema = z.object({
|
|
4
|
+
statusCode: z.number().int(),
|
|
5
|
+
message: z.union([z.string(), z.array(z.string())]),
|
|
6
|
+
error: z.string().optional()
|
|
7
|
+
});
|
|
8
|
+
var ERROR_RESPONSE_SCHEMA_NAME = "ErrorResponseDto";
|
|
9
|
+
|
|
10
|
+
// runtime/shared/openapi/errors.ts
|
|
11
|
+
var OpenApiPeerDepMissingError = class extends Error {
|
|
12
|
+
name = "OpenApiPeerDepMissingError";
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(
|
|
15
|
+
message ?? "OpenApiRegistry requires @anatine/zod-openapi. Install it: bun add @anatine/zod-openapi"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var DuplicateSchemaError = class extends Error {
|
|
20
|
+
constructor(schemaName) {
|
|
21
|
+
super(
|
|
22
|
+
`DuplicateSchemaError: schema '${schemaName}' is already registered. Each schema name must be unique within the OpenApiRegistry.`
|
|
23
|
+
);
|
|
24
|
+
this.schemaName = schemaName;
|
|
25
|
+
}
|
|
26
|
+
schemaName;
|
|
27
|
+
name = "DuplicateSchemaError";
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// runtime/shared/openapi/registry.ts
|
|
31
|
+
var OpenApiRegistry = class {
|
|
32
|
+
zodSchemas = /* @__PURE__ */ new Map();
|
|
33
|
+
pathEntries = /* @__PURE__ */ new Map();
|
|
34
|
+
peer = null;
|
|
35
|
+
constructor() {
|
|
36
|
+
this.zodSchemas.set(ERROR_RESPONSE_SCHEMA_NAME, errorResponseSchema);
|
|
37
|
+
}
|
|
38
|
+
registerSchema(name, schema) {
|
|
39
|
+
if (this.zodSchemas.has(name)) {
|
|
40
|
+
throw new DuplicateSchemaError(name);
|
|
41
|
+
}
|
|
42
|
+
this.zodSchemas.set(name, schema);
|
|
43
|
+
}
|
|
44
|
+
registerPath(path, method, spec) {
|
|
45
|
+
let methods = this.pathEntries.get(path);
|
|
46
|
+
if (!methods) {
|
|
47
|
+
methods = /* @__PURE__ */ new Map();
|
|
48
|
+
this.pathEntries.set(path, methods);
|
|
49
|
+
}
|
|
50
|
+
methods.set(method, spec);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Emit the full OpenAPI document. Lazy-imports `@anatine/zod-openapi`
|
|
54
|
+
* on first call; failure to resolve raises `OpenApiPeerDepMissingError`
|
|
55
|
+
* (matches the `CubeAnalyticsBackend.onModuleInit` precedent).
|
|
56
|
+
*
|
|
57
|
+
* OpenAPI version is pinned to `3.0.3` — Swagger UI tooling is most
|
|
58
|
+
* stable on 3.0.x (see OPENAPI-PHASE-1-PLAN §Four locked decisions).
|
|
59
|
+
*/
|
|
60
|
+
async build(info) {
|
|
61
|
+
const peer = await this.loadPeer();
|
|
62
|
+
const schemas = {};
|
|
63
|
+
for (const [name, zodSchema] of this.zodSchemas) {
|
|
64
|
+
schemas[name] = peer.generateSchema(zodSchema, false, "3.0");
|
|
65
|
+
}
|
|
66
|
+
const paths = {};
|
|
67
|
+
for (const [path, methods] of this.pathEntries) {
|
|
68
|
+
const methodMap = {};
|
|
69
|
+
for (const [method, spec] of methods) {
|
|
70
|
+
methodMap[method] = spec;
|
|
71
|
+
}
|
|
72
|
+
paths[path] = methodMap;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
openapi: "3.0.3",
|
|
76
|
+
info,
|
|
77
|
+
paths,
|
|
78
|
+
components: { schemas }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Test helper — clears registered schemas and paths, then re-seeds the
|
|
83
|
+
* core `ErrorResponseDto` entry so post-reset state matches the
|
|
84
|
+
* invariant established in the constructor.
|
|
85
|
+
*/
|
|
86
|
+
reset() {
|
|
87
|
+
this.zodSchemas.clear();
|
|
88
|
+
this.pathEntries.clear();
|
|
89
|
+
this.peer = null;
|
|
90
|
+
this.zodSchemas.set(ERROR_RESPONSE_SCHEMA_NAME, errorResponseSchema);
|
|
91
|
+
}
|
|
92
|
+
async loadPeer() {
|
|
93
|
+
if (this.peer) return this.peer;
|
|
94
|
+
try {
|
|
95
|
+
const specifier = "@anatine/zod-openapi";
|
|
96
|
+
const mod = await import(specifier);
|
|
97
|
+
this.peer = mod;
|
|
98
|
+
return mod;
|
|
99
|
+
} catch {
|
|
100
|
+
throw new OpenApiPeerDepMissingError();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
export {
|
|
105
|
+
OpenApiRegistry
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/shared/openapi/error-response.dto.ts","../../../../runtime/shared/openapi/errors.ts","../../../../runtime/shared/openapi/registry.ts"],"sourcesContent":["/**\n * Shared error response schema (OPENAPI-3).\n *\n * Generated controllers `@ApiResponse(...)` decorators reference this\n * schema by `$ref` (name `ErrorResponseDto`) for non-success status codes\n * (400, 401, 404, etc.). Shape matches NestJS's default `HttpException`\n * JSON body — see `packages/common/src/exceptions/http.exception.ts`.\n *\n * The registry auto-registers this schema on construction so every\n * consumer project exposes `components.schemas.ErrorResponseDto` on\n * `/docs-json` without per-entity duplication.\n */\nimport { z } from 'zod';\n\nexport const errorResponseSchema = z.object({\n statusCode: z.number().int(),\n message: z.union([z.string(), z.array(z.string())]),\n error: z.string().optional(),\n});\n\nexport type ErrorResponseDto = z.infer<typeof errorResponseSchema>;\n\n/** Canonical name used across `$ref` URIs in generated controllers. */\nexport const ERROR_RESPONSE_SCHEMA_NAME = 'ErrorResponseDto';\n","/**\n * Typed errors for the OpenAPI registry (OPENAPI-1).\n *\n * Same shape as `runtime/subsystems/bridge/bridge-errors.ts` so consumers\n * can catch them with the same exception-filter pattern used elsewhere.\n */\n\n/**\n * Thrown by `OpenApiRegistry.build()` when `@anatine/zod-openapi` is not\n * resolvable. The peer is declared optional (`peerDependenciesMeta`) so\n * consumer apps that don't care about OpenAPI still boot; the cost is a\n * deferred failure here on first `build()`.\n */\nexport class OpenApiPeerDepMissingError extends Error {\n override readonly name = 'OpenApiPeerDepMissingError';\n constructor(message?: string) {\n super(\n message ??\n 'OpenApiRegistry requires @anatine/zod-openapi. Install it: bun add @anatine/zod-openapi',\n );\n }\n}\n\n/**\n * Thrown by `OpenApiRegistry.registerSchema(name, ...)` when `name` is\n * already registered. Silent overwrite would make debugging\n * double-registration bugs (e.g. two entity pipelines both emitting a\n * `User` DTO) painful; loud failure lets the mismatch surface at module\n * init where the stack trace is clear.\n */\nexport class DuplicateSchemaError extends Error {\n override readonly name = 'DuplicateSchemaError';\n constructor(public readonly schemaName: string) {\n super(\n `DuplicateSchemaError: schema '${schemaName}' is already registered. ` +\n `Each schema name must be unique within the OpenApiRegistry.`,\n );\n }\n}\n","/**\n * OpenApiRegistry — collects Zod schemas and path specs, emits a\n * complete `OpenAPIObject` on `build()` (OPENAPI-1).\n *\n * Wraps `@anatine/zod-openapi` as an **optional peer dependency** using\n * the lazy-import pattern from `runtime/subsystems/analytics/cube-backend.ts`\n * — consumer apps that never call `build()` still boot even if\n * `@anatine/zod-openapi` isn't installed.\n *\n * The registry is the single source of truth consumed by OPENAPI-2\n * (generated DTOs register their Zod schemas at module init), OPENAPI-3\n * (controller decorators reference those schemas), and OPENAPI-4\n * (Swagger UI bootstrap calls `build()` once at startup).\n */\nimport type { z } from 'zod';\n\nimport { ERROR_RESPONSE_SCHEMA_NAME, errorResponseSchema } from './error-response.dto';\nimport { OpenApiPeerDepMissingError, DuplicateSchemaError } from './errors';\n\nexport type HttpMethod = 'get' | 'post' | 'patch' | 'delete' | 'put';\n\n/**\n * OpenAPI path spec. Structurally compatible with `openapi3-ts`'s\n * `OperationObject` but typed loosely here because the peer type package\n * isn't installed as a direct dep — consumers supply whatever their\n * codegen emits.\n */\nexport interface PathSpec {\n summary?: string;\n description?: string;\n operationId?: string;\n tags?: string[];\n parameters?: unknown[];\n requestBody?: unknown;\n responses?: Record<string, unknown>;\n security?: unknown[];\n [key: string]: unknown;\n}\n\nexport interface OpenAPIInfo {\n title: string;\n version: string;\n description?: string;\n}\n\n/**\n * Minimal OpenAPIObject shape. We redeclare rather than pull\n * `openapi3-ts` types through the peer — the peer's `generateSchema`\n * returns a `SchemaObject`, but the final document assembly is ours.\n */\nexport interface OpenAPIObject {\n openapi: string;\n info: OpenAPIInfo;\n paths: Record<string, Record<string, PathSpec>>;\n components: {\n schemas: Record<string, unknown>;\n };\n}\n\ninterface PeerModule {\n generateSchema: (zodRef: unknown, useOutput?: boolean, version?: '3.0' | '3.1') => unknown;\n}\n\nexport class OpenApiRegistry {\n private zodSchemas = new Map<string, z.ZodType>();\n private pathEntries = new Map<string, Map<HttpMethod, PathSpec>>();\n private peer: PeerModule | null = null;\n\n constructor() {\n // Auto-register the shared error response schema so controllers that\n // reference `#/components/schemas/ErrorResponseDto` always resolve\n // (OPENAPI-3). Consumers can `reset()` + re-register in tests.\n this.zodSchemas.set(ERROR_RESPONSE_SCHEMA_NAME, errorResponseSchema);\n }\n\n registerSchema(name: string, schema: z.ZodType): void {\n if (this.zodSchemas.has(name)) {\n throw new DuplicateSchemaError(name);\n }\n this.zodSchemas.set(name, schema);\n }\n\n registerPath(path: string, method: HttpMethod, spec: PathSpec): void {\n let methods = this.pathEntries.get(path);\n if (!methods) {\n methods = new Map();\n this.pathEntries.set(path, methods);\n }\n methods.set(method, spec);\n }\n\n /**\n * Emit the full OpenAPI document. Lazy-imports `@anatine/zod-openapi`\n * on first call; failure to resolve raises `OpenApiPeerDepMissingError`\n * (matches the `CubeAnalyticsBackend.onModuleInit` precedent).\n *\n * OpenAPI version is pinned to `3.0.3` — Swagger UI tooling is most\n * stable on 3.0.x (see OPENAPI-PHASE-1-PLAN §Four locked decisions).\n */\n async build(info: OpenAPIInfo): Promise<OpenAPIObject> {\n const peer = await this.loadPeer();\n\n const schemas: Record<string, unknown> = {};\n for (const [name, zodSchema] of this.zodSchemas) {\n schemas[name] = peer.generateSchema(zodSchema, false, '3.0');\n }\n\n const paths: Record<string, Record<string, PathSpec>> = {};\n for (const [path, methods] of this.pathEntries) {\n const methodMap: Record<string, PathSpec> = {};\n for (const [method, spec] of methods) {\n methodMap[method] = spec;\n }\n paths[path] = methodMap;\n }\n\n return {\n openapi: '3.0.3',\n info,\n paths,\n components: { schemas },\n };\n }\n\n /**\n * Test helper — clears registered schemas and paths, then re-seeds the\n * core `ErrorResponseDto` entry so post-reset state matches the\n * invariant established in the constructor.\n */\n reset(): void {\n this.zodSchemas.clear();\n this.pathEntries.clear();\n this.peer = null;\n this.zodSchemas.set(ERROR_RESPONSE_SCHEMA_NAME, errorResponseSchema);\n }\n\n protected async loadPeer(): Promise<PeerModule> {\n if (this.peer) return this.peer;\n try {\n // Computed specifier: prevents tsc from resolving this import at\n // typecheck time. Consumers vendor this file but may not install\n // @anatine/zod-openapi (optional peer).\n const specifier: string = '@anatine/zod-openapi';\n const mod = (await import(specifier)) as PeerModule;\n this.peer = mod;\n return mod;\n } catch {\n throw new OpenApiPeerDepMissingError();\n }\n }\n}\n"],"mappings":";AAYA,SAAS,SAAS;AAEX,IAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,YAAY,EAAE,OAAO,EAAE,IAAI;AAAA,EAC3B,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,EAClD,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAKM,IAAM,6BAA6B;;;ACVnC,IAAM,6BAAN,cAAyC,MAAM;AAAA,EAClC,OAAO;AAAA,EACzB,YAAY,SAAkB;AAC5B;AAAA,MACE,WACE;AAAA,IACJ;AAAA,EACF;AACF;AASO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAE9C,YAA4B,YAAoB;AAC9C;AAAA,MACE,iCAAiC,UAAU;AAAA,IAE7C;AAJ0B;AAAA,EAK5B;AAAA,EAL4B;AAAA,EADV,OAAO;AAO3B;;;ACyBO,IAAM,kBAAN,MAAsB;AAAA,EACnB,aAAa,oBAAI,IAAuB;AAAA,EACxC,cAAc,oBAAI,IAAuC;AAAA,EACzD,OAA0B;AAAA,EAElC,cAAc;AAIZ,SAAK,WAAW,IAAI,4BAA4B,mBAAmB;AAAA,EACrE;AAAA,EAEA,eAAe,MAAc,QAAyB;AACpD,QAAI,KAAK,WAAW,IAAI,IAAI,GAAG;AAC7B,YAAM,IAAI,qBAAqB,IAAI;AAAA,IACrC;AACA,SAAK,WAAW,IAAI,MAAM,MAAM;AAAA,EAClC;AAAA,EAEA,aAAa,MAAc,QAAoB,MAAsB;AACnE,QAAI,UAAU,KAAK,YAAY,IAAI,IAAI;AACvC,QAAI,CAAC,SAAS;AACZ,gBAAU,oBAAI,IAAI;AAClB,WAAK,YAAY,IAAI,MAAM,OAAO;AAAA,IACpC;AACA,YAAQ,IAAI,QAAQ,IAAI;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,MAAM,MAA2C;AACrD,UAAM,OAAO,MAAM,KAAK,SAAS;AAEjC,UAAM,UAAmC,CAAC;AAC1C,eAAW,CAAC,MAAM,SAAS,KAAK,KAAK,YAAY;AAC/C,cAAQ,IAAI,IAAI,KAAK,eAAe,WAAW,OAAO,KAAK;AAAA,IAC7D;AAEA,UAAM,QAAkD,CAAC;AACzD,eAAW,CAAC,MAAM,OAAO,KAAK,KAAK,aAAa;AAC9C,YAAM,YAAsC,CAAC;AAC7C,iBAAW,CAAC,QAAQ,IAAI,KAAK,SAAS;AACpC,kBAAU,MAAM,IAAI;AAAA,MACtB;AACA,YAAM,IAAI,IAAI;AAAA,IAChB;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA,YAAY,EAAE,QAAQ;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,SAAK,WAAW,MAAM;AACtB,SAAK,YAAY,MAAM;AACvB,SAAK,OAAO;AACZ,SAAK,WAAW,IAAI,4BAA4B,mBAAmB;AAAA,EACrE;AAAA,EAEA,MAAgB,WAAgC;AAC9C,QAAI,KAAK,KAAM,QAAO,KAAK;AAC3B,QAAI;AAIF,YAAM,YAAoB;AAC1B,YAAM,MAAO,MAAM,OAAO;AAC1B,WAAK,OAAO;AACZ,aAAO;AAAA,IACT,QAAQ;AACN,YAAM,IAAI,2BAA2B;AAAA,IACvC;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injection token for the OpenAPI registry (OPENAPI-1).
|
|
3
|
+
*
|
|
4
|
+
* String constant (not a Symbol) so it matches by value across import
|
|
5
|
+
* boundaries — same convention as `ANALYTICS_QUERY` in analytics and
|
|
6
|
+
* `EVENT_BUS` / `BRIDGE_DELIVERY_REPO` in events / bridge. The OPENAPI-1
|
|
7
|
+
* spec sketched a Symbol, but the repo-wide convention wins — codebase
|
|
8
|
+
* consistency matters more than the spec's initial guess.
|
|
9
|
+
*
|
|
10
|
+
* Consumed by generated DTO providers (OPENAPI-2), controllers
|
|
11
|
+
* (OPENAPI-3), and the Swagger bootstrap (OPENAPI-4).
|
|
12
|
+
*/
|
|
13
|
+
declare const OPENAPI_REGISTRY: "OPENAPI_REGISTRY";
|
|
14
|
+
|
|
15
|
+
export { OPENAPI_REGISTRY };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/shared/openapi/registry.tokens.ts"],"sourcesContent":["/**\n * Injection token for the OpenAPI registry (OPENAPI-1).\n *\n * String constant (not a Symbol) so it matches by value across import\n * boundaries — same convention as `ANALYTICS_QUERY` in analytics and\n * `EVENT_BUS` / `BRIDGE_DELIVERY_REPO` in events / bridge. The OPENAPI-1\n * spec sketched a Symbol, but the repo-wide convention wins — codebase\n * consistency matters more than the spec's initial guess.\n *\n * Consumed by generated DTO providers (OPENAPI-2), controllers\n * (OPENAPI-3), and the Swagger bootstrap (OPENAPI-4).\n */\nexport const OPENAPI_REGISTRY = 'OPENAPI_REGISTRY' as const;\n"],"mappings":";AAYO,IAAM,mBAAmB;","names":[]}
|
|
@@ -357,7 +357,7 @@ declare const syncRuns: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
357
357
|
tableName: "sync_runs";
|
|
358
358
|
dataType: "string";
|
|
359
359
|
columnType: "PgEnumColumn";
|
|
360
|
-
data: "success" | "
|
|
360
|
+
data: "success" | "failed" | "no_changes" | "running";
|
|
361
361
|
driverParam: string;
|
|
362
362
|
notNull: true;
|
|
363
363
|
hasDefault: true;
|
|
@@ -639,7 +639,7 @@ declare const syncRunItems: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
|
639
639
|
tableName: "sync_run_items";
|
|
640
640
|
dataType: "string";
|
|
641
641
|
columnType: "PgEnumColumn";
|
|
642
|
-
data: "
|
|
642
|
+
data: "noop" | "created" | "updated" | "deleted";
|
|
643
643
|
driverParam: string;
|
|
644
644
|
notNull: true;
|
|
645
645
|
hasDefault: false;
|