tempest-express-sdk 0.31.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
@@ -16086,6 +16086,239 @@ function makeMetricsRouter(options = {}) {
16086
16086
  });
16087
16087
  return router;
16088
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
+ }
16089
16322
  var logger4 = new JSONLogger("tempest_express_sdk.api.server");
16090
16323
  function corsMiddleware(origins) {
16091
16324
  const allowAll = origins === "*";
@@ -16134,6 +16367,14 @@ async function createApp(options = {}) {
16134
16367
  mountRedoc(app, redocPath ?? "/redoc", specPath, redoc ?? {});
16135
16368
  }
16136
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
+ }
16137
16378
  registerExceptionHandlers(app, {
16138
16379
  ...options.errorHandling,
16139
16380
  ...options.catalog !== void 0 ? { catalog: options.catalog } : {}
@@ -17026,7 +17267,7 @@ async function withTestDatabase(models, fn) {
17026
17267
  }
17027
17268
 
17028
17269
  // src/version.ts
17029
- var VERSION = "0.31.0";
17270
+ var VERSION = "0.32.0";
17030
17271
 
17031
17272
  Object.defineProperty(exports, "OpenAPIRegistry", {
17032
17273
  enumerable: true,
@@ -17185,6 +17426,7 @@ Object.defineProperty(exports, "update", {
17185
17426
  get: function () { return tempestDbJs.update; }
17186
17427
  });
17187
17428
  exports.ADMIN_CSS = ADMIN_CSS;
17429
+ exports.ASYNCAPI_VERSION = ASYNCAPI_VERSION;
17188
17430
  exports.ActivationService = ActivationService;
17189
17431
  exports.AdminJsonSite = AdminJsonSite;
17190
17432
  exports.AdminModel = AdminModel;
@@ -17192,6 +17434,7 @@ exports.AdminPermission = AdminPermission;
17192
17434
  exports.AdminSessionStore = AdminSessionStore;
17193
17435
  exports.AdminSite = AdminSite;
17194
17436
  exports.AppException = AppException;
17437
+ exports.AsyncApiRegistry = AsyncApiRegistry;
17195
17438
  exports.AttemptThrottle = AttemptThrottle;
17196
17439
  exports.AuditAction = AuditAction;
17197
17440
  exports.BaseAuditLogModel = BaseAuditLogModel;
@@ -17253,6 +17496,7 @@ exports.OAuthError = OAuthError;
17253
17496
  exports.OIDCProvider = OIDCProvider;
17254
17497
  exports.OutboxRelay = OutboxRelay;
17255
17498
  exports.OutboxStatus = OutboxStatus;
17499
+ exports.PERSPECTIVE_EXTENSION = PERSPECTIVE_EXTENSION;
17256
17500
  exports.PHONE_BR_PATTERN = PHONE_BR_PATTERN;
17257
17501
  exports.PasswordResetService = PasswordResetService;
17258
17502
  exports.PasswordUtils = PasswordUtils;
@@ -17324,6 +17568,7 @@ exports.corsSettingsShape = corsSettingsShape;
17324
17568
  exports.cpfField = cpfField;
17325
17569
  exports.cpfOrCnpjField = cpfOrCnpjField;
17326
17570
  exports.createApp = createApp;
17571
+ exports.createAsyncApiRegistry = createAsyncApiRegistry;
17327
17572
  exports.createOpenApiRegistry = createOpenApiRegistry;
17328
17573
  exports.createTestDatabase = createTestDatabase;
17329
17574
  exports.createdByColumn = createdByColumn;
@@ -17349,6 +17594,7 @@ exports.foreignKeyLabel = foreignKeyLabel;
17349
17594
  exports.foreignKeyTable = foreignKeyTable;
17350
17595
  exports.formatCellValue = formatCellValue;
17351
17596
  exports.formatFieldValue = formatFieldValue;
17597
+ exports.generateAsyncApiDocument = generateAsyncApiDocument;
17352
17598
  exports.generateCsrfToken = generateCsrfToken;
17353
17599
  exports.generateOAuthState = generateOAuthState;
17354
17600
  exports.generateOpaqueToken = generateOpaqueToken;
@@ -17409,6 +17655,7 @@ exports.mfaCodeSchema = mfaCodeSchema;
17409
17655
  exports.mfaEnrollResponseSchema = mfaEnrollResponseSchema;
17410
17656
  exports.minioSettingsShape = minioSettingsShape;
17411
17657
  exports.modifyDict = modifyDict;
17658
+ exports.mountAsyncApiJson = mountAsyncApiJson;
17412
17659
  exports.mountOpenApiJson = mountOpenApiJson;
17413
17660
  exports.mountRedoc = mountRedoc;
17414
17661
  exports.mountSwaggerUi = mountSwaggerUi;