tempest-express-sdk 0.30.0 → 0.32.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.
package/dist/index.cjs CHANGED
@@ -15829,8 +15829,40 @@ function registerExceptionHandlers(app, options = {}) {
15829
15829
  })
15830
15830
  );
15831
15831
  }
15832
+ function normalizeSchema(refId, schema) {
15833
+ const candidate = schema;
15834
+ if (typeof candidate.openapi === "function") return schema;
15835
+ if (typeof candidate.meta === "function") {
15836
+ return candidate.meta({ id: refId });
15837
+ }
15838
+ throw new Error(
15839
+ `Cannot register "${refId}" for OpenAPI: the value carries neither \`.openapi()\` nor zod v4's \`.meta()\`. This usually means it is not a zod schema, or it comes from a second copy of zod in node_modules \u2014 check \`npm ls zod\` shows a single deduped instance.`
15840
+ );
15841
+ }
15842
+ var TempestOpenApiRegistry = class extends zodToOpenapi.OpenAPIRegistry {
15843
+ /**
15844
+ * Register a component schema, normalizing it first.
15845
+ *
15846
+ * @param refId - The component name.
15847
+ * @param zodSchema - The schema to register.
15848
+ * @returns The registered schema, as the library returns it.
15849
+ */
15850
+ register(refId, zodSchema) {
15851
+ return super.register(refId, normalizeSchema(refId, zodSchema));
15852
+ }
15853
+ /**
15854
+ * Register a parameter schema, normalizing it first.
15855
+ *
15856
+ * @param refId - The parameter name.
15857
+ * @param zodSchema - The schema to register.
15858
+ * @returns The registered schema, as the library returns it.
15859
+ */
15860
+ registerParameter(refId, zodSchema) {
15861
+ return super.registerParameter(refId, normalizeSchema(refId, zodSchema));
15862
+ }
15863
+ };
15832
15864
  function createOpenApiRegistry() {
15833
- return new zodToOpenapi.OpenAPIRegistry();
15865
+ return new TempestOpenApiRegistry();
15834
15866
  }
15835
15867
  function generateOpenApiDocument(registry, options) {
15836
15868
  const config = {
@@ -16054,6 +16086,239 @@ function makeMetricsRouter(options = {}) {
16054
16086
  });
16055
16087
  return router;
16056
16088
  }
