@degenerate/endpoints 0.2.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +18 -0
  2. package/dist/examples/express-server/app.d.ts.map +1 -1
  3. package/dist/examples/express-server/app.js +8 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/openapi/build-openapi-document.d.ts +27 -0
  8. package/dist/openapi/build-openapi-document.d.ts.map +1 -0
  9. package/dist/openapi/build-openapi-document.js +223 -0
  10. package/dist/openapi/domain-schemas.d.ts +16 -0
  11. package/dist/openapi/domain-schemas.d.ts.map +1 -0
  12. package/dist/openapi/domain-schemas.js +39 -0
  13. package/dist/openapi/index.d.ts +6 -0
  14. package/dist/openapi/index.d.ts.map +1 -0
  15. package/dist/openapi/index.js +21 -0
  16. package/dist/openapi/operations.d.ts +8 -0
  17. package/dist/openapi/operations.d.ts.map +1 -0
  18. package/dist/openapi/operations.js +22 -0
  19. package/dist/openapi/response-schemas.d.ts +32 -0
  20. package/dist/openapi/response-schemas.d.ts.map +1 -0
  21. package/dist/openapi/response-schemas.js +30 -0
  22. package/dist/openapi/types.d.ts +29 -0
  23. package/dist/openapi/types.d.ts.map +1 -0
  24. package/dist/openapi/types.js +2 -0
  25. package/dist/routes/authorities/v1.openapi.d.ts +4 -0
  26. package/dist/routes/authorities/v1.openapi.d.ts.map +1 -0
  27. package/dist/routes/authorities/v1.openapi.js +68 -0
  28. package/dist/routes/events/v1.openapi.d.ts +4 -0
  29. package/dist/routes/events/v1.openapi.d.ts.map +1 -0
  30. package/dist/routes/events/v1.openapi.js +54 -0
  31. package/dist/routes/events.routes.d.ts.map +1 -1
  32. package/dist/routes/events.routes.js +10 -60
  33. package/dist/routes/functions/v1.openapi.d.ts +4 -0
  34. package/dist/routes/functions/v1.openapi.d.ts.map +1 -0
  35. package/dist/routes/functions/v1.openapi.js +68 -0
  36. package/dist/routes/functions.routes.d.ts.map +1 -1
  37. package/dist/routes/functions.routes.js +10 -60
  38. package/dist/routes/rules/v1/rule-set.openapi.d.ts +4 -0
  39. package/dist/routes/rules/v1/rule-set.openapi.d.ts.map +1 -0
  40. package/dist/routes/rules/v1/rule-set.openapi.js +72 -0
  41. package/dist/routes/sharedata/v1.openapi.d.ts +8 -0
  42. package/dist/routes/sharedata/v1.openapi.d.ts.map +1 -0
  43. package/dist/routes/sharedata/v1.openapi.js +60 -0
  44. package/dist/routes/sharedata/v1.routes.d.ts.map +1 -1
  45. package/dist/routes/sharedata/v1.routes.js +27 -1
  46. package/dist/routes/validation/rules/validate-rules.openapi.d.ts +8 -0
  47. package/dist/routes/validation/rules/validate-rules.openapi.d.ts.map +1 -0
  48. package/dist/routes/validation/rules/validate-rules.openapi.js +76 -0
  49. package/package.json +6 -6
package/README.md CHANGED
@@ -29,3 +29,21 @@ npm install @degenerate/endpoints
29
29
  ts-node ./packages/endpoints/src/examples/express-server/server.ts
