@degenerate/endpoints 0.1.7 → 0.4.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 (87) hide show
  1. package/README.md +18 -0
  2. package/dist/cors/authority/jwt/build-validate-jwt-for-audience.d.ts +41 -0
  3. package/dist/cors/authority/jwt/build-validate-jwt-for-audience.d.ts.map +1 -0
  4. package/dist/cors/authority/jwt/build-validate-jwt-for-audience.js +75 -0
  5. package/dist/cors/authority/jwt/validate-jwt-authorities.d.ts +5 -13
  6. package/dist/cors/authority/jwt/validate-jwt-authorities.d.ts.map +1 -1
  7. package/dist/cors/authority/jwt/validate-jwt-authorities.js +7 -27
  8. package/dist/cors/authority/jwt/validate-jwt-endpoints.d.ts +5 -13
  9. package/dist/cors/authority/jwt/validate-jwt-endpoints.d.ts.map +1 -1
  10. package/dist/cors/authority/jwt/validate-jwt-endpoints.js +7 -27
  11. package/dist/cors/authority/jwt/validate-jwt-events.d.ts +5 -13
  12. package/dist/cors/authority/jwt/validate-jwt-events.d.ts.map +1 -1
  13. package/dist/cors/authority/jwt/validate-jwt-events.js +7 -27
  14. package/dist/cors/authority/jwt/validate-jwt-functions.d.ts +5 -13
  15. package/dist/cors/authority/jwt/validate-jwt-functions.d.ts.map +1 -1
  16. package/dist/cors/authority/jwt/validate-jwt-functions.js +7 -27
  17. package/dist/cors/authority/jwt/validate-jwt-rules.d.ts +5 -13
  18. package/dist/cors/authority/jwt/validate-jwt-rules.d.ts.map +1 -1
  19. package/dist/cors/authority/jwt/validate-jwt-rules.js +7 -27
  20. package/dist/cors/authority/jwt/validate-jwt-share-data.d.ts +5 -13
  21. package/dist/cors/authority/jwt/validate-jwt-share-data.d.ts.map +1 -1
  22. package/dist/cors/authority/jwt/validate-jwt-share-data.js +7 -27
  23. package/dist/cors/endpoints/helpers/validate-incoming-request-data.d.ts.map +1 -1
  24. package/dist/cors/endpoints/helpers/validate-incoming-request-data.js +1 -1
  25. package/dist/cors/index.d.ts +1 -0
  26. package/dist/cors/index.d.ts.map +1 -1
  27. package/dist/cors/index.js +1 -0
  28. package/dist/examples/express-server/app.d.ts +20 -2
  29. package/dist/examples/express-server/app.d.ts.map +1 -1
  30. package/dist/examples/express-server/app.js +42 -21
  31. package/dist/examples/express-server/authenticate-realtime.d.ts +27 -0
  32. package/dist/examples/express-server/authenticate-realtime.d.ts.map +1 -0
  33. package/dist/examples/express-server/authenticate-realtime.js +23 -0
  34. package/dist/examples/express-server/server.d.ts +32 -1
  35. package/dist/examples/express-server/server.d.ts.map +1 -1
  36. package/dist/examples/express-server/server.js +61 -7
  37. package/dist/index.d.ts +1 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1 -0
  40. package/dist/openapi/build-openapi-document.d.ts +27 -0
  41. package/dist/openapi/build-openapi-document.d.ts.map +1 -0
  42. package/dist/openapi/build-openapi-document.js +223 -0
  43. package/dist/openapi/domain-schemas.d.ts +16 -0
  44. package/dist/openapi/domain-schemas.d.ts.map +1 -0
  45. package/dist/openapi/domain-schemas.js +39 -0
  46. package/dist/openapi/index.d.ts +6 -0
  47. package/dist/openapi/index.d.ts.map +1 -0
  48. package/dist/openapi/index.js +21 -0
  49. package/dist/openapi/operations.d.ts +8 -0
  50. package/dist/openapi/operations.d.ts.map +1 -0
  51. package/dist/openapi/operations.js +22 -0
  52. package/dist/openapi/response-schemas.d.ts +32 -0
  53. package/dist/openapi/response-schemas.d.ts.map +1 -0
  54. package/dist/openapi/response-schemas.js +30 -0
  55. package/dist/openapi/types.d.ts +29 -0
  56. package/dist/openapi/types.d.ts.map +1 -0
  57. package/dist/openapi/types.js +2 -0
  58. package/dist/routes/authorities/v1.openapi.d.ts +4 -0
  59. package/dist/routes/authorities/v1.openapi.d.ts.map +1 -0
  60. package/dist/routes/authorities/v1.openapi.js +68 -0
  61. package/dist/routes/events/v1.openapi.d.ts +4 -0
  62. package/dist/routes/events/v1.openapi.d.ts.map +1 -0
  63. package/dist/routes/events/v1.openapi.js +54 -0
  64. package/dist/routes/events/v1.routes.d.ts.map +1 -1
  65. package/dist/routes/events/v1.routes.js +5 -3
  66. package/dist/routes/events.routes.d.ts.map +1 -1
  67. package/dist/routes/events.routes.js +10 -60
  68. package/dist/routes/functions/v1.openapi.d.ts +4 -0
  69. package/dist/routes/functions/v1.openapi.d.ts.map +1 -0
  70. package/dist/routes/functions/v1.openapi.js +68 -0
  71. package/dist/routes/functions.routes.d.ts.map +1 -1
  72. package/dist/routes/functions.routes.js +10 -60
  73. package/dist/routes/rules/v1/rule-set.openapi.d.ts +4 -0
  74. package/dist/routes/rules/v1/rule-set.openapi.d.ts.map +1 -0
  75. package/dist/routes/rules/v1/rule-set.openapi.js +72 -0
  76. package/dist/routes/rules/v1/rule-set.routes.js +3 -3
  77. package/dist/routes/share-data.routes.d.ts.map +1 -1
  78. package/dist/routes/share-data.routes.js +17 -59
  79. package/dist/routes/sharedata/v1.openapi.d.ts +8 -0
  80. package/dist/routes/sharedata/v1.openapi.d.ts.map +1 -0
  81. package/dist/routes/sharedata/v1.openapi.js +60 -0
  82. package/dist/routes/sharedata/v1.routes.d.ts.map +1 -1
  83. package/dist/routes/sharedata/v1.routes.js +195 -9
  84. package/dist/routes/validation/rules/validate-rules.openapi.d.ts +8 -0
  85. package/dist/routes/validation/rules/validate-rules.openapi.d.ts.map +1 -0
  86. package/dist/routes/validation/rules/validate-rules.openapi.js +76 -0
  87. package/package.json +28 -15