16089
+ var PERSPECTIVE_EXTENSION = "x-tempest-perspective";
16090
+ var ASYNCAPI_VERSION = "3.0.0";
16091
+ function actionFor(direction) {
16092
+ return direction === "clientToServer" ? "receive" : "send";
16093
+ }
16094
+ var AsyncApiRegistry = class {
16095
+ /** Channels by `name`. */
16096
+ channels = /* @__PURE__ */ new Map();
16097
+ /** Messages by `name`. */
16098
+ messages = /* @__PURE__ */ new Map();
16099
+ /** Operations, in registration order. */
16100
+ operations = [];
16101
+ /**
16102
+ * Register the connection a socket is served on.
16103
+ *
16104
+ * @param channel - The channel definition.
16105
+ * @returns This registry, for chaining.
16106
+ * @throws Error When `name` is already registered.
16107
+ */
16108
+ registerChannel(channel) {
16109
+ if (this.channels.has(channel.name)) {
16110
+ throw new Error(`AsyncAPI channel "${channel.name}" is already registered.`);
16111
+ }
16112
+ this.channels.set(channel.name, channel);
16113
+ return this;
16114
+ }
16115
+ /**
16116
+ * Register one frame the socket carries.
16117
+ *
16118
+ * @param message - The message definition.
16119
+ * @returns This registry, for chaining.
16120
+ * @throws Error When `name` is already registered.
16121
+ */
16122
+ registerMessage(message) {
16123
+ if (this.messages.has(message.name)) {
16124
+ throw new Error(`AsyncAPI message "${message.name}" is already registered.`);
16125
+ }
16126
+ this.messages.set(message.name, message);
16127
+ return this;
16128
+ }
16129
+ /**
16130
+ * Register a set of messages travelling one way on one channel.
16131
+ *
16132
+ * @param operation - The operation definition.
16133
+ * @returns This registry, for chaining.
16134
+ * @throws Error When `name` is already registered.
16135
+ */
16136
+ registerOperation(operation) {
16137
+ if (this.operations.some((existing) => existing.name === operation.name)) {
16138
+ throw new Error(`AsyncAPI operation "${operation.name}" is already registered.`);
16139
+ }
16140
+ this.operations.push(operation);
16141
+ return this;
16142
+ }
16143
+ /**
16144
+ * Render the AsyncAPI document.
16145
+ *
16146
+ * @param options - The `info` block, optional servers and default content
16147
+ * type.
16148
+ * @returns The document, JSON-serializable.
16149
+ * @throws Error When an operation names a channel or a message that was
16150
+ * never registered. A dangling `$ref` produces a document that validates
16151
+ * structurally and generates a client missing the frame, so it fails
16152
+ * here instead.
16153
+ */
16154
+ generate(options) {
16155
+ this.assertReferencesResolve();
16156
+ const schemas = this.renderPayloads();
16157
+ const messages = this.renderMessages();
16158
+ const channels = this.renderChannels(schemas);
16159
+ const operations = this.renderOperations();
16160
+ return {
16161
+ asyncapi: ASYNCAPI_VERSION,
16162
+ [PERSPECTIVE_EXTENSION]: "server",
16163
+ info: {
16164
+ title: options.info.title,
16165
+ version: options.info.version,
16166
+ ...options.info.description !== void 0 ? { description: options.info.description } : {}
16167
+ },
16168
+ ...options.servers !== void 0 ? { servers: options.servers } : {},
16169
+ defaultContentType: options.defaultContentType ?? "application/json",
16170
+ channels,
16171
+ operations,
16172
+ components: { messages, schemas }
16173
+ };
16174
+ }
16175
+ /**
16176
+ * Fail when an operation points at something that was never registered.
16177
+ *
16178
+ * @throws Error Naming the operation and what it could not resolve.
16179
+ */
16180
+ assertReferencesResolve() {
16181
+ for (const operation of this.operations) {
16182
+ if (!this.channels.has(operation.channel)) {
16183
+ throw new Error(
16184
+ `AsyncAPI operation "${operation.name}" refers to channel "${operation.channel}", which is not registered.`
16185
+ );
16186
+ }
16187
+ for (const message of operation.messages) {
16188
+ if (!this.messages.has(message)) {
16189
+ throw new Error(
16190
+ `AsyncAPI operation "${operation.name}" refers to message "${message}", which is not registered.`
16191
+ );
16192
+ }
16193
+ }
16194
+ }
16195
+ }
16196
+ /**
16197
+ * Render every payload schema through the OpenAPI generator.
16198
+ *
16199
+ * @returns Component schemas keyed by message name, plus any handshake
16200
+ * schema a channel declared.
16201
+ *
16202
+ * AsyncAPI 3 payloads are JSON Schema, and `generateComponents` emits
16203
+ * exactly that from the Zod objects already validating at runtime. Reusing
16204
+ * it is what keeps the document from describing a shape the server would
16205
+ * reject.
16206
+ */
16207
+ renderPayloads() {
16208
+ const registry = createOpenApiRegistry();
16209
+ for (const message of this.messages.values()) {
16210
+ registry.register(message.name, message.schema);
16211
+ }
16212
+ for (const channel of this.channels.values()) {
16213
+ if (channel.handshakeHeaders !== void 0) {
16214
+ registry.register(`${channel.name}Headers`, channel.handshakeHeaders);
16215
+ }
16216
+ if (channel.handshakeQuery !== void 0) {
16217
+ registry.register(`${channel.name}Query`, channel.handshakeQuery);
16218
+ }
16219
+ }
16220
+ const components = new zodToOpenapi.OpenApiGeneratorV31(registry.definitions).generateComponents();
16221
+ const schemas = components.components?.schemas ?? {};
16222
+ return schemas;
16223
+ }
16224
+ /**
16225
+ * Render `components.messages`.
16226
+ *
16227
+ * @returns Message objects keyed by name, each pointing at its payload.
16228
+ */
16229
+ renderMessages() {
16230
+ const rendered = {};
16231
+ for (const [name, message] of this.messages) {
16232
+ rendered[name] = {
16233
+ name,
16234
+ contentType: message.contentType ?? "application/json",
16235
+ ...message.summary !== void 0 ? { summary: message.summary } : {},
16236
+ ...message.description !== void 0 ? { description: message.description } : {},
16237
+ payload: { $ref: `#/components/schemas/${name}` }
16238
+ };
16239
+ }
16240
+ return rendered;
16241
+ }
16242
+ /**
16243
+ * Render `channels`, each listing every message it can carry.
16244
+ *
16245
+ * @param schemas - The rendered component schemas, to inline the
16246
+ * handshake ones into the binding.
16247
+ * @returns Channel objects keyed by name.
16248
+ *
16249
+ * The handshake schemas are **inlined** rather than `$ref`-ed. The
16250
+ * specification types the binding's `headers` and `query` as
16251
+ * `oneOf: [Schema, Reference]`, and a bare `{"$ref": ...}` object
16252
+ * satisfies both branches — so `oneOf` sees two matches and the document
16253
+ * fails validation against AsyncAPI's own JSON Schema. Measured: the same
16254
+ * binding with the schema inlined validates clean.
16255
+ *
16256
+ * A channel lists every registered message rather than only those its own
16257
+ * operations use: the specification requires an operation's `messages` to
16258
+ * be a subset of its channel's, and with one connection per document the
16259
+ * distinction buys nothing.
16260
+ */
16261
+ renderChannels(schemas) {
16262
+ const everyMessage = {};
16263
+ for (const name of this.messages.keys()) {
16264
+ everyMessage[name] = { $ref: `#/components/messages/${name}` };
16265
+ }
16266
+ const rendered = {};
16267
+ for (const [name, channel] of this.channels) {
16268
+ const bindings = {
16269
+ bindingVersion: "0.1.0",
16270
+ method: "GET"
16271
+ };
16272
+ if (channel.handshakeHeaders !== void 0) {
16273
+ bindings.headers = schemas[`${name}Headers`];
16274
+ }
16275
+ if (channel.handshakeQuery !== void 0) {
16276
+ bindings.query = schemas[`${name}Query`];
16277
+ }
16278
+ rendered[name] = {
16279
+ address: channel.address,
16280
+ ...channel.title !== void 0 ? { title: channel.title } : {},
16281
+ ...channel.description !== void 0 ? { description: channel.description } : {},
16282
+ messages: everyMessage,
16283
+ bindings: { ws: bindings }
16284
+ };
16285
+ }
16286
+ return rendered;
16287
+ }
16288
+ /**
16289
+ * Render `operations`, translating each direction into an `action`.
16290
+ *
16291
+ * @returns Operation objects keyed by name.
16292
+ */
16293
+ renderOperations() {
16294
+ const rendered = {};
16295
+ for (const operation of this.operations) {
16296
+ rendered[operation.name] = {
16297
+ action: actionFor(operation.direction),
16298
+ channel: { $ref: `#/channels/${operation.channel}` },
16299
+ ...operation.summary !== void 0 ? { summary: operation.summary } : {},
16300
+ ...operation.description !== void 0 ? { description: operation.description } : {},
16301
+ messages: operation.messages.map((message) => ({
16302
+ $ref: `#/channels/${operation.channel}/messages/${message}`
16303
+ }))
16304
+ };
16305
+ }
16306
+ return rendered;
16307
+ }
16308
+ };
16309
+ function createAsyncApiRegistry() {
16310
+ return new AsyncApiRegistry();
16311
+ }
16312
+ function generateAsyncApiDocument(registry, options) {
16313
+ return registry.generate(options);
16314
+ }
16315
+
16316
+ // src/asyncapi/mount.ts
16317
+ function mountAsyncApiJson(app, path, document) {
16318
+ app.get(path, (_req, res) => {
16319
+ res.json(document);
16320
+ });
16321
+ }
16057
16322
  var logger4 = new JSONLogger("tempest_express_sdk.api.server");