30
30
  ```
31
31
 
32
+ ## OpenAPI
33
+
34
+ The example server exposes a machine-readable description of its HTTP surface at:
35
+
36
+ ```
37
+ GET /openapi.json
38
+ ```
39
+
40
+ This route is public (no JWT) so codegen, Swagger UI, and contract-testing tools can fetch it freely. The document is an OpenAPI 3.0 spec generated by `buildOpenApiDocument()` from the same Zod schemas the routes validate against — request and response shapes never drift from what the handlers actually accept. To build the document outside the server (e.g. to write it to a file):
41
+
42
+ ```ts
43
+ import { buildOpenApiDocument } from '@degenerate/endpoints';
44
+
45
+ const spec = buildOpenApiDocument();
46
+ ```
47
+
48
+ The operation surface is described by the `OperationDescriptor` arrays under `src/routes/**/**.openapi.ts`; a coverage test asserts the descriptors and the mounted Express routes stay in lockstep.
49
+
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/examples/express-server/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAWzD,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,OAAO,CAAC;IACb,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,IAAI,eAAe,CAqB3C;;AAKD,wBAAmC"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/examples/express-server/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,EAAE,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAYzD,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,OAAO,CAAC;IACb,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,IAAI,eAAe,CA6B3C;;AAKD,wBAAmC"}
@@ -15,6 +15,7 @@ const rules_routes_1 = require("../../routes/rules.routes");
15
15
  const share_data_routes_1 = require("../../routes/share-data.routes");
16
16
  const validation_routes_1 = require("../../routes/validation.routes");
17
17
  const cors_1 = require("../../cors");
18
+ const openapi_1 = require("../../openapi");
18
19
  /**
19
20
  * Builds a fresh Express app with all SAFE routes wired in. Returns the app
20
21
  * along with the shared `ShareDataManager` instance — handed back so callers
@@ -31,6 +32,13 @@ function createApp() {
31
32
  const shareDataManager = new sharedata_1.ShareDataManager();
32
33
  app.set('authorizedAuthorities', authorizedAuthorities);
33
34
  app.set('shareDataManager', shareDataManager);
35
+ // Public, unauthenticated machine-readable API description. Generated once
36
+ // from the same Zod schemas the routes validate against; no auth so tooling
37
+ // (codegen, Swagger UI, contract tests) can fetch it freely.
38
+ const openApiDocument = (0, openapi_1.buildOpenApiDocument)();
39
+ app.get('/openapi.json', (_req, res) => {
40
+ res.status(200).json(openApiDocument);
41
+ });
34
42
  app.use('/authorities', authorities_routes_1.authorities);
35
43
  app.use('/events', events_routes_1.events);
36
44
  app.use('/functions', functions_routes_1.functions);
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './cors';
2
+ export * from './openapi';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  // This is the main file for the package
18
18
  __exportStar(require("./cors"), exports);
19
+ __exportStar(require("./openapi"), exports);
@@ -0,0 +1,27 @@
1
+ import type { OperationDescriptor } from './types';
2
+ export interface OpenApiInfo {
3
+ title: string;
4
+ version: string;
5
+ description?: string;
6
+ }
7
+ /**
8
+ * Minimal structural shape of the emitted document. Component and path values
9
+ * are JSON Schema / OpenAPI fragments produced by Zod, so they are typed
10
+ * loosely rather than re-modeling the whole OpenAPI 3.0 type tree.
11
+ */
12
+ export interface OpenApiDocument {
13
+ openapi: string;
14
+ info: OpenApiInfo;
15
+ components: {
16
+ securitySchemes: Record<string, unknown>;
17
+ schemas: Record<string, unknown>;
18
+ };
19
+ paths: Record<string, Record<string, unknown>>;
20
+ }
21
+ /**
22
+ * Build an OpenAPI 3.0 document from the operation descriptors. Pure function:
23
+ * same input, same output, no side effects — so it can be computed once at
24
+ * server start and served as static JSON.
25
+ */
26
+ export declare function buildOpenApiDocument(operations?: OperationDescriptor[], info?: OpenApiInfo): OpenApiDocument;
27
+ //# sourceMappingURL=build-openapi-document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-openapi-document.d.ts","sourceRoot":"","sources":["../../src/openapi/build-openapi-document.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAOnD,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,EAAE;QACR,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC;IACF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD;AAqOD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAChC,UAAU,GAAE,mBAAmB,EAAkB,EACjD,IAAI,GAAE,WAA0B,GACjC,eAAe,CAuBjB"}
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildOpenApiDocument = buildOpenApiDocument;
4
+ const zod_1 = require("zod");
5
+ const operations_1 = require("./operations");
6
+ const domain_schemas_1 = require("./domain-schemas");
7
+ /** Stable component names for the raw `@degenerate/generic` domain schemas. */
8
+ const domainNames = new Map(domain_schemas_1.domainComponentNames);
9
+ const DEFAULT_INFO = {
10
+ title: 'SAFE Endpoints API',
11
+ version: '1.0.0',
12
+ description: 'HTTP surface for the SAFE platform. Request and response schemas are ' +
13
+ 'generated from the same Zod schemas the routes validate against.',
14
+ };
15
+ const STATUS_DESCRIPTIONS = {
16
+ 200: 'Success',
17
+ 400: 'Validation error',
18
+ 403: 'Unauthorized',
19
+ 404: 'Not found',
20
+ 500: 'Internal server error',
21
+ 501: 'Not implemented',
22
+ };
23
+ const statusDescription = (code) => STATUS_DESCRIPTIONS[code] ?? `Status ${code}`;
24
+ /** Names of `{param}` placeholders in an OpenAPI path template, in order. */
25
+ const pathParamNames = (path) => {
26
+ const names = [];
27
+ const matcher = /\{([^}]+)\}/g;
28
+ let match;
29
+ while ((match = matcher.exec(path)) !== null) {
30
+ names.push(match[1]);
31
+ }
32
+ return names;
33
+ };
34
+ const componentRef = (name) => ({
35
+ $ref: `#/components/schemas/${name}`,
36
+ });
37
+ /**
38
+ * Prefix of the `$ref`s Zod emits into its internal `__shared` defs bucket when
39
+ * a non-registered sub-schema is reused across two or more registered roots.
40
+ */
41
+ const SHARED_REF_PREFIX = '#/components/schemas/__shared#/definitions/';
42
+ /**
43
+ * Inline any `__shared` `$ref` with the actual sub-schema it points at, leaving
44
+ * every real `#/components/schemas/<Name>` `$ref` untouched. Returns a fresh
45
+ * deep copy — never mutates the input.
46
+ *
47
+ * Why this is needed: `IShareRuleAppliedSchema` is `IShareRuleSchema.extend(...)`,
48
+ * so both share the very same `z.array(IConditionSchema)` instance for
49
+ * `currentDataConditions` / `proposedDataConditions`. When several rule schemas
50
+ * are registered together, Zod hoists that shared array into a `__shared`
51
+ * defs entry and points to it with a `__shared#/definitions/schemaN` `$ref`,
52
+ * which is not a resolvable OpenAPI component. Inlining the (small, acyclic)
53
+ * shared sub-schema yields a clean spec; the genuinely recursive schemas
54
+ * (`ShareRule` ↔ `Condition`) stay as proper component `$ref`s because they are
55
+ * registered.
56
+ */
57
+ const inlineSharedRefs = (value, defs, active) => {
58
+ if (Array.isArray(value)) {
59
+ return value.map((item) => inlineSharedRefs(item, defs, active));
60
+ }
61
+ if (value && typeof value === 'object') {
62
+ const obj = value;
63
+ const ref = obj.$ref;
64
+ if (typeof ref === 'string' && ref.startsWith(SHARED_REF_PREFIX)) {
65
+ const defName = ref.slice(SHARED_REF_PREFIX.length);
66
+ if (active.has(defName)) {
67
+ throw new Error(`Cyclic __shared definition while inlining: ${defName}`);
68
+ }
69
+ if (!(defName in defs)) {
70
+ throw new Error(`Unresolved __shared definition: ${defName}`);
71
+ }
72
+ active.add(defName);
73
+ const resolved = inlineSharedRefs(defs[defName], defs, active);
74
+ active.delete(defName);
75
+ return resolved;
76
+ }
77
+ const out = {};
78
+ for (const [key, val] of Object.entries(obj)) {
79
+ out[key] = inlineSharedRefs(val, defs, active);
80
+ }
81
+ return out;
82
+ }
83
+ return value;
84
+ };
85
+ /** Collect distinct schema instances (request + every response) in encounter order. */
86
+ const collectSchemas = (operations) => {
87
+ const seen = new Set();
88
+ const distinct = [];
89
+ const add = (schema) => {
90
+ if (schema && !seen.has(schema)) {
91
+ seen.add(schema);
92
+ distinct.push(schema);
93
+ }
94
+ };
95
+ for (const op of operations) {
96
+ add(op.request);
97
+ for (const schema of Object.values(op.response)) {
98
+ add(schema);
99
+ }
100
+ }
101
+ return distinct;
102
+ };
103
+ /**
104
+ * Register each distinct schema under a stable component name, convert the
105
+ * whole registry to OpenAPI-3.0 JSON Schema in one pass, and return both the
106
+ * `components.schemas` map and the schema→name lookup used to build `$ref`s.
107
+ *
108
+ * One pass with `io: 'output'` is correct for both request and response
109
+ * positions here because no SAFE schema uses a transform / default / coercion —
110
+ * so a schema's input and output JSON Schema are identical. Conversion via the
111
+ * registry overload makes a schema reused across operations (or nested inside a
112
+ * response envelope) emit as a single `$ref`'d component automatically.
113
+ */
114
+ const buildComponents = (distinct) => {
115
+ const registry = zod_1.z.registry();
116
+ const nameBySchema = new Map();
117
+ const usedNames = new Set();
118
+ let autoCounter = 0;
119
+ for (const schema of distinct) {
120
+ const metaId = schema.meta()?.id ?? domainNames.get(schema);
121
+ let name = metaId ?? `Component${++autoCounter}`;
122
+ // Defensive: distinct schemas must map to distinct component names.
123
+ while (usedNames.has(name)) {
124
+ name = `${name}_${++autoCounter}`;
125
+ }
126
+ usedNames.add(name);
127
+ nameBySchema.set(schema, name);
128
+ registry.add(schema, { id: name });
129
+ }
130
+ const { schemas } = zod_1.z.toJSONSchema(registry, {
131
+ target: 'openapi-3.0',
132
+ io: 'output',
133
+ unrepresentable: 'any',
134
+ uri: (id) => `#/components/schemas/${id}`,
135
+ });
136
+ // Zod parks sub-schemas reused across registered roots in a `__shared`
137
+ // bucket; inline those back into the components and drop the bucket so every
138
+ // remaining `$ref` resolves to a real component. See `inlineSharedRefs`.
139
+ const sharedDefs = schemas.__shared?.definitions ?? {};
140
+ // Strip the non-OpenAPI `$id` Zod stamps on each component; `$ref`s between
141
+ // components are unaffected.
142
+ const cleaned = {};
143
+ for (const [name, schema] of Object.entries(schemas)) {
144
+ if (name === '__shared') {
145
+ continue;
146
+ }
147
+ const inlined = inlineSharedRefs(schema, sharedDefs, new Set());
148
+ const { $id, ...rest } = inlined;
149
+ void $id;
150
+ cleaned[name] = rest;
151
+ }
152
+ return { schemas: cleaned, nameBySchema };
153
+ };
154
+ const buildPaths = (operations, nameBySchema) => {
155
+ const paths = {};
156
+ for (const op of operations) {
157
+ const pathItem = paths[op.path] ?? (paths[op.path] = {});
158
+ const responses = {};
159
+ for (const [status, schema] of Object.entries(op.response)) {
160
+ responses[status] = {
161
+ description: statusDescription(Number(status)),
162
+ content: { 'application/json': { schema: componentRef(nameBySchema.get(schema)) } },
163
+ };
164
+ }
165
+ const operationObject = {
166
+ summary: op.summary,
167
+ description: op.security === 'public'
168
+ ? 'Public — no authentication required.'
169
+ : `Requires a valid JWT for the ${op.security} audience.`,
170
+ security: op.security === 'public' ? [] : [{ bearerAuth: [] }],
171
+ responses,
172
+ };
173
+ if (op.deprecated) {
174
+ operationObject.deprecated = true;
175
+ }
176
+ const params = pathParamNames(op.path);
177
+ if (params.length > 0) {
178
+ operationObject.parameters = params.map((name) => ({
179
+ name,
180
+ in: 'path',
181
+ required: true,
182
+ schema: { type: 'string' },
183
+ }));
184
+ }
185
+ if (op.request) {
186
+ operationObject.requestBody = {
187
+ required: true,
188
+ content: {
189
+ 'application/json': { schema: componentRef(nameBySchema.get(op.request)) },
190
+ },
191
+ };
192
+ }
193
+ pathItem[op.method] = operationObject;
194
+ }
195
+ return paths;
196
+ };
197
+ /**
198
+ * Build an OpenAPI 3.0 document from the operation descriptors. Pure function:
199
+ * same input, same output, no side effects — so it can be computed once at
200
+ * server start and served as static JSON.
201
+ */
202
+ function buildOpenApiDocument(operations = operations_1.operations, info = DEFAULT_INFO) {
203
+ const { schemas, nameBySchema } = buildComponents(collectSchemas(operations));
204
+ const paths = buildPaths(operations, nameBySchema);
205
+ return {
206
+ openapi: '3.0.3',
207
+ info,
208
+ components: {
209
+ securitySchemes: {
210
+ bearerAuth: {
211
+ type: 'http',
212
+ scheme: 'bearer',
213
+ bearerFormat: 'JWT',
214
+ description: 'JWT bearer token. The token audience (aud) selects which route group it ' +
215
+ 'authorizes: AUTHORITIES, EVENTS, FUNCTIONS, RULES, or SHAREDATA. Operations ' +
216
+ 'on /share-data additionally enforce a per-resource (BOLA) check.',
217
+ },
218
+ },
219
+ schemas,
220
+ },
221
+ paths,
222
+ };
223
+ }
@@ -0,0 +1,16 @@
1
+ import type { z } from 'zod';
2
+ /**
3
+ * Stable OpenAPI component names for the centralized request/domain schemas in
4
+ * `@degenerate/generic`, keyed by the **raw** schema instance.
5
+ *
6
+ * Keyed by the raw instance (not a `.meta()` clone) on purpose: several rule
7
+ * schemas are recursive (`IShareRuleSchema` ↔ `IConditionSchema`, and
8
+ * `IShareRuleGroupSchema` via `z.lazy`), and those `z.lazy` thunks resolve to
9
+ * the raw generic instances. The generator must register the very instances
10
+ * the schema graph references, otherwise the recursive sub-schemas have no
11
+ * component id and Zod hoists them into a malformed shared-defs bucket. Every
12
+ * recursive schema below is also mounted as a `/validation` request body, so
13
+ * registering these names makes the whole graph resolve to clean `$ref`s.
14
+ */
15
+ export declare const domainComponentNames: ReadonlyArray<readonly [z.ZodType, string]>;
16
+ //# sourceMappingURL=domain-schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-schemas.d.ts","sourceRoot":"","sources":["../../src/openapi/domain-schemas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAwB7B;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAqB5E,CAAC"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.domainComponentNames = void 0;
4
+ const generic_1 = require("@degenerate/generic");
5
+ /**
6
+ * Stable OpenAPI component names for the centralized request/domain schemas in
7
+ * `@degenerate/generic`, keyed by the **raw** schema instance.
8
+ *
9
+ * Keyed by the raw instance (not a `.meta()` clone) on purpose: several rule
10
+ * schemas are recursive (`IShareRuleSchema` ↔ `IConditionSchema`, and
11
+ * `IShareRuleGroupSchema` via `z.lazy`), and those `z.lazy` thunks resolve to
12
+ * the raw generic instances. The generator must register the very instances
13
+ * the schema graph references, otherwise the recursive sub-schemas have no
14
+ * component id and Zod hoists them into a malformed shared-defs bucket. Every
15
+ * recursive schema below is also mounted as a `/validation` request body, so
16
+ * registering these names makes the whole graph resolve to clean `$ref`s.
17
+ */
18
+ exports.domainComponentNames = [
19
+ [generic_1.ISAFETransactionAuthorityAddSchema, 'SAFETransactionAuthorityAdd'],
20
+ [generic_1.ISAFETransactionAuthorityUpdateSchema, 'SAFETransactionAuthorityUpdate'],
21
+ [generic_1.ISAFETransactionAuthorityDeleteSchema, 'SAFETransactionAuthorityDelete'],
22
+ [generic_1.ISAFETransactionEventAddSchema, 'SAFETransactionEventAdd'],
23
+ [generic_1.ISAFETransactionEventUpdateSchema, 'SAFETransactionEventUpdate'],
24
+ [generic_1.ISAFETransactionEventDeleteSchema, 'SAFETransactionEventDelete'],
25
+ [generic_1.ISAFETransactionFunctionAddSchema, 'SAFETransactionFunctionAdd'],
26
+ [generic_1.ISAFETransactionFunctionUpdateSchema, 'SAFETransactionFunctionUpdate'],
27
+ [generic_1.ISAFETransactionFunctionDeleteSchema, 'SAFETransactionFunctionDelete'],
28
+ [generic_1.ISAFETransactionRuleSetAddSchema, 'SAFETransactionRuleSetAdd'],
29
+ [generic_1.ISAFETransactionRuleSetUpdateSchema, 'SAFETransactionRuleSetUpdate'],
30
+ [generic_1.ISAFETransactionRuleSetDeleteSchema, 'SAFETransactionRuleSetDelete'],
31
+ [generic_1.ISAFETransactionShareDataAddSchema, 'SAFETransactionShareDataAdd'],
32
+ [generic_1.ISAFETransactionShareDataUpdateSchema, 'SAFETransactionShareDataUpdate'],
33
+ [generic_1.ISAFETransactionShareDataDeleteSchema, 'SAFETransactionShareDataDelete'],
34
+ [generic_1.IShareRuleSetSchema, 'ShareRuleSet'],
35
+ [generic_1.IShareRuleGroupSchema, 'ShareRuleGroup'],
36
+ [generic_1.IShareRuleAppliedSchema, 'ShareRuleApplied'],
37
+ [generic_1.IShareRuleSchema, 'ShareRule'],
38
+ [generic_1.IConditionSchema, 'Condition'],
39
+ ];
@@ -0,0 +1,6 @@
1
+ export * from './types';
2
+ export * from './response-schemas';
3
+ export * from './domain-schemas';
4
+ export * from './operations';
5
+ export * from './build-openapi-document';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/openapi/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,0BAA0B,CAAC"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./response-schemas"), exports);
19
+ __exportStar(require("./domain-schemas"), exports);
20
+ __exportStar(require("./operations"), exports);
21
+ __exportStar(require("./build-openapi-document"), exports);
@@ -0,0 +1,8 @@
1
+ import type { OperationDescriptor } from './types';
2
+ /**
3
+ * Every mounted SAFE HTTP operation, one descriptor each. This is the single
4
+ * feed for `buildOpenApiDocument` and the subject the coverage test reconciles
5
+ * against the live Express route table.
6
+ */
7
+ export declare const operations: OperationDescriptor[];
8
+ //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../src/openapi/operations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQnD;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,mBAAmB,EAO3C,CAAC"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.operations = void 0;
4
+ const v1_openapi_1 = require("../routes/authorities/v1.openapi");
5
+ const v1_openapi_2 = require("../routes/events/v1.openapi");
6
+ const v1_openapi_3 = require("../routes/functions/v1.openapi");
7
+ const rule_set_openapi_1 = require("../routes/rules/v1/rule-set.openapi");
8
+ const v1_openapi_4 = require("../routes/sharedata/v1.openapi");
9
+ const validate_rules_openapi_1 = require("../routes/validation/rules/validate-rules.openapi");
10
+ /**
11
+ * Every mounted SAFE HTTP operation, one descriptor each. This is the single
12
+ * feed for `buildOpenApiDocument` and the subject the coverage test reconciles
13
+ * against the live Express route table.
14
+ */
15
+ exports.operations = [
16
+ ...v1_openapi_1.authorityOperations,
17
+ ...v1_openapi_2.eventOperations,
18
+ ...v1_openapi_3.functionOperations,
19
+ ...rule_set_openapi_1.ruleSetOperations,
20
+ ...v1_openapi_4.shareDataOperations,
21
+ ...validate_rules_openapi_1.validateRulesOperations,
22
+ ];
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * HTTP response envelopes for the SAFE endpoints surface. These describe the
4
+ * transport shape (`{ successful, … }`) the route handlers actually return —
5
+ * an HTTP concern specific to this API, deliberately kept out of
6
+ * `@degenerate/generic` (domain models). Payloads inside an envelope reuse
7
+ * generic domain schemas where one exists; otherwise the envelope is documented
8
+ * and the payload left loosely typed rather than fabricated.
9
+ *
10
+ * Define each schema once and reuse the instance so the OpenAPI generator
11
+ * dedupes it to a single `components.schemas` entry.
12
+ */
13
+ /** Uniform error body emitted by the three error handlers at 400 / 403 / 404 / 500 / 501. */
14
+ export declare const ErrorResponseSchema: z.ZodObject<{
15
+ successful: z.ZodLiteral<false>;
16
+ error: z.ZodString;
17
+ }, z.core.$strip>;
18
+ export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;
19
+ /** Bare success body: `{ successful: true }` with no additional payload. */
20
+ export declare const SuccessResponseSchema: z.ZodObject<{
21
+ successful: z.ZodLiteral<true>;
22
+ }, z.core.$strip>;
23
+ export type SuccessResponse = z.infer<typeof SuccessResponseSchema>;
24
+ /**
25
+ * Build a success envelope that carries extra fields alongside
26
+ * `successful: true`. Define the result as a module-level const in the group's
27
+ * descriptor file so the generator sees one stable instance per shape.
28
+ */
29
+ export declare const successEnvelope: <Shape extends z.ZodRawShape>(payload: Shape) => z.ZodObject<{
30
+ successful: z.ZodLiteral<true>;
31
+ } & Shape extends infer T ? { -readonly [P in keyof T]: T[P]; } : never, z.core.$strip>;
32
+ //# sourceMappingURL=response-schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-schemas.d.ts","sourceRoot":"","sources":["../../src/openapi/response-schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;GAUG;AAEH,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB;;;iBAEE,CAAC;AACnC,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB;;iBAEE,CAAC;AACrC,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,SAAS,CAAC,CAAC,WAAW,EAAE,SAAS,KAAK;;uFAClB,CAAC"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.successEnvelope = exports.SuccessResponseSchema = exports.ErrorResponseSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ /**
6
+ * HTTP response envelopes for the SAFE endpoints surface. These describe the
7
+ * transport shape (`{ successful, … }`) the route handlers actually return —
8
+ * an HTTP concern specific to this API, deliberately kept out of
9
+ * `@degenerate/generic` (domain models). Payloads inside an envelope reuse
10
+ * generic domain schemas where one exists; otherwise the envelope is documented
11
+ * and the payload left loosely typed rather than fabricated.
12
+ *
13
+ * Define each schema once and reuse the instance so the OpenAPI generator
14
+ * dedupes it to a single `components.schemas` entry.
15
+ */
16
+ /** Uniform error body emitted by the three error handlers at 400 / 403 / 404 / 500 / 501. */
17
+ exports.ErrorResponseSchema = zod_1.z
18
+ .object({ successful: zod_1.z.literal(false), error: zod_1.z.string() })
19
+ .meta({ id: 'ErrorResponse' });
20
+ /** Bare success body: `{ successful: true }` with no additional payload. */
21
+ exports.SuccessResponseSchema = zod_1.z
22
+ .object({ successful: zod_1.z.literal(true) })
23
+ .meta({ id: 'SuccessResponse' });
24
+ /**
25
+ * Build a success envelope that carries extra fields alongside
26
+ * `successful: true`. Define the result as a module-level const in the group's
27
+ * descriptor file so the generator sees one stable instance per shape.
28
+ */
29
+ const successEnvelope = (payload) => zod_1.z.object({ successful: zod_1.z.literal(true), ...payload });
30
+ exports.successEnvelope = successEnvelope;
@@ -0,0 +1,29 @@
1
+ import type { z } from 'zod';
2
+ import type { AuthorizationAudience } from '@degenerate/generic';
3
+ /**
4
+ * One descriptor per mounted HTTP operation. This is the single place that
5
+ * records everything OpenAPI needs about an operation that Express does not
6
+ * expose introspectively: human summary, the JWT audience that gates it, and
7
+ * the Zod schemas bound to its request body and each response status.
8
+ *
9
+ * Descriptors live beside the route files they describe (one array per group,
10
+ * e.g. `routes/sharedata/v1.openapi.ts`) and are aggregated into a single
11
+ * `operations[]`. A coverage test reconciles `operations[]` against the live
12
+ * Express route table so the two cannot silently drift.
13
+ */
14
+ export interface OperationDescriptor {
15
+ method: 'get' | 'post' | 'patch' | 'delete';
16
+ /** OpenAPI path template, e.g. `/share-data/v1/{sharedataid}`. */
17
+ path: string;
18
+ /** JWT audience that gates the operation, or `'public'` for no auth. */
19
+ security: AuthorizationAudience | 'public';
20
+ summary: string;
21
+ /** Request body schema; omit for operations that take no body. */
22
+ request?: z.ZodType;
23
+ /** Response schema per HTTP status — at minimum the success status. */
24
+ response: {
25
+ [status: number]: z.ZodType;
26
+ };
27
+ deprecated?: boolean;
28
+ }
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/openapi/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC5C,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,QAAQ,EAAE,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IACpB,uEAAuE;IACvE,QAAQ,EAAE;QAAE,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,CAAA;KAAE,CAAC;IAC1C,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ import type { OperationDescriptor } from '../../openapi/types';
2
+ /** Operations under `/authorities` — all gated by the AUTHORITIES JWT audience. */
3
+ export declare const authorityOperations: OperationDescriptor[];
4
+ //# sourceMappingURL=v1.openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1.openapi.d.ts","sourceRoot":"","sources":["../../../src/routes/authorities/v1.openapi.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAW/D,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,EAAE,mBAAmB,EAyDpD,CAAC"}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authorityOperations = void 0;
4
+ const zod_1 = require("zod");
5
+ const generic_1 = require("@degenerate/generic");
6
+ const response_schemas_1 = require("../../openapi/response-schemas");
7
+ const AuthorityIdResponseSchema = (0, response_schemas_1.successEnvelope)({ authorityId: zod_1.z.string() }).meta({
8
+ id: 'AuthorityIdResponse',
9
+ });
10
+ /** Operations under `/authorities` — all gated by the AUTHORITIES JWT audience. */
11
+ exports.authorityOperations = [
12
+ {
13
+ method: 'post',
14
+ path: '/authorities/v1',
15
+ security: generic_1.AuthorizationAudience.AUTHORITIES,
16
+ summary: 'Create an authority.',
17
+ request: generic_1.ISAFETransactionAuthorityAddSchema,
18
+ response: {
19
+ 200: response_schemas_1.SuccessResponseSchema,
20
+ 400: response_schemas_1.ErrorResponseSchema,
21
+ 403: response_schemas_1.ErrorResponseSchema,
22
+ },
23
+ },
24
+ {
25
+ method: 'get',
26
+ path: '/authorities/v1',
27
+ security: generic_1.AuthorizationAudience.AUTHORITIES,
28
+ summary: 'List authorities.',
29
+ response: {
30
+ 200: response_schemas_1.SuccessResponseSchema,
31
+ 403: response_schemas_1.ErrorResponseSchema,
32
+ },
33
+ },
34
+ {
35
+ method: 'get',
36
+ path: '/authorities/v1/{authorityid}',
37
+ security: generic_1.AuthorizationAudience.AUTHORITIES,
38
+ summary: 'Get an authority by id.',
39
+ response: {
40
+ 200: AuthorityIdResponseSchema,
41
+ 403: response_schemas_1.ErrorResponseSchema,
42
+ },
43
+ },
44
+ {
45
+ method: 'patch',
46
+ path: '/authorities/v1/{authorityid}',
47
+ security: generic_1.AuthorizationAudience.AUTHORITIES,
48
+ summary: 'Update an authority by id.',
49
+ request: generic_1.ISAFETransactionAuthorityUpdateSchema,
50
+ response: {
51
+ 200: AuthorityIdResponseSchema,
52
+ 400: response_schemas_1.ErrorResponseSchema,
53
+ 403: response_schemas_1.ErrorResponseSchema,
54
+ },
55
+ },
56
+ {
57
+ method: 'delete',
58
+ path: '/authorities/v1/{authorityid}',
59
+ security: generic_1.AuthorizationAudience.AUTHORITIES,
60
+ summary: 'Delete an authority by id.',
61
+ request: generic_1.ISAFETransactionAuthorityDeleteSchema,
62
+ response: {
63
+ 200: AuthorityIdResponseSchema,
64
+ 400: response_schemas_1.ErrorResponseSchema,
65
+ 403: response_schemas_1.ErrorResponseSchema,
66
+ },
67
+ },
68
+ ];
@@ -0,0 +1,4 @@
1
+ import type { OperationDescriptor } from '../../openapi/types';
2
+ /** Operations under `/events` — all gated by the EVENTS JWT audience. */
3
+ export declare const eventOperations: OperationDescriptor[];
4
+ //# sourceMappingURL=v1.openapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v1.openapi.d.ts","sourceRoot":"","sources":["../../../src/routes/events/v1.openapi.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG/D,yEAAyE;AACzE,eAAO,MAAM,eAAe,EAAE,mBAAmB,EA+ChD,CAAC"}