tina4-nodejs 3.13.131 → 3.13.133

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.
@@ -80,12 +80,46 @@ function sanitizeSecurity(reqs, schemes) {
80
80
  });
81
81
  }
82
82
  function generate(routes, models = []) {
83
+ const schemes = resolveSecuritySchemes();
84
+ const spec = {
85
+ openapi: resolveOpenApiVersion(),
86
+ info: buildInfo(),
87
+ servers: resolveServers(),
88
+ paths: {},
89
+ components: {
90
+ schemas: {},
91
+ // Configurable security schemes (v3.13.42): bearerFormat via env, optional
92
+ // apiKey scheme, plus any programmatically-registered schemes (which may
93
+ // override bearerAuth — e.g. an oauth2 scheme with scopes).
94
+ securitySchemes: schemes
95
+ }
96
+ };
97
+ const tableToSchema = /* @__PURE__ */ new Map();
98
+ buildComponentSchemas(models, spec, tableToSchema);
99
+ const ctx = {
100
+ models,
101
+ tableToSchema,
102
+ schemes,
103
+ // Default scheme secured routes use when no explicit meta.security is set.
104
+ defaultScheme: process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth",
105
+ // Path filters (comma-separated raw-path prefixes).
106
+ includePrefixes: csv(process.env.TINA4_SWAGGER_INCLUDE),
107
+ excludePrefixes: csv(process.env.TINA4_SWAGGER_EXCLUDE),
108
+ // Reusable custom schemas referenced by routes via meta.requestSchema/responseSchemas.
109
+ refSchemas: /* @__PURE__ */ new Set(),
110
+ usedTags: [],
111
+ seenIds: /* @__PURE__ */ new Set()
112
+ };
113
+ for (const route of routes) {
114
+ buildOperation(route, spec, ctx);
115
+ }
116
+ buildRefSchemas(spec, ctx.refSchemas);
117
+ buildTags(spec, ctx.usedTags);
118
+ return spec;
119
+ }
120
+ function buildInfo() {
83
121
  const info = {
84
122
  title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
85
- // The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
86
- // 0.0.1). description defaults to the empty string, not a canned sentence.
87
- // Both are the settled cross-framework defaults (parity with the Python
88
- // master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
89
123
  version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
90
124
  description: process.env.TINA4_SWAGGER_DESCRIPTION ?? ""
91
125
  };
@@ -102,135 +136,134 @@ function generate(routes, models = []) {
102
136
  const [name, url] = licenseRaw.split("|").map((s) => s.trim());
103
137
  info.license = url ? { name, url } : { name };
104
138
  }
105
- const schemes = resolveSecuritySchemes();
106
- const spec = {
107
- openapi: resolveOpenApiVersion(),
108
- info,
109
- servers: resolveServers(),
110
- paths: {},
111
- components: {
112
- schemas: {},
113
- // Configurable security schemes (v3.13.42): bearerFormat via env, optional
114
- // apiKey scheme, plus any programmatically-registered schemes (which may
115
- // override bearerAuth — e.g. an oauth2 scheme with scopes).
116
- securitySchemes: schemes
117
- }
118
- };
119
- const defaultScheme = process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth";
120
- const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
121
- const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
122
- const refSchemas = /* @__PURE__ */ new Set();
123
- const tableToSchema = /* @__PURE__ */ new Map();
139
+ return info;
140
+ }
141
+ function buildComponentSchemas(models, spec, tableToSchema) {
124
142
  for (const model of models) {
125
143
  const schemaKey = schemaNameForModel(model);
126
144
  tableToSchema.set(model.tableName, schemaKey);
127
145
  spec.components.schemas[schemaKey] = modelToSchema(model);
128
146
  }
129
- const usedTags = [];
130
- const seenIds = /* @__PURE__ */ new Set();
131
- for (const route of routes) {
132
- if (!isIncludedPath(route.pattern, includePrefixes, excludePrefixes)) continue;
133
- const openApiPath = patternToOpenAPI(route.pattern);
134
- const method = route.method.toLowerCase();
135
- if (!spec.paths[openApiPath]) {
136
- spec.paths[openApiPath] = {};
147
+ }
148
+ function buildOperation(route, spec, ctx) {
149
+ if (!isIncludedPath(route.pattern, ctx.includePrefixes, ctx.excludePrefixes)) return;
150
+ const openApiPath = patternToOpenAPI(route.pattern);
151
+ const method = route.method.toLowerCase();
152
+ if (!spec.paths[openApiPath]) {
153
+ spec.paths[openApiPath] = {};
154
+ }
155
+ const tags = route.meta?.tags ?? inferTags(route.pattern);
156
+ for (const t of tags) {
157
+ if (!ctx.usedTags.includes(t)) ctx.usedTags.push(t);
158
+ }
159
+ const operation = {
160
+ operationId: uniqueOperationId(method, openApiPath, ctx.seenIds),
161
+ summary: route.meta?.summary ?? `${route.method} ${route.pattern}`,
162
+ tags,
163
+ responses: route.meta?.responses ?? {
164
+ "200": { description: "Successful response" }
137
165
  }
138
- const tags = route.meta?.tags ?? inferTags(route.pattern);
139
- for (const t of tags) {
140
- if (!usedTags.includes(t)) usedTags.push(t);
166
+ };
167
+ if (route.meta?.description) operation.description = route.meta.description;
168
+ if (route.meta?.deprecated) operation.deprecated = true;
169
+ const parameters = operationParameters(route, method, ctx.models);
170
+ if (parameters.length > 0) operation.parameters = parameters;
171
+ operationRequestBody(route, method, operation, ctx);
172
+ operationResponseSchemas(route, operation, ctx.refSchemas);
173
+ operationSecurity(route, method, operation, ctx.schemes, ctx.defaultScheme);
174
+ spec.paths[openApiPath][method] = operation;
175
+ }
176
+ function operationParameters(route, method, models) {
177
+ let parameters = [];
178
+ const pathParams = extractPathParams(route.pattern);
179
+ if (pathParams.length > 0) {
180
+ parameters = pathParams.map(({ name, schema }) => ({
181
+ name,
182
+ in: "path",
183
+ required: true,
184
+ schema
185
+ }));
186
+ }
187
+ if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
188
+ const modelName = inferModelFromPath(route.pattern);
189
+ if (modelName && models.some((m) => m.tableName === modelName)) {
190
+ parameters = [
191
+ ...parameters,
192
+ { name: "page", in: "query", schema: { type: "integer", default: 1 } },
193
+ { name: "limit", in: "query", schema: { type: "integer", default: 20 } },
194
+ { name: "sort", in: "query", schema: { type: "string" }, description: "Sort fields (prefix with - for descending)" }
195
+ ];
141
196
  }
142
- const operation = {
143
- operationId: uniqueOperationId(method, openApiPath, seenIds),
144
- summary: route.meta?.summary ?? `${route.method} ${route.pattern}`,
145
- tags,
146
- responses: route.meta?.responses ?? {
147
- "200": { description: "Successful response" }
148
- }
197
+ }
198
+ return parameters;
199
+ }
200
+ function mediaWithExample(schema, example) {
201
+ const media = { schema };
202
+ if (example !== void 0) media.example = example;
203
+ return media;
204
+ }
205
+ function operationRequestBody(route, method, operation, ctx) {
206
+ const reqSchemaRef = parseRequestSchema(route.meta?.requestSchema);
207
+ if (reqSchemaRef && (method === "post" || method === "put" || method === "patch")) {
208
+ ctx.refSchemas.add(reqSchemaRef.name);
209
+ const media = mediaWithExample({ $ref: `#/components/schemas/${reqSchemaRef.name}` }, route.meta?.example);
210
+ operation.requestBody = {
211
+ content: { [reqSchemaRef.contentType]: media }
149
212
  };
150
- if (route.meta?.description) operation.description = route.meta.description;
151
- if (route.meta?.deprecated) operation.deprecated = true;
152
- const pathParams = extractPathParams(route.pattern);
153
- if (pathParams.length > 0) {
154
- operation.parameters = pathParams.map(({ name, schema }) => ({
155
- name,
156
- in: "path",
157
- required: true,
158
- schema
159
- }));
160
- }
161
- if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
162
- const modelName = inferModelFromPath(route.pattern);
163
- if (modelName && models.some((m) => m.tableName === modelName)) {
164
- operation.parameters = [
165
- ...operation.parameters ?? [],
166
- { name: "page", in: "query", schema: { type: "integer", default: 1 } },
167
- { name: "limit", in: "query", schema: { type: "integer", default: 20 } },
168
- { name: "sort", in: "query", schema: { type: "string" }, description: "Sort fields (prefix with - for descending)" }
169
- ];
170
- }
171
- }
172
- const reqSchemaRef = parseRequestSchema(route.meta?.requestSchema);
173
- if (reqSchemaRef && (method === "post" || method === "put" || method === "patch")) {
174
- refSchemas.add(reqSchemaRef.name);
175
- const media = {
176
- schema: { $ref: `#/components/schemas/${reqSchemaRef.name}` }
177
- };
178
- if (route.meta?.example !== void 0) media.example = route.meta.example;
213
+ } else if (method === "post" || method === "put") {
214
+ const modelName = inferModelFromPath(route.pattern);
215
+ const schemaKey = modelName ? ctx.tableToSchema.get(modelName) : void 0;
216
+ if (schemaKey) {
217
+ const sref = `#/components/schemas/${schemaKey}`;
179
218
  operation.requestBody = {
180
- content: { [reqSchemaRef.contentType]: media }
219
+ required: true,
220
+ content: { "application/json": mediaWithExample({ $ref: sref }, route.meta?.example) }
181
221
  };
182
- } else if (method === "post" || method === "put") {
183
- const modelName = inferModelFromPath(route.pattern);
184
- const schemaKey = modelName ? tableToSchema.get(modelName) : void 0;
185
- if (schemaKey) {
186
- const sref = `#/components/schemas/${schemaKey}`;
187
- const media = { schema: { $ref: sref } };
188
- if (route.meta?.example !== void 0) media.example = route.meta.example;
189
- operation.requestBody = {
190
- required: true,
191
- content: { "application/json": media }
192
- };
193
- if (route.meta?.responses === void 0) {
194
- operation.responses = {
195
- "200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
196
- };
197
- }
198
- } else if (route.meta?.example !== void 0) {
199
- operation.requestBody = {
200
- content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
222
+ if (route.meta?.responses === void 0) {
223
+ operation.responses = {
224
+ "200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
201
225
  };
202
226
  }
227
+ } else if (route.meta?.example !== void 0) {
228
+ operation.requestBody = {
229
+ content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
230
+ };
203
231
  }
204
- const respSchemas = parseResponseSchemas(route.meta?.responseSchemas);
205
- if (respSchemas.length > 0) {
206
- const responses = operation.responses;
207
- for (const { status, name, isList } of respSchemas) {
208
- refSchemas.add(name);
209
- const sref = `#/components/schemas/${name}`;
210
- const schema = isList ? { type: "array", items: { $ref: sref } } : { $ref: sref };
211
- responses[status] = {
212
- description: status.startsWith("2") ? "Successful response" : "Response",
213
- content: { "application/json": { schema } }
214
- };
215
- }
232
+ }
233
+ }
234
+ function operationResponseSchemas(route, operation, refSchemas) {
235
+ const respSchemas = parseResponseSchemas(route.meta?.responseSchemas);
236
+ if (respSchemas.length > 0) {
237
+ const responses = operation.responses;
238
+ for (const { status, name, isList } of respSchemas) {
239
+ refSchemas.add(name);
240
+ const sref = `#/components/schemas/${name}`;
241
+ const schema = isList ? { type: "array", items: { $ref: sref } } : { $ref: sref };
242
+ responses[status] = {
243
+ description: status.startsWith("2") ? "Successful response" : "Response",
244
+ content: { "application/json": { schema } }
245
+ };
216
246
  }
217
- const hasExplicitSecurity = route.meta?.security !== void 0 || route.meta?.scopes !== void 0 && route.meta.scopes.length > 0;
218
- if (hasExplicitSecurity) {
219
- const normalized = normalizeSecurity(route.meta?.security, route.meta?.scopes);
220
- operation.security = normalized.length > 0 ? sanitizeSecurity(normalized, schemes) : [];
221
- if (normalized.length > 0) {
222
- const responses = operation.responses;
223
- if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
224
- }
225
- } else if (routeRequiresAuth(route, method)) {
226
- const requirements = [{ [defaultScheme]: [] }];
227
- if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
228
- operation.security = sanitizeSecurity(requirements, schemes);
247
+ }
248
+ }
249
+ function operationSecurity(route, method, operation, schemes, defaultScheme) {
250
+ const hasExplicitSecurity = route.meta?.security !== void 0 || route.meta?.scopes !== void 0 && route.meta.scopes.length > 0;
251
+ if (hasExplicitSecurity) {
252
+ const normalized = normalizeSecurity(route.meta?.security, route.meta?.scopes);
253
+ operation.security = normalized.length > 0 ? sanitizeSecurity(normalized, schemes) : [];
254
+ if (normalized.length > 0) {
229
255
  const responses = operation.responses;
230
256
  if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
231
257
  }
232
- spec.paths[openApiPath][method] = operation;
258
+ } else if (routeRequiresAuth(route, method)) {
259
+ const requirements = [{ [defaultScheme]: [] }];
260
+ if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
261
+ operation.security = sanitizeSecurity(requirements, schemes);
262
+ const responses = operation.responses;
263
+ if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
233
264
  }
265
+ }
266
+ function buildRefSchemas(spec, refSchemas) {
234
267
  if (refSchemas.size > 0) {
235
268
  const schemas = spec.components.schemas;
236
269
  for (const name of refSchemas) {
@@ -239,10 +272,11 @@ function generate(routes, models = []) {
239
272
  }
240
273
  }
241
274
  }
275
+ }
276
+ function buildTags(spec, usedTags) {
242
277
  if (usedTags.length > 0) {
243
278
  spec.tags = usedTags.map((name) => ({ name }));
244
279
  }
245
- return spec;
246
280
  }
247
281
  function routeRequiresAuth(route, method) {
248
282
  if (route.noAuth) return false;