16058
16323
  function corsMiddleware(origins) {
16059
16324
  const allowAll = origins === "*";
@@ -16102,6 +16367,14 @@ async function createApp(options = {}) {
16102
16367
  mountRedoc(app, redocPath ?? "/redoc", specPath, redoc ?? {});
16103
16368
  }
16104
16369
  }
16370
+ if (options.asyncapi) {
16371
+ const { registry, jsonPath, ...genOptions } = options.asyncapi;
16372
+ mountAsyncApiJson(
16373
+ app,
16374
+ jsonPath ?? "/asyncapi.json",
16375
+ generateAsyncApiDocument(registry, genOptions)
16376
+ );
16377
+ }
16105
16378
  registerExceptionHandlers(app, {
16106
16379
  ...options.errorHandling,
16107
16380
  ...options.catalog !== void 0 ? { catalog: options.catalog } : {}
@@ -16994,12 +17267,16 @@ async function withTestDatabase(models, fn) {
16994
17267
  }
16995
17268
 
16996
17269
  // src/version.ts
16997
- var VERSION = "0.30.0";
17270
+ var VERSION = "0.32.0";
16998
17271
 
16999
17272
  Object.defineProperty(exports, "OpenAPIRegistry", {
17000
17273
  enumerable: true,
17001
17274
  get: function () { return zodToOpenapi.OpenAPIRegistry; }
17002
17275
  });
17276
+ Object.defineProperty(exports, "extendZodWithOpenApi", {
17277
+ enumerable: true,
17278
+ get: function () { return zodToOpenapi.extendZodWithOpenApi; }
17279
+ });
17003
17280
  Object.defineProperty(exports, "z", {
17004
17281
  enumerable: true,
17005
17282
  get: function () { return zod.z; }
@@ -17149,6 +17426,7 @@ Object.defineProperty(exports, "update", {
17149
17426
  get: function () { return tempestDbJs.update; }
17150
17427
  });
17151
17428
  exports.ADMIN_CSS = ADMIN_CSS;
17429
+ exports.ASYNCAPI_VERSION = ASYNCAPI_VERSION;
17152
17430
  exports.ActivationService = ActivationService;
17153
17431
  exports.AdminJsonSite = AdminJsonSite;
17154
17432
  exports.AdminModel = AdminModel;
@@ -17156,6 +17434,7 @@ exports.AdminPermission = AdminPermission;
17156
17434
  exports.AdminSessionStore = AdminSessionStore;
17157
17435
  exports.AdminSite = AdminSite;
17158
17436
  exports.AppException = AppException;
17437
+ exports.AsyncApiRegistry = AsyncApiRegistry;
17159
17438
  exports.AttemptThrottle = AttemptThrottle;
17160
17439
  exports.AuditAction = AuditAction;
17161
17440
  exports.BaseAuditLogModel = BaseAuditLogModel;
@@ -17217,6 +17496,7 @@ exports.OAuthError = OAuthError;
17217
17496
  exports.OIDCProvider = OIDCProvider;
17218
17497
  exports.OutboxRelay = OutboxRelay;
17219
17498
  exports.OutboxStatus = OutboxStatus;
17499
+ exports.PERSPECTIVE_EXTENSION = PERSPECTIVE_EXTENSION;
17220
17500
  exports.PHONE_BR_PATTERN = PHONE_BR_PATTERN;
17221
17501
  exports.PasswordResetService = PasswordResetService;
17222
17502
  exports.PasswordUtils = PasswordUtils;
@@ -17288,6 +17568,7 @@ exports.corsSettingsShape = corsSettingsShape;
17288
17568
  exports.cpfField = cpfField;
17289
17569
  exports.cpfOrCnpjField = cpfOrCnpjField;
17290
17570
  exports.createApp = createApp;
17571
+ exports.createAsyncApiRegistry = createAsyncApiRegistry;
17291
17572
  exports.createOpenApiRegistry = createOpenApiRegistry;
17292
17573
  exports.createTestDatabase = createTestDatabase;
17293
17574
  exports.createdByColumn = createdByColumn;
@@ -17313,6 +17594,7 @@ exports.foreignKeyLabel = foreignKeyLabel;
17313
17594
  exports.foreignKeyTable = foreignKeyTable;
17314
17595
  exports.formatCellValue = formatCellValue;
17315
17596
  exports.formatFieldValue = formatFieldValue;
17597
+ exports.generateAsyncApiDocument = generateAsyncApiDocument;
17316
17598
  exports.generateCsrfToken = generateCsrfToken;
17317
17599
  exports.generateOAuthState = generateOAuthState;
17318
17600
  exports.generateOpaqueToken = generateOpaqueToken;
@@ -17373,6 +17655,7 @@ exports.mfaCodeSchema = mfaCodeSchema;
17373
17655
  exports.mfaEnrollResponseSchema = mfaEnrollResponseSchema;
17374
17656
  exports.minioSettingsShape = minioSettingsShape;
17375
17657
  exports.modifyDict = modifyDict;
17658
+ exports.mountAsyncApiJson = mountAsyncApiJson;
17376
17659
  exports.mountOpenApiJson = mountOpenApiJson;
17377
17660
  exports.mountRedoc = mountRedoc;
17378
17661
  exports.mountSwaggerUi = mountSwaggerUi;