@@ -1,3 +1,21 @@
1
- declare const app: import("express-serve-static-core").Express;
2
- export default app;
1
+ import express, { type Express } from 'express';
2
+ import { AuthorizedAuthorities } from '@degenerate/generic';
3
+ import { ShareDataManager } from '@degenerate/sharedata';
4
+ export interface CreateAppResult {
5
+ app: Express;
6
+ authorizedAuthorities: AuthorizedAuthorities;
7
+ shareDataManager: ShareDataManager;
8
+ }
9
+ /**
10
+ * Builds a fresh Express app with all SAFE routes wired in. Returns the app
11
+ * along with the shared `ShareDataManager` instance — handed back so callers
12
+ * (e.g. `createSafeServer` for realtime attachment) can subscribe to its
13
+ * events without reaching into `app.locals`.
14
+ *
15
+ * Route handlers access the manager via `req.app.get('shareDataManager')`,
16
+ * matching the existing `authorizedAuthorities` pattern.
17
+ */
18
+ export declare function createApp(): CreateAppResult;
19
+ declare const _default: express.Express;
20
+ export default _default;
3
21
  //# sourceMappingURL=app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/examples/express-server/app.ts"],"names":[],"mappings":"AAeA,QAAA,MAAM,GAAG,6CAAY,CAAC;AAsBtB,eAAe,GAAG,CAAC"}
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"}
@@ -3,7 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createApp = createApp;
6
7
  const express_1 = __importDefault(require("express"));
8
+ const generic_1 = require("@degenerate/generic");
9
+ const sharedata_1 = require("@degenerate/sharedata");
7
10
  //#region Route Imports
8
11
  const authorities_routes_1 = require("../../routes/authorities.routes");
9
12
  const events_routes_1 = require("../../routes/events.routes");
@@ -11,25 +14,43 @@ const functions_routes_1 = require("../../routes/functions.routes");
11
14
  const rules_routes_1 = require("../../routes/rules.routes");
12
15
  const share_data_routes_1 = require("../../routes/share-data.routes");
13
16
  const validation_routes_1 = require("../../routes/validation.routes");
14
- const generic_1 = require("@degenerate/generic");
15
17
  const cors_1 = require("../../cors");
16
- //#endregion Route Imports
17
- // create the express application
18
- const app = (0, express_1.default)();
19
- app.use(express_1.default.json());
20
- const authorizedAuthorities = new generic_1.AuthorizedAuthorities();
21
- // Initialize the authorized authorities here if needed
22
- // e.g., authorizedAuthorities.addAuthorizedJWTAuthority(...);
23
- // Make the instance available throughout the app
24
- app.set('authorizedAuthorities', authorizedAuthorities);
25
- // Make sure you enable whatever middleware you need here
26
- app.use('/authorities', authorities_routes_1.authorities); // add the authorities routes
27
- app.use('/events', events_routes_1.events); // add the events routes
28
- app.use('/functions', functions_routes_1.functions); // add the functions routes
29
- app.use('/rules', rules_routes_1.rules); // add the rules routes
30
- app.use('/share-data', share_data_routes_1.shareData); // add the share data routes
31
- app.use('/validation', validation_routes_1.validation); // add the validation routes
32
- app.use(cors_1.unauthorizedHandler); // the unauthorized handler should be the first error handler
33
- app.use(cors_1.badRequestHandler); // the bad request handler should be the second error handler
34
- app.use(cors_1.allErrorHandler); // the all error handler catches all remaining errors
35
- exports.default = app;
18
+ const openapi_1 = require("../../openapi");
19
+ /**
20
+ * Builds a fresh Express app with all SAFE routes wired in. Returns the app
21
+ * along with the shared `ShareDataManager` instance — handed back so callers
22
+ * (e.g. `createSafeServer` for realtime attachment) can subscribe to its
23
+ * events without reaching into `app.locals`.
24
+ *
25
+ * Route handlers access the manager via `req.app.get('shareDataManager')`,
26
+ * matching the existing `authorizedAuthorities` pattern.
27
+ */
28
+ function createApp() {
29
+ const app = (0, express_1.default)();
30
+ app.use(express_1.default.json());
31
+ const authorizedAuthorities = new generic_1.AuthorizedAuthorities();
32
+ const shareDataManager = new sharedata_1.ShareDataManager();
33
+ app.set('authorizedAuthorities', authorizedAuthorities);
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
+ });
42
+ app.use('/authorities', authorities_routes_1.authorities);
43
+ app.use('/events', events_routes_1.events);
44
+ app.use('/functions', functions_routes_1.functions);
45
+ app.use('/rules', rules_routes_1.rules);
46
+ app.use('/share-data', share_data_routes_1.shareData);
47
+ app.use('/validation', validation_routes_1.validation);
48
+ app.use(cors_1.unauthorizedHandler);
49
+ app.use(cors_1.badRequestHandler);
50
+ app.use(cors_1.allErrorHandler);
51
+ return { app, authorizedAuthorities, shareDataManager };
52
+ }
53
+ // Back-compat: tests and existing callers import the default app singleton.
54
+ // New code should prefer `createApp()` so each instance is isolated.
55
+ const defaultInstance = createApp();
56
+ exports.default = defaultInstance.app;
@@ -0,0 +1,27 @@
1
+ import type { AuthenticateUpgrade } from '@degenerate/realtime';
2
+ /**
3
+ * Context attached to every accepted WebSocket connection. Replace the
4
+ * placeholder fields with whatever your application needs to authorize
5
+ * subsequent messages (subject, scopes, tenant id, etc.).
6
+ */
7
+ export interface ExampleRealtimeContext {
8
+ subject: string;
9
+ }
10
+ /**
11
+ * Example upgrade authenticator. Accepts every connection with a synthetic
12
+ * subject — adequate for demos and integration tests, not for production.
13
+ *
14
+ * To wire real JWT auth, replace the body with something like:
15
+ *
16
+ * import { ... } from '@degenerate/generic';
17
+ * const token = extractTokenFromHeaderOrQuery(req);
18
+ * if (!token) return null;
19
+ * const payload = await verifyJwt(token, authorizedAuthorities);
20
+ * return payload ? { subject: payload.sub } : null;
21
+ *
22
+ * `validateJWTShareData` in `cors/authority/jwt/` is the HTTP-side equivalent
23
+ * — reuse the same key resolution + claim checks here so HTTP and WS share an
24
+ * authorization model.
25
+ */
26
+ export declare const exampleAuthenticate: AuthenticateUpgrade<ExampleRealtimeContext>;
27
+ //# sourceMappingURL=authenticate-realtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authenticate-realtime.d.ts","sourceRoot":"","sources":["../../../src/examples/express-server/authenticate-realtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,EAAE,mBAAmB,CAAC,sBAAsB,CAI3E,CAAC"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exampleAuthenticate = void 0;
4
+ /**
5
+ * Example upgrade authenticator. Accepts every connection with a synthetic
6
+ * subject — adequate for demos and integration tests, not for production.
7
+ *
8
+ * To wire real JWT auth, replace the body with something like:
9
+ *
10
+ * import { ... } from '@degenerate/generic';
11
+ * const token = extractTokenFromHeaderOrQuery(req);
12
+ * if (!token) return null;
13
+ * const payload = await verifyJwt(token, authorizedAuthorities);
14
+ * return payload ? { subject: payload.sub } : null;
15
+ *
16
+ * `validateJWTShareData` in `cors/authority/jwt/` is the HTTP-side equivalent
17
+ * — reuse the same key resolution + claim checks here so HTTP and WS share an
18
+ * authorization model.
19
+ */
20
+ const exampleAuthenticate = (_req) => {
21
+ return { subject: 'example-anonymous' };
22
+ };
23
+ exports.exampleAuthenticate = exampleAuthenticate;
@@ -1,2 +1,33 @@
1
- export {};
1
+ import { type Server as HttpServer } from 'http';
2
+ import type { Express } from 'express';
3
+ import { ShareDataManager } from '@degenerate/sharedata';
4
+ import { type RealtimeServerHandle } from '@degenerate/realtime';
5
+ export interface SafeServerHandle {
6
+ /** The Express application — the same instance imported from `./app`. */
7
+ readonly app: Express;
8
+ /** The underlying Node HTTP server. Call `.listen()` to start accepting. */
9
+ readonly httpServer: HttpServer;
10
+ /** ShareDataManager broadcasting to subscribed WebSocket clients. */
11
+ readonly manager: ShareDataManager;
12
+ /** Realtime websocket handle. Call `.close()` during shutdown. */
13
+ readonly realtime: RealtimeServerHandle;
14
+ /** Convenience: cleanly stop realtime + close the HTTP server. */
15
+ close(): Promise<void>;
16
+ }
17
+ /**
18
+ * Builds the example SAFE server: the existing Express app wrapped in a Node
19
+ * HTTP server, with the realtime WebSocket transport attached at `/realtime`.
20
+ * Returns handles for caller-controlled `listen()` and shutdown so this is
21
+ * safe to use from tests as well as a `bin/` entry point.
22
+ *
23
+ * The returned `manager` is the one wired to the realtime broadcast loop and
24
+ * also the one routes resolve via `req.app.get('shareDataManager')`. HTTP
25
+ * route mutations therefore appear as `transaction` frames on the websocket.
26
+ *
27
+ * `propose` is wired to the id-based function resolver: any `IShareFunction`
28
+ * registered on a ShareData with an `id` becomes addressable by clients via
29
+ * the `functionId` field in a `propose` message. Functions without an `id`
30
+ * remain invisible to the wire — useful for purely-internal functions.
31
+ */
32
+ export declare function createSafeServer(): SafeServerHandle;
2
33
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/examples/express-server/server.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/examples/express-server/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAC;AAC/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAGH,KAAK,oBAAoB,EAC5B,MAAM,sBAAsB,CAAC;AAI9B,MAAM,WAAW,gBAAgB;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,oBAAoB,CAAC;IACxC,kEAAkE;IAClE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,IAAI,gBAAgB,CA6BnD"}
@@ -1,9 +1,63 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const app_1 = __importDefault(require("./app"));
7
- app_1.default.listen(3000, () => {
8
- console.log('Server is running on port 3000');
9
- });
3
+ exports.createSafeServer = createSafeServer;
4
+ const http_1 = require("http");
5
+ const realtime_1 = require("@degenerate/realtime");
6
+ const app_1 = require("./app");
7
+ const authenticate_realtime_1 = require("./authenticate-realtime");
8
+ /**
9
+ * Builds the example SAFE server: the existing Express app wrapped in a Node
10
+ * HTTP server, with the realtime WebSocket transport attached at `/realtime`.
11
+ * Returns handles for caller-controlled `listen()` and shutdown so this is
12
+ * safe to use from tests as well as a `bin/` entry point.
13
+ *
14
+ * The returned `manager` is the one wired to the realtime broadcast loop and
15
+ * also the one routes resolve via `req.app.get('shareDataManager')`. HTTP
16
+ * route mutations therefore appear as `transaction` frames on the websocket.
17
+ *
18
+ * `propose` is wired to the id-based function resolver: any `IShareFunction`
19
+ * registered on a ShareData with an `id` becomes addressable by clients via
20
+ * the `functionId` field in a `propose` message. Functions without an `id`
21
+ * remain invisible to the wire — useful for purely-internal functions.
22
+ */
23
+ function createSafeServer() {
24
+ const { app, shareDataManager: manager } = (0, app_1.createApp)();
25
+ const httpServer = (0, http_1.createServer)(app);
26
+ const realtime = (0, realtime_1.attachRealtimeServer)({
27
+ server: httpServer,
28
+ manager,
29
+ authenticate: authenticate_realtime_1.exampleAuthenticate,
30
+ path: '/realtime',
31
+ resolveFunction: (0, realtime_1.createIdBasedFunctionResolver)(manager),
32
+ });
33
+ let closed = false;
34
+ return {
35
+ app,
36
+ httpServer,
37
+ manager,
38
+ realtime,
39
+ async close() {
40
+ if (closed)
41
+ return;
42
+ closed = true;
43
+ await realtime.close();
44
+ // `httpServer.close()` errors if the server is already closed.
45
+ // The realtime handle may have already terminated all WS sockets,
46
+ // but the HTTP listener still needs to be stopped. Swallow the
47
+ // "not running" error so close() is idempotent.
48
+ await new Promise((resolve) => {
49
+ httpServer.close(() => resolve());
50
+ });
51
+ },
52
+ };
53
+ }
54
+ // When run directly (e.g. `node dist/examples/express-server/server.js`),
55
+ // start the server on port 3000. Tests should call `createSafeServer()` and
56
+ // drive `.httpServer.listen(0, ...)` themselves.
57
+ if (require.main === module) {
58
+ const handle = createSafeServer();
59
+ handle.httpServer.listen(3000, () => {
60
+ // eslint-disable-next-line no-console
61
+ console.log('SAFE example server listening on :3000 (WS at /realtime)');
62
+ });
63
+ }
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);