adorn-api 1.0.11 → 1.0.12
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/README.md +318 -620
- package/dist/adapter/express/auth.d.ts +5 -0
- package/dist/adapter/express/auth.d.ts.map +1 -0
- package/dist/adapter/express/coercion.d.ts +22 -0
- package/dist/adapter/express/coercion.d.ts.map +1 -0
- package/dist/adapter/express/index.d.ts +3 -50
- package/dist/adapter/express/index.d.ts.map +1 -1
- package/dist/adapter/express/merge.d.ts +0 -3
- package/dist/adapter/express/merge.d.ts.map +1 -1
- package/dist/adapter/express/openapi.d.ts +11 -0
- package/dist/adapter/express/openapi.d.ts.map +1 -0
- package/dist/adapter/express/router.d.ts +4 -0
- package/dist/adapter/express/router.d.ts.map +1 -0
- package/dist/adapter/express/swagger.d.ts +4 -0
- package/dist/adapter/express/swagger.d.ts.map +1 -0
- package/dist/adapter/express/types.d.ts +64 -0
- package/dist/adapter/express/types.d.ts.map +1 -0
- package/dist/adapter/express/validation.d.ts +10 -0
- package/dist/adapter/express/validation.d.ts.map +1 -0
- package/dist/cli.cjs +332 -153
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +331 -152
- package/dist/cli.js.map +1 -1
- package/dist/compiler/analyze/scanControllers.d.ts +0 -1
- package/dist/compiler/analyze/scanControllers.d.ts.map +1 -1
- package/dist/compiler/manifest/emit.d.ts.map +1 -1
- package/dist/compiler/manifest/format.d.ts +1 -1
- package/dist/compiler/manifest/format.d.ts.map +1 -1
- package/dist/compiler/schema/openapi.d.ts.map +1 -1
- package/dist/compiler/schema/typeToJsonSchema.d.ts +7 -1
- package/dist/compiler/schema/typeToJsonSchema.d.ts.map +1 -1
- package/dist/decorators/index.d.ts +0 -1
- package/dist/decorators/index.d.ts.map +1 -1
- package/dist/express.cjs +619 -596
- package/dist/express.cjs.map +1 -1
- package/dist/express.js +616 -593
- package/dist/express.js.map +1 -1
- package/dist/http.d.ts +1 -9
- package/dist/http.d.ts.map +1 -1
- package/dist/index.cjs +2 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -33
- package/dist/index.js.map +1 -1
- package/dist/metal/applyListQuery.d.ts +27 -0
- package/dist/metal/applyListQuery.d.ts.map +1 -0
- package/dist/metal/index.cjs +59 -0
- package/dist/metal/index.cjs.map +1 -1
- package/dist/metal/index.d.ts +4 -0
- package/dist/metal/index.d.ts.map +1 -1
- package/dist/metal/index.js +55 -0
- package/dist/metal/index.js.map +1 -1
- package/dist/metal/listQuery.d.ts +7 -0
- package/dist/metal/listQuery.d.ts.map +1 -0
- package/dist/metal/queryOptions.d.ts +8 -0
- package/dist/metal/queryOptions.d.ts.map +1 -0
- package/dist/runtime/metadata/types.d.ts +0 -3
- package/dist/runtime/metadata/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/compiler/analyze/extractQueryStyle.d.ts +0 -8
- package/dist/compiler/analyze/extractQueryStyle.d.ts.map +0 -1
- package/dist/decorators/Paginated.d.ts +0 -5
- package/dist/decorators/Paginated.d.ts.map +0 -1
package/dist/express.js
CHANGED
|
@@ -8,10 +8,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
8
8
|
// src/runtime/polyfill.ts
|
|
9
9
|
Symbol.metadata ??= /* @__PURE__ */ Symbol("Symbol.metadata");
|
|
10
10
|
|
|
11
|
-
// src/adapter/express/
|
|
11
|
+
// src/adapter/express/router.ts
|
|
12
12
|
import { Router } from "express";
|
|
13
|
-
import {
|
|
14
|
-
import { resolve, join, isAbsolute } from "path";
|
|
13
|
+
import { join } from "path";
|
|
15
14
|
|
|
16
15
|
// src/runtime/metadata/key.ts
|
|
17
16
|
var ADORN_META = /* @__PURE__ */ Symbol.for("adorn-api/meta");
|
|
@@ -122,8 +121,7 @@ function computeBoundRoutes(controllers, manifest) {
|
|
|
122
121
|
responses: manifestOp.responses,
|
|
123
122
|
use: routeOp.use,
|
|
124
123
|
auth: routeOp.auth,
|
|
125
|
-
controllerUse: bucket.controllerUse
|
|
126
|
-
paginationConfig: routeOp.pagination
|
|
124
|
+
controllerUse: bucket.controllerUse
|
|
127
125
|
});
|
|
128
126
|
}
|
|
129
127
|
}
|
|
@@ -232,101 +230,62 @@ async function loadArtifacts(options) {
|
|
|
232
230
|
};
|
|
233
231
|
}
|
|
234
232
|
|
|
235
|
-
// src/adapter/express/
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
middleware,
|
|
253
|
-
auth,
|
|
254
|
-
coerce
|
|
255
|
-
} = options;
|
|
256
|
-
if (controllers.length === 0) {
|
|
257
|
-
reject(new Error("At least one controller must be provided to bootstrap()."));
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const envPort = process.env.PORT;
|
|
261
|
-
const port = userPort ?? (envPort !== void 0 ? Number(envPort) : 3e3);
|
|
262
|
-
const host = userHost ?? process.env.HOST ?? "0.0.0.0";
|
|
263
|
-
if (isNaN(port) || port < 0 || port > 65535) {
|
|
264
|
-
reject(new Error(`Invalid port: ${port}. Port must be between 0 and 65535.`));
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
const absoluteArtifactsDir = path2.isAbsolute(userArtifactsDir) ? userArtifactsDir : path2.resolve(process.cwd(), userArtifactsDir);
|
|
268
|
-
const app = express();
|
|
269
|
-
app.use(express.json());
|
|
270
|
-
const router = await createExpressRouter({
|
|
271
|
-
controllers,
|
|
272
|
-
artifactsDir: absoluteArtifactsDir,
|
|
273
|
-
middleware,
|
|
274
|
-
auth,
|
|
275
|
-
coerce
|
|
276
|
-
});
|
|
277
|
-
app.use(router);
|
|
278
|
-
if (enableSwagger) {
|
|
279
|
-
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
280
|
-
const serverUrl = `http://${displayHost}:${port}`;
|
|
281
|
-
app.use(
|
|
282
|
-
setupSwagger({
|
|
283
|
-
artifactsDir: absoluteArtifactsDir,
|
|
284
|
-
jsonPath: swaggerJsonPath,
|
|
285
|
-
uiPath: swaggerPath,
|
|
286
|
-
swaggerOptions: {
|
|
287
|
-
servers: [{ url: serverUrl }]
|
|
288
|
-
}
|
|
289
|
-
})
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
const server = app.listen(port, host, () => {
|
|
293
|
-
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
294
|
-
const serverUrl = `http://${displayHost}:${port}`;
|
|
295
|
-
console.log(`\u{1F680} Server running on ${serverUrl}`);
|
|
296
|
-
if (enableSwagger) {
|
|
297
|
-
console.log(`\u{1F4DA} Swagger UI: ${serverUrl}${swaggerPath}`);
|
|
298
|
-
}
|
|
299
|
-
const result = {
|
|
300
|
-
server,
|
|
301
|
-
app,
|
|
302
|
-
url: serverUrl,
|
|
303
|
-
port,
|
|
304
|
-
host,
|
|
305
|
-
close: () => new Promise((closeResolve) => {
|
|
306
|
-
server.close(() => {
|
|
307
|
-
console.log("Server closed gracefully");
|
|
308
|
-
closeResolve();
|
|
309
|
-
});
|
|
310
|
-
})
|
|
311
|
-
};
|
|
312
|
-
resolve2(result);
|
|
313
|
-
});
|
|
314
|
-
server.on("error", (error) => {
|
|
315
|
-
if (error.code === "EADDRINUSE") {
|
|
316
|
-
reject(new Error(`Port ${port} already in use. Please choose a different port.`));
|
|
317
|
-
} else if (error.code === "EACCES") {
|
|
318
|
-
reject(new Error(`Permission denied for port ${port}. Ports below 1024 require root privileges.`));
|
|
319
|
-
} else {
|
|
320
|
-
reject(new Error(`Failed to start server: ${error.message}`));
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
} catch (error) {
|
|
324
|
-
reject(error);
|
|
233
|
+
// src/adapter/express/openapi.ts
|
|
234
|
+
function toOpenApiPath(path3) {
|
|
235
|
+
return path3.replace(/:([^/]+)/g, "{$1}");
|
|
236
|
+
}
|
|
237
|
+
function getOpenApiOperation(openapi, route) {
|
|
238
|
+
const pathKey = toOpenApiPath(route.fullPath);
|
|
239
|
+
const pathItem = openapi.paths?.[pathKey];
|
|
240
|
+
if (!pathItem) return null;
|
|
241
|
+
return pathItem[route.httpMethod.toLowerCase()] ?? null;
|
|
242
|
+
}
|
|
243
|
+
function getParamSchemaIndex(operation) {
|
|
244
|
+
const index = /* @__PURE__ */ new Map();
|
|
245
|
+
const params = operation?.parameters ?? [];
|
|
246
|
+
for (const param of params) {
|
|
247
|
+
if (!param?.name || !param?.in) continue;
|
|
248
|
+
if (param.schema) {
|
|
249
|
+
index.set(`${param.in}:${param.name}`, param.schema);
|
|
325
250
|
}
|
|
326
|
-
}
|
|
251
|
+
}
|
|
252
|
+
return index;
|
|
253
|
+
}
|
|
254
|
+
function getParamSchemaFromIndex(index, location, name) {
|
|
255
|
+
return index.get(`${location}:${name}`) ?? null;
|
|
256
|
+
}
|
|
257
|
+
function getRequestBodySchema(operation, contentType) {
|
|
258
|
+
const content = operation?.requestBody?.content;
|
|
259
|
+
if (!content) return null;
|
|
260
|
+
if (contentType && content[contentType]?.schema) {
|
|
261
|
+
return content[contentType].schema;
|
|
262
|
+
}
|
|
263
|
+
const first = Object.values(content)[0];
|
|
264
|
+
return first?.schema ?? null;
|
|
265
|
+
}
|
|
266
|
+
function schemaFromType(schemaType) {
|
|
267
|
+
if (!schemaType) return null;
|
|
268
|
+
return { type: schemaType };
|
|
269
|
+
}
|
|
270
|
+
function resolveSchema(schema, components, seen = /* @__PURE__ */ new Set()) {
|
|
271
|
+
const ref = schema.$ref;
|
|
272
|
+
if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
|
|
273
|
+
return schema;
|
|
274
|
+
}
|
|
275
|
+
const name = ref.replace("#/components/schemas/", "");
|
|
276
|
+
if (seen.has(name)) return schema;
|
|
277
|
+
const next = components[name];
|
|
278
|
+
if (!next) return schema;
|
|
279
|
+
seen.add(name);
|
|
280
|
+
return resolveSchema(next, components, seen);
|
|
281
|
+
}
|
|
282
|
+
function getSchemaByRef(openapi, ref) {
|
|
283
|
+
if (!ref.startsWith("#/components/schemas/")) return null;
|
|
284
|
+
const schemaName = ref.replace("#/components/schemas/", "");
|
|
285
|
+
return openapi.components.schemas[schemaName] || null;
|
|
327
286
|
}
|
|
328
287
|
|
|
329
|
-
// src/adapter/express/
|
|
288
|
+
// src/adapter/express/coercion.ts
|
|
330
289
|
function normalizeCoerceOptions(coerce) {
|
|
331
290
|
return {
|
|
332
291
|
body: coerce?.body ?? false,
|
|
@@ -338,281 +297,270 @@ function normalizeCoerceOptions(coerce) {
|
|
|
338
297
|
date: coerce?.date ?? false
|
|
339
298
|
};
|
|
340
299
|
}
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
300
|
+
function getDateCoercionOptions(coerce, location) {
|
|
301
|
+
const enabled = coerce[location];
|
|
302
|
+
return {
|
|
303
|
+
dateTime: enabled && coerce.dateTime,
|
|
304
|
+
date: enabled && coerce.date
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function coerceDatesWithSchema(value, schema, dateCoercion, components) {
|
|
308
|
+
if (!schema || !dateCoercion.date && !dateCoercion.dateTime) return value;
|
|
309
|
+
return coerceWithSchema(value, schema, dateCoercion, components, { coercePrimitives: false });
|
|
310
|
+
}
|
|
311
|
+
function coerceParamValue(value, schema, dateCoercion, components) {
|
|
312
|
+
if (!schema) return value;
|
|
313
|
+
return coerceWithSchema(value, schema, dateCoercion, components, { coercePrimitives: true });
|
|
314
|
+
}
|
|
315
|
+
function coerceWithSchema(value, schema, dateCoercion, components, options) {
|
|
316
|
+
if (value === void 0 || value === null) return value;
|
|
317
|
+
if (value instanceof Date) return value;
|
|
318
|
+
const resolved = resolveSchema(schema, components);
|
|
319
|
+
const override = resolved["x-adorn-coerce"];
|
|
320
|
+
const allowDateTime = override === true ? true : override === false ? false : dateCoercion.dateTime;
|
|
321
|
+
const allowDate = override === true ? true : override === false ? false : dateCoercion.date;
|
|
322
|
+
const byFormat = coerceDateString(value, resolved, allowDateTime, allowDate);
|
|
323
|
+
if (byFormat !== value) return byFormat;
|
|
324
|
+
const allOf = resolved.allOf;
|
|
325
|
+
if (Array.isArray(allOf)) {
|
|
326
|
+
let out = value;
|
|
327
|
+
for (const entry of allOf) {
|
|
328
|
+
out = coerceWithSchema(out, entry, { dateTime: allowDateTime, date: allowDate }, components, options);
|
|
356
329
|
}
|
|
357
|
-
|
|
358
|
-
const artifacts = await loadArtifacts({ outDir: artifactsDir });
|
|
359
|
-
manifest = artifacts.manifest;
|
|
360
|
-
openapi = artifacts.openapi;
|
|
361
|
-
precompiledValidators = artifacts.validators?.validators ?? null;
|
|
330
|
+
return out;
|
|
362
331
|
}
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
function getInstance(Ctor) {
|
|
369
|
-
if (!instanceCache.has(Ctor)) {
|
|
370
|
-
instanceCache.set(Ctor, new Ctor());
|
|
332
|
+
const variants = resolved.oneOf ?? resolved.anyOf;
|
|
333
|
+
if (Array.isArray(variants)) {
|
|
334
|
+
for (const entry of variants) {
|
|
335
|
+
const out = coerceWithSchema(value, entry, { dateTime: allowDateTime, date: allowDate }, components, options);
|
|
336
|
+
if (out !== value) return out;
|
|
371
337
|
}
|
|
372
|
-
return instanceCache.get(Ctor);
|
|
373
338
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
throw new Error(`Named middleware "${item}" not found in middleware registry`);
|
|
380
|
-
}
|
|
381
|
-
return fn;
|
|
382
|
-
}
|
|
383
|
-
return item;
|
|
384
|
-
});
|
|
339
|
+
const schemaType = resolved.type;
|
|
340
|
+
const types = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
|
|
341
|
+
if ((types.includes("array") || resolved.items) && Array.isArray(value)) {
|
|
342
|
+
const itemSchema = resolved.items ?? {};
|
|
343
|
+
return value.map((item) => coerceWithSchema(item, itemSchema, { dateTime: allowDateTime, date: allowDate }, components, options));
|
|
385
344
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
if (isPublic) {
|
|
395
|
-
return next();
|
|
396
|
-
}
|
|
397
|
-
const authMeta = routeAuth;
|
|
398
|
-
const scheme = authMeta.scheme;
|
|
399
|
-
const requiredScopes = authMeta.scopes || [];
|
|
400
|
-
const isOptional = authMeta.optional ?? false;
|
|
401
|
-
const authRuntime = authConfig.schemes[scheme];
|
|
402
|
-
if (!authRuntime) {
|
|
403
|
-
throw new Error(`Auth scheme "${scheme}" not found in auth configuration`);
|
|
404
|
-
}
|
|
405
|
-
const result = await authRuntime.authenticate(req);
|
|
406
|
-
if (!result) {
|
|
407
|
-
if (isOptional) {
|
|
408
|
-
req.auth = null;
|
|
409
|
-
return next();
|
|
345
|
+
if ((types.includes("object") || resolved.properties || resolved.additionalProperties) && isPlainObject(value)) {
|
|
346
|
+
const props = resolved.properties;
|
|
347
|
+
const out = { ...value };
|
|
348
|
+
if (props) {
|
|
349
|
+
for (const [key, propSchema] of Object.entries(props)) {
|
|
350
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
351
|
+
out[key] = coerceWithSchema(value[key], propSchema, { dateTime: allowDateTime, date: allowDate }, components, options);
|
|
410
352
|
}
|
|
411
|
-
return authRuntime.challenge(res);
|
|
412
353
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
354
|
+
}
|
|
355
|
+
const additional = resolved.additionalProperties;
|
|
356
|
+
if (additional && typeof additional === "object") {
|
|
357
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
358
|
+
if (props && Object.prototype.hasOwnProperty.call(props, key)) continue;
|
|
359
|
+
out[key] = coerceWithSchema(entry, additional, { dateTime: allowDateTime, date: allowDate }, components, options);
|
|
419
360
|
}
|
|
420
|
-
|
|
421
|
-
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
422
363
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const schemaName = ref.replace("#/components/schemas/", "");
|
|
426
|
-
return openapi.components.schemas[schemaName] || null;
|
|
364
|
+
if (options.coercePrimitives) {
|
|
365
|
+
return coercePrimitiveValue(value, types);
|
|
427
366
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
middlewareChain.push(...resolveMiddleware(middleware.global, middleware.named || {}));
|
|
441
|
-
}
|
|
442
|
-
if (route.controllerUse) {
|
|
443
|
-
middlewareChain.push(...resolveMiddleware(route.controllerUse, middleware.named || {}));
|
|
444
|
-
}
|
|
445
|
-
if (route.use) {
|
|
446
|
-
middlewareChain.push(...resolveMiddleware(route.use, middleware.named || {}));
|
|
367
|
+
return value;
|
|
368
|
+
}
|
|
369
|
+
function coerceDateString(value, schema, allowDateTime, allowDate) {
|
|
370
|
+
if (typeof value !== "string") return value;
|
|
371
|
+
const format = schema.format;
|
|
372
|
+
const schemaType = schema.type;
|
|
373
|
+
const types = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
|
|
374
|
+
const allowsString = types.length === 0 || types.includes("string");
|
|
375
|
+
if (format === "date-time" && allowDateTime && allowsString) {
|
|
376
|
+
const parsed = new Date(value);
|
|
377
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
378
|
+
throw new Error(`Invalid date-time: ${value}`);
|
|
447
379
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
380
|
+
return parsed;
|
|
381
|
+
}
|
|
382
|
+
if (format === "date" && allowDate && allowsString) {
|
|
383
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
384
|
+
throw new Error(`Invalid date: ${value}`);
|
|
451
385
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
const instance = getInstance(route.controllerCtor);
|
|
459
|
-
const handler = instance[route.methodName];
|
|
460
|
-
if (typeof handler !== "function") {
|
|
461
|
-
throw new Error(`Method ${route.methodName} not found on controller`);
|
|
462
|
-
}
|
|
463
|
-
const args = [];
|
|
464
|
-
if (route.args.body) {
|
|
465
|
-
const coercedBody = (coerceBodyDates.date || coerceBodyDates.dateTime) && bodySchema ? coerceDatesWithSchema(req.body, bodySchema, coerceBodyDates, openapi.components.schemas) : req.body;
|
|
466
|
-
args[route.args.body.index] = coercedBody;
|
|
467
|
-
}
|
|
468
|
-
for (const pathArg of route.args.path) {
|
|
469
|
-
const rawValue = req.params[pathArg.name];
|
|
470
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "path", pathArg.name) ?? (pathArg.schemaRef ? getSchemaByRef(pathArg.schemaRef) : null) ?? schemaFromType(pathArg.schemaType);
|
|
471
|
-
const coerced = coerceParamValue(rawValue, paramSchema, coercePathDates, openapi.components.schemas);
|
|
472
|
-
args[pathArg.index] = coerced;
|
|
473
|
-
}
|
|
474
|
-
if (route.args.query.length > 0) {
|
|
475
|
-
const deepObjectArgs = route.args.query.filter((q) => q.serialization?.style === "deepObject");
|
|
476
|
-
const standardArgs = route.args.query.filter((q) => q.serialization?.style !== "deepObject");
|
|
477
|
-
if (deepObjectArgs.length > 0) {
|
|
478
|
-
const rawQuery = getRawQueryString(req);
|
|
479
|
-
const names = new Set(deepObjectArgs.map((q) => q.name));
|
|
480
|
-
const parsedDeep = parseDeepObjectParams(rawQuery, names);
|
|
481
|
-
for (const q of deepObjectArgs) {
|
|
482
|
-
const rawValue = parsedDeep[q.name];
|
|
483
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
|
|
484
|
-
const baseValue = rawValue === void 0 ? {} : rawValue;
|
|
485
|
-
const coerced = coerceParamValue(baseValue, paramSchema, coerceQueryDates, openapi.components.schemas);
|
|
486
|
-
args[q.index] = coerced;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
if (standardArgs.length > 0) {
|
|
490
|
-
const firstQueryIndex = standardArgs[0].index;
|
|
491
|
-
const allSameIndex = standardArgs.every((q) => q.index === firstQueryIndex);
|
|
492
|
-
if (allSameIndex) {
|
|
493
|
-
args[firstQueryIndex] = {};
|
|
494
|
-
for (const q of standardArgs) {
|
|
495
|
-
const parsed = parseQueryValue(req.query[q.name], q);
|
|
496
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
|
|
497
|
-
const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
|
|
498
|
-
args[firstQueryIndex][q.name] = coerced;
|
|
499
|
-
}
|
|
500
|
-
} else {
|
|
501
|
-
for (const q of standardArgs) {
|
|
502
|
-
const parsed = parseQueryValue(req.query[q.name], q);
|
|
503
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
|
|
504
|
-
const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
|
|
505
|
-
args[q.index] = coerced;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
if (route.args.paginationParamIndex !== null) {
|
|
511
|
-
const pageStr = req.query.page;
|
|
512
|
-
const pageSizeStr = req.query.pageSize;
|
|
513
|
-
const defaultPageSize = route.paginationConfig?.defaultPageSize ?? 10;
|
|
514
|
-
const page = pageStr ? parseInt(pageStr, 10) : 1;
|
|
515
|
-
const pageSize = pageSizeStr ? parseInt(pageSizeStr, 10) : defaultPageSize;
|
|
516
|
-
args[route.args.paginationParamIndex] = { page, pageSize };
|
|
517
|
-
}
|
|
518
|
-
if (route.args.headers.length > 0) {
|
|
519
|
-
const firstHeaderIndex = route.args.headers[0].index;
|
|
520
|
-
const allSameIndex = route.args.headers.every((h) => h.index === firstHeaderIndex);
|
|
521
|
-
if (allSameIndex) {
|
|
522
|
-
args[firstHeaderIndex] = {};
|
|
523
|
-
for (const h of route.args.headers) {
|
|
524
|
-
const headerValue = req.headers[h.name.toLowerCase()];
|
|
525
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(h.schemaRef) : null) ?? schemaFromType(h.schemaType);
|
|
526
|
-
const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
|
|
527
|
-
args[firstHeaderIndex][h.name] = coerced ?? void 0;
|
|
528
|
-
}
|
|
529
|
-
} else {
|
|
530
|
-
for (const h of route.args.headers) {
|
|
531
|
-
const headerValue = req.headers[h.name.toLowerCase()];
|
|
532
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(h.schemaRef) : null) ?? schemaFromType(h.schemaType);
|
|
533
|
-
const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
|
|
534
|
-
args[h.index] = coerced ?? void 0;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
if (route.args.cookies.length > 0) {
|
|
539
|
-
const firstCookieIndex = route.args.cookies[0].index;
|
|
540
|
-
const allSameIndex = route.args.cookies.every((c) => c.index === firstCookieIndex);
|
|
541
|
-
const cookies = parseCookies(req.headers.cookie);
|
|
542
|
-
if (allSameIndex) {
|
|
543
|
-
args[firstCookieIndex] = {};
|
|
544
|
-
for (const c of route.args.cookies) {
|
|
545
|
-
const cookieValue = cookies[c.name];
|
|
546
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(c.schemaRef) : null) ?? schemaFromType(c.schemaType);
|
|
547
|
-
const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
|
|
548
|
-
args[firstCookieIndex][c.name] = coerced;
|
|
549
|
-
}
|
|
550
|
-
} else {
|
|
551
|
-
for (const c of route.args.cookies) {
|
|
552
|
-
const cookieValue = cookies[c.name];
|
|
553
|
-
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(c.schemaRef) : null) ?? schemaFromType(c.schemaType);
|
|
554
|
-
const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
|
|
555
|
-
args[c.index] = coerced;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
if (args.length === 0) {
|
|
560
|
-
args.push(req);
|
|
561
|
-
}
|
|
562
|
-
const result = await handler.apply(instance, args);
|
|
563
|
-
const primaryResponse = route.responses[0];
|
|
564
|
-
const status = primaryResponse?.status ?? 200;
|
|
565
|
-
res.status(status).json(result);
|
|
566
|
-
} catch (error) {
|
|
567
|
-
next(error);
|
|
568
|
-
}
|
|
569
|
-
});
|
|
386
|
+
const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
387
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
388
|
+
throw new Error(`Invalid date: ${value}`);
|
|
389
|
+
}
|
|
390
|
+
return parsed;
|
|
570
391
|
}
|
|
571
|
-
return
|
|
392
|
+
return value;
|
|
572
393
|
}
|
|
573
|
-
function
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
394
|
+
function coercePrimitiveValue(value, types) {
|
|
395
|
+
if (value === void 0 || value === null) return value;
|
|
396
|
+
if (types.includes("number") || types.includes("integer")) {
|
|
397
|
+
const num = Number(value);
|
|
398
|
+
if (Number.isNaN(num)) {
|
|
399
|
+
throw new Error(`Invalid number: ${value}`);
|
|
400
|
+
}
|
|
401
|
+
return num;
|
|
402
|
+
}
|
|
403
|
+
if (types.includes("boolean")) {
|
|
404
|
+
if (value === "true") return true;
|
|
405
|
+
if (value === "false") return false;
|
|
406
|
+
if (typeof value === "boolean") return value;
|
|
407
|
+
throw new Error(`Invalid boolean: ${value}`);
|
|
408
|
+
}
|
|
409
|
+
return value;
|
|
579
410
|
}
|
|
580
|
-
function
|
|
581
|
-
|
|
411
|
+
function isPlainObject(value) {
|
|
412
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
413
|
+
const proto = Object.getPrototypeOf(value);
|
|
414
|
+
return proto === Object.prototype || proto === null;
|
|
582
415
|
}
|
|
583
|
-
function
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
if (!
|
|
587
|
-
|
|
416
|
+
function parseQueryValue(value, param) {
|
|
417
|
+
if (value === void 0 || value === null) return value;
|
|
418
|
+
const isArray = Array.isArray(param.schemaType) ? param.schemaType.includes("array") : param.schemaType === "array";
|
|
419
|
+
if (!isArray) return value;
|
|
420
|
+
const style = param.serialization?.style ?? "form";
|
|
421
|
+
const explode = param.serialization?.explode ?? true;
|
|
422
|
+
if (Array.isArray(value)) {
|
|
423
|
+
return value;
|
|
424
|
+
}
|
|
425
|
+
if (style === "form") {
|
|
426
|
+
if (explode) {
|
|
427
|
+
return value;
|
|
428
|
+
}
|
|
429
|
+
return value.split(",");
|
|
430
|
+
}
|
|
431
|
+
if (style === "spaceDelimited") {
|
|
432
|
+
return value.split(" ");
|
|
433
|
+
}
|
|
434
|
+
if (style === "pipeDelimited") {
|
|
435
|
+
return value.split("|");
|
|
436
|
+
}
|
|
437
|
+
return value;
|
|
588
438
|
}
|
|
589
|
-
function
|
|
590
|
-
const
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
439
|
+
function getRawQueryString(req) {
|
|
440
|
+
const url = req.originalUrl ?? req.url ?? "";
|
|
441
|
+
const index = url.indexOf("?");
|
|
442
|
+
if (index === -1) return "";
|
|
443
|
+
return url.slice(index + 1);
|
|
444
|
+
}
|
|
445
|
+
function parseDeepObjectParams(rawQuery, names) {
|
|
446
|
+
const out = {};
|
|
447
|
+
if (!rawQuery || names.size === 0) return out;
|
|
448
|
+
const params = new URLSearchParams(rawQuery);
|
|
449
|
+
for (const [key, value] of params.entries()) {
|
|
450
|
+
const path3 = parseBracketPath(key);
|
|
451
|
+
if (path3.length === 0) continue;
|
|
452
|
+
const root = path3[0];
|
|
453
|
+
if (!names.has(root)) continue;
|
|
454
|
+
assignDeepValue(out, path3, value);
|
|
455
|
+
}
|
|
456
|
+
return out;
|
|
457
|
+
}
|
|
458
|
+
function parseBracketPath(key) {
|
|
459
|
+
const parts = [];
|
|
460
|
+
let current = "";
|
|
461
|
+
for (let i = 0; i < key.length; i++) {
|
|
462
|
+
const ch = key[i];
|
|
463
|
+
if (ch === "[") {
|
|
464
|
+
if (current) parts.push(current);
|
|
465
|
+
current = "";
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (ch === "]") {
|
|
469
|
+
if (current) parts.push(current);
|
|
470
|
+
current = "";
|
|
471
|
+
continue;
|
|
596
472
|
}
|
|
473
|
+
current += ch;
|
|
597
474
|
}
|
|
598
|
-
|
|
475
|
+
if (current) parts.push(current);
|
|
476
|
+
return parts;
|
|
599
477
|
}
|
|
600
|
-
function
|
|
601
|
-
|
|
478
|
+
function assignDeepValue(target, path3, value) {
|
|
479
|
+
let cursor = target;
|
|
480
|
+
for (let i = 0; i < path3.length; i++) {
|
|
481
|
+
const key = path3[i];
|
|
482
|
+
if (!key) continue;
|
|
483
|
+
const isLast = i === path3.length - 1;
|
|
484
|
+
if (isLast) {
|
|
485
|
+
const existing = cursor[key];
|
|
486
|
+
if (existing === void 0) {
|
|
487
|
+
cursor[key] = value;
|
|
488
|
+
} else if (Array.isArray(existing)) {
|
|
489
|
+
existing.push(value);
|
|
490
|
+
} else {
|
|
491
|
+
cursor[key] = [existing, value];
|
|
492
|
+
}
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const next = cursor[key];
|
|
496
|
+
if (!isPlainObject(next)) {
|
|
497
|
+
cursor[key] = {};
|
|
498
|
+
}
|
|
499
|
+
cursor = cursor[key];
|
|
500
|
+
}
|
|
602
501
|
}
|
|
603
|
-
function
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
502
|
+
function parseCookies(cookieHeader) {
|
|
503
|
+
if (!cookieHeader) return {};
|
|
504
|
+
const cookies = {};
|
|
505
|
+
const pairs = cookieHeader.split(";");
|
|
506
|
+
for (const pair of pairs) {
|
|
507
|
+
const [name, ...valueParts] = pair.trim().split("=");
|
|
508
|
+
if (name) {
|
|
509
|
+
cookies[name] = valueParts.join("=");
|
|
510
|
+
}
|
|
608
511
|
}
|
|
609
|
-
|
|
610
|
-
return first?.schema ?? null;
|
|
512
|
+
return cookies;
|
|
611
513
|
}
|
|
612
|
-
function
|
|
613
|
-
if (
|
|
614
|
-
|
|
514
|
+
function normalizeSort(sort) {
|
|
515
|
+
if (Array.isArray(sort)) {
|
|
516
|
+
return sort.map((s) => String(s));
|
|
517
|
+
}
|
|
518
|
+
if (typeof sort === "string") {
|
|
519
|
+
return sort.split(",").map((s) => s.trim()).filter(Boolean);
|
|
520
|
+
}
|
|
521
|
+
return [];
|
|
615
522
|
}
|
|
523
|
+
|
|
524
|
+
// src/adapter/express/auth.ts
|
|
525
|
+
function createAuthMiddleware(authConfig, routeAuth, globalSecurity) {
|
|
526
|
+
return async (req, res, next) => {
|
|
527
|
+
const isPublic = routeAuth === "public";
|
|
528
|
+
const hasAuthDecorator = routeAuth && routeAuth !== "public";
|
|
529
|
+
const hasGlobalSecurity = globalSecurity && globalSecurity.length > 0;
|
|
530
|
+
if (!hasAuthDecorator && !hasGlobalSecurity) {
|
|
531
|
+
return next();
|
|
532
|
+
}
|
|
533
|
+
if (isPublic) {
|
|
534
|
+
return next();
|
|
535
|
+
}
|
|
536
|
+
const authMeta = routeAuth;
|
|
537
|
+
const scheme = authMeta.scheme;
|
|
538
|
+
const requiredScopes = authMeta.scopes || [];
|
|
539
|
+
const isOptional = authMeta.optional ?? false;
|
|
540
|
+
const authRuntime = authConfig.schemes[scheme];
|
|
541
|
+
if (!authRuntime) {
|
|
542
|
+
throw new Error(`Auth scheme "${scheme}" not found in auth configuration`);
|
|
543
|
+
}
|
|
544
|
+
const result = await authRuntime.authenticate(req);
|
|
545
|
+
if (!result) {
|
|
546
|
+
if (isOptional) {
|
|
547
|
+
req.auth = null;
|
|
548
|
+
return next();
|
|
549
|
+
}
|
|
550
|
+
return authRuntime.challenge(res);
|
|
551
|
+
}
|
|
552
|
+
req.auth = result.principal;
|
|
553
|
+
if (authRuntime.authorize && requiredScopes.length > 0) {
|
|
554
|
+
if (!authRuntime.authorize(result, requiredScopes)) {
|
|
555
|
+
res.status(403).json({ error: "Forbidden", message: "Insufficient scopes" });
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
next();
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/adapter/express/validation.ts
|
|
616
564
|
function validateRequestWithPrecompiled(route, req, validators) {
|
|
617
565
|
const errors = [];
|
|
618
566
|
const deepNames = new Set(route.args.query.filter((q) => q.serialization?.style === "deepObject").map((q) => q.name));
|
|
@@ -635,48 +583,31 @@ function validateRequestWithPrecompiled(route, req, validators) {
|
|
|
635
583
|
}
|
|
636
584
|
}
|
|
637
585
|
for (const q of route.args.query) {
|
|
638
|
-
|
|
639
|
-
if (value ===
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
for (const p of route.args.path) {
|
|
652
|
-
const value = req.params[p.name];
|
|
653
|
-
if (value === void 0) continue;
|
|
654
|
-
const schema = schemaFromType(p.schemaType) ?? {};
|
|
655
|
-
const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, {});
|
|
656
|
-
if (Object.keys(schema).length > 0 && coerced !== void 0) {
|
|
657
|
-
errors.push({
|
|
658
|
-
path: `#/path/${p.name}`,
|
|
659
|
-
message: `Schema validation not supported for path params in precompiled mode`,
|
|
660
|
-
keyword: "notSupported",
|
|
661
|
-
params: {}
|
|
662
|
-
});
|
|
586
|
+
let value = q.serialization?.style === "deepObject" ? deepValues[q.name] : req.query[q.name];
|
|
587
|
+
if (q.content === "application/json" && typeof value === "string") {
|
|
588
|
+
try {
|
|
589
|
+
value = JSON.parse(value);
|
|
590
|
+
} catch (e) {
|
|
591
|
+
errors.push({
|
|
592
|
+
path: `#/query/${q.name}`,
|
|
593
|
+
message: `Invalid JSON string`,
|
|
594
|
+
keyword: "json",
|
|
595
|
+
params: {}
|
|
596
|
+
});
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
663
599
|
}
|
|
664
600
|
}
|
|
665
601
|
return errors.length > 0 ? errors : null;
|
|
666
602
|
}
|
|
667
603
|
function validateRequest(route, req, openapi, validator) {
|
|
668
|
-
function getSchemaByRef(ref) {
|
|
669
|
-
if (!ref.startsWith("#/components/schemas/")) return null;
|
|
670
|
-
const schemaName = ref.replace("#/components/schemas/", "");
|
|
671
|
-
return openapi.components.schemas[schemaName] || null;
|
|
672
|
-
}
|
|
673
604
|
const openapiOperation = getOpenApiOperation(openapi, route);
|
|
674
|
-
const paramSchemaIndex =
|
|
605
|
+
const paramSchemaIndex = getParamSchemaIndex(openapiOperation);
|
|
675
606
|
const deepNames = new Set(route.args.query.filter((q) => q.serialization?.style === "deepObject").map((q) => q.name));
|
|
676
607
|
const deepValues = deepNames.size > 0 ? parseDeepObjectParams(getRawQueryString(req), deepNames) : {};
|
|
677
608
|
const errors = [];
|
|
678
609
|
if (route.args.body) {
|
|
679
|
-
const bodySchema = getSchemaByRef(route.args.body.schemaRef);
|
|
610
|
+
const bodySchema = getSchemaByRef(openapi, route.args.body.schemaRef);
|
|
680
611
|
if (bodySchema) {
|
|
681
612
|
const validate = validator.compile(bodySchema);
|
|
682
613
|
const valid = validate(req.body);
|
|
@@ -693,7 +624,20 @@ function validateRequest(route, req, openapi, validator) {
|
|
|
693
624
|
}
|
|
694
625
|
}
|
|
695
626
|
for (const q of route.args.query) {
|
|
696
|
-
|
|
627
|
+
let value = q.serialization?.style === "deepObject" ? deepValues[q.name] : req.query[q.name];
|
|
628
|
+
if (q.content === "application/json" && typeof value === "string") {
|
|
629
|
+
try {
|
|
630
|
+
value = JSON.parse(value);
|
|
631
|
+
} catch (e) {
|
|
632
|
+
errors.push({
|
|
633
|
+
path: `#/query/${q.name}`,
|
|
634
|
+
message: `Invalid JSON string`,
|
|
635
|
+
keyword: "json",
|
|
636
|
+
params: {}
|
|
637
|
+
});
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
697
641
|
const openapiSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name);
|
|
698
642
|
let schema = {};
|
|
699
643
|
if (openapiSchema) {
|
|
@@ -704,7 +648,7 @@ function validateRequest(route, req, openapi, validator) {
|
|
|
704
648
|
schema.type = type;
|
|
705
649
|
}
|
|
706
650
|
if (q.schemaRef && q.schemaRef.includes("Inline")) {
|
|
707
|
-
const inlineSchema = getSchemaByRef(q.schemaRef);
|
|
651
|
+
const inlineSchema = getSchemaByRef(openapi, q.schemaRef);
|
|
708
652
|
if (inlineSchema) {
|
|
709
653
|
Object.assign(schema, inlineSchema);
|
|
710
654
|
}
|
|
@@ -738,7 +682,7 @@ function validateRequest(route, req, openapi, validator) {
|
|
|
738
682
|
schema.type = type;
|
|
739
683
|
}
|
|
740
684
|
if (p.schemaRef && p.schemaRef.includes("Inline")) {
|
|
741
|
-
const inlineSchema = getSchemaByRef(p.schemaRef);
|
|
685
|
+
const inlineSchema = getSchemaByRef(openapi, p.schemaRef);
|
|
742
686
|
if (inlineSchema) {
|
|
743
687
|
Object.assign(schema, inlineSchema);
|
|
744
688
|
}
|
|
@@ -762,225 +706,213 @@ function validateRequest(route, req, openapi, validator) {
|
|
|
762
706
|
}
|
|
763
707
|
return errors.length > 0 ? errors : null;
|
|
764
708
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
}
|
|
781
|
-
function coerceParamValue(value, schema, dateCoercion, components) {
|
|
782
|
-
if (!schema) return value;
|
|
783
|
-
return coerceWithSchema(value, schema, dateCoercion, components, { coercePrimitives: true });
|
|
784
|
-
}
|
|
785
|
-
function coerceWithSchema(value, schema, dateCoercion, components, options) {
|
|
786
|
-
if (value === void 0 || value === null) return value;
|
|
787
|
-
if (value instanceof Date) return value;
|
|
788
|
-
const resolved = resolveSchema(schema, components);
|
|
789
|
-
const override = resolved["x-adorn-coerce"];
|
|
790
|
-
const allowDateTime = override === true ? true : override === false ? false : dateCoercion.dateTime;
|
|
791
|
-
const allowDate = override === true ? true : override === false ? false : dateCoercion.date;
|
|
792
|
-
const byFormat = coerceDateString(value, resolved, allowDateTime, allowDate);
|
|
793
|
-
if (byFormat !== value) return byFormat;
|
|
794
|
-
const allOf = resolved.allOf;
|
|
795
|
-
if (Array.isArray(allOf)) {
|
|
796
|
-
let out = value;
|
|
797
|
-
for (const entry of allOf) {
|
|
798
|
-
out = coerceWithSchema(out, entry, { dateTime: allowDateTime, date: allowDate }, components, options);
|
|
799
|
-
}
|
|
800
|
-
return out;
|
|
801
|
-
}
|
|
802
|
-
const variants = resolved.oneOf ?? resolved.anyOf;
|
|
803
|
-
if (Array.isArray(variants)) {
|
|
804
|
-
for (const entry of variants) {
|
|
805
|
-
const out = coerceWithSchema(value, entry, { dateTime: allowDateTime, date: allowDate }, components, options);
|
|
806
|
-
if (out !== value) return out;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
const schemaType = resolved.type;
|
|
810
|
-
const types = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
|
|
811
|
-
if ((types.includes("array") || resolved.items) && Array.isArray(value)) {
|
|
812
|
-
const itemSchema = resolved.items ?? {};
|
|
813
|
-
return value.map((item) => coerceWithSchema(item, itemSchema, { dateTime: allowDateTime, date: allowDate }, components, options));
|
|
814
|
-
}
|
|
815
|
-
if ((types.includes("object") || resolved.properties || resolved.additionalProperties) && isPlainObject(value)) {
|
|
816
|
-
const props = resolved.properties;
|
|
817
|
-
const out = { ...value };
|
|
818
|
-
if (props) {
|
|
819
|
-
for (const [key, propSchema] of Object.entries(props)) {
|
|
820
|
-
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
821
|
-
out[key] = coerceWithSchema(value[key], propSchema, { dateTime: allowDateTime, date: allowDate }, components, options);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
const additional = resolved.additionalProperties;
|
|
826
|
-
if (additional && typeof additional === "object") {
|
|
827
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
828
|
-
if (props && Object.prototype.hasOwnProperty.call(props, key)) continue;
|
|
829
|
-
out[key] = coerceWithSchema(entry, additional, { dateTime: allowDateTime, date: allowDate }, components, options);
|
|
709
|
+
|
|
710
|
+
// src/adapter/express/router.ts
|
|
711
|
+
async function createExpressRouter(options) {
|
|
712
|
+
const { controllers, artifactsDir = ".adorn", middleware = {}, defaultPageSize = 10 } = options;
|
|
713
|
+
let manifest;
|
|
714
|
+
let openapi;
|
|
715
|
+
let precompiledValidators = null;
|
|
716
|
+
if (options.manifest && options.openapi) {
|
|
717
|
+
manifest = options.manifest;
|
|
718
|
+
openapi = options.openapi;
|
|
719
|
+
if (manifest.validation.mode === "precompiled" && manifest.validation.precompiledModule) {
|
|
720
|
+
try {
|
|
721
|
+
const validatorPath = join(artifactsDir, manifest.validation.precompiledModule);
|
|
722
|
+
precompiledValidators = __require(validatorPath).validators;
|
|
723
|
+
} catch (err) {
|
|
724
|
+
console.warn(`Failed to load precompiled validators: ${err}`);
|
|
830
725
|
}
|
|
831
726
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
const parsed = new Date(value);
|
|
847
|
-
if (Number.isNaN(parsed.getTime())) {
|
|
848
|
-
throw new Error(`Invalid date-time: ${value}`);
|
|
849
|
-
}
|
|
850
|
-
return parsed;
|
|
851
|
-
}
|
|
852
|
-
if (format === "date" && allowDate && allowsString) {
|
|
853
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
854
|
-
throw new Error(`Invalid date: ${value}`);
|
|
855
|
-
}
|
|
856
|
-
const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
857
|
-
if (Number.isNaN(parsed.getTime())) {
|
|
858
|
-
throw new Error(`Invalid date: ${value}`);
|
|
859
|
-
}
|
|
860
|
-
return parsed;
|
|
861
|
-
}
|
|
862
|
-
return value;
|
|
863
|
-
}
|
|
864
|
-
function coercePrimitiveValue(value, types) {
|
|
865
|
-
if (value === void 0 || value === null) return value;
|
|
866
|
-
if (types.includes("number") || types.includes("integer")) {
|
|
867
|
-
const num = Number(value);
|
|
868
|
-
if (Number.isNaN(num)) {
|
|
869
|
-
throw new Error(`Invalid number: ${value}`);
|
|
870
|
-
}
|
|
871
|
-
return num;
|
|
872
|
-
}
|
|
873
|
-
if (types.includes("boolean")) {
|
|
874
|
-
if (value === "true") return true;
|
|
875
|
-
if (value === "false") return false;
|
|
876
|
-
if (typeof value === "boolean") return value;
|
|
877
|
-
throw new Error(`Invalid boolean: ${value}`);
|
|
878
|
-
}
|
|
879
|
-
return value;
|
|
880
|
-
}
|
|
881
|
-
function isPlainObject(value) {
|
|
882
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
883
|
-
const proto = Object.getPrototypeOf(value);
|
|
884
|
-
return proto === Object.prototype || proto === null;
|
|
885
|
-
}
|
|
886
|
-
function parseQueryValue(value, param) {
|
|
887
|
-
if (value === void 0 || value === null) return value;
|
|
888
|
-
const isArray = Array.isArray(param.schemaType) ? param.schemaType.includes("array") : param.schemaType === "array";
|
|
889
|
-
if (!isArray) return value;
|
|
890
|
-
const style = param.serialization?.style ?? "form";
|
|
891
|
-
const explode = param.serialization?.explode ?? true;
|
|
892
|
-
if (Array.isArray(value)) {
|
|
893
|
-
return value;
|
|
894
|
-
}
|
|
895
|
-
if (style === "form") {
|
|
896
|
-
if (explode) {
|
|
897
|
-
return value;
|
|
898
|
-
}
|
|
899
|
-
return value.split(",");
|
|
900
|
-
}
|
|
901
|
-
if (style === "spaceDelimited") {
|
|
902
|
-
return value.split(" ");
|
|
903
|
-
}
|
|
904
|
-
if (style === "pipeDelimited") {
|
|
905
|
-
return value.split("|");
|
|
906
|
-
}
|
|
907
|
-
return value;
|
|
908
|
-
}
|
|
909
|
-
function getRawQueryString(req) {
|
|
910
|
-
const url = req.originalUrl ?? req.url ?? "";
|
|
911
|
-
const index = url.indexOf("?");
|
|
912
|
-
if (index === -1) return "";
|
|
913
|
-
return url.slice(index + 1);
|
|
914
|
-
}
|
|
915
|
-
function parseDeepObjectParams(rawQuery, names) {
|
|
916
|
-
const out = {};
|
|
917
|
-
if (!rawQuery || names.size === 0) return out;
|
|
918
|
-
const params = new URLSearchParams(rawQuery);
|
|
919
|
-
for (const [key, value] of params.entries()) {
|
|
920
|
-
const path3 = parseBracketPath(key);
|
|
921
|
-
if (path3.length === 0) continue;
|
|
922
|
-
const root = path3[0];
|
|
923
|
-
if (!names.has(root)) continue;
|
|
924
|
-
assignDeepValue(out, path3, value);
|
|
925
|
-
}
|
|
926
|
-
return out;
|
|
927
|
-
}
|
|
928
|
-
function parseBracketPath(key) {
|
|
929
|
-
const parts = [];
|
|
930
|
-
let current = "";
|
|
931
|
-
for (let i = 0; i < key.length; i++) {
|
|
932
|
-
const ch = key[i];
|
|
933
|
-
if (ch === "[") {
|
|
934
|
-
if (current) parts.push(current);
|
|
935
|
-
current = "";
|
|
936
|
-
continue;
|
|
937
|
-
}
|
|
938
|
-
if (ch === "]") {
|
|
939
|
-
if (current) parts.push(current);
|
|
940
|
-
current = "";
|
|
941
|
-
continue;
|
|
727
|
+
} else {
|
|
728
|
+
const artifacts = await loadArtifacts({ outDir: artifactsDir });
|
|
729
|
+
manifest = artifacts.manifest;
|
|
730
|
+
openapi = artifacts.openapi;
|
|
731
|
+
precompiledValidators = artifacts.validators?.validators ?? null;
|
|
732
|
+
}
|
|
733
|
+
const routes = bindRoutes({ controllers, manifest });
|
|
734
|
+
const validator = precompiledValidators ? null : createValidator();
|
|
735
|
+
const router = Router();
|
|
736
|
+
const instanceCache = /* @__PURE__ */ new Map();
|
|
737
|
+
const coerce = normalizeCoerceOptions(options.coerce);
|
|
738
|
+
function getInstance(Ctor) {
|
|
739
|
+
if (!instanceCache.has(Ctor)) {
|
|
740
|
+
instanceCache.set(Ctor, new Ctor());
|
|
942
741
|
}
|
|
943
|
-
|
|
742
|
+
return instanceCache.get(Ctor);
|
|
944
743
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
const isLast = i === path3.length - 1;
|
|
954
|
-
if (isLast) {
|
|
955
|
-
const existing = cursor[key];
|
|
956
|
-
if (existing === void 0) {
|
|
957
|
-
cursor[key] = value;
|
|
958
|
-
} else if (Array.isArray(existing)) {
|
|
959
|
-
existing.push(value);
|
|
960
|
-
} else {
|
|
961
|
-
cursor[key] = [existing, value];
|
|
744
|
+
function resolveMiddleware(items, named = {}) {
|
|
745
|
+
return items.map((item) => {
|
|
746
|
+
if (typeof item === "string") {
|
|
747
|
+
const fn = named[item];
|
|
748
|
+
if (!fn) {
|
|
749
|
+
throw new Error(`Named middleware "${item}" not found in middleware registry`);
|
|
750
|
+
}
|
|
751
|
+
return fn;
|
|
962
752
|
}
|
|
963
|
-
return;
|
|
753
|
+
return item;
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
for (const route of routes) {
|
|
757
|
+
const method = route.httpMethod.toLowerCase();
|
|
758
|
+
const openapiOperation = getOpenApiOperation(openapi, route);
|
|
759
|
+
const paramSchemaIndex = getParamSchemaIndex(openapiOperation);
|
|
760
|
+
const bodySchema = getRequestBodySchema(openapiOperation, route.args.body?.contentType) ?? (route.args.body ? getSchemaByRef(openapi, route.args.body.schemaRef) : null);
|
|
761
|
+
const coerceBodyDates = getDateCoercionOptions(coerce, "body");
|
|
762
|
+
const coerceQueryDates = getDateCoercionOptions(coerce, "query");
|
|
763
|
+
const coercePathDates = getDateCoercionOptions(coerce, "path");
|
|
764
|
+
const coerceHeaderDates = getDateCoercionOptions(coerce, "header");
|
|
765
|
+
const coerceCookieDates = getDateCoercionOptions(coerce, "cookie");
|
|
766
|
+
const middlewareChain = [];
|
|
767
|
+
if (middleware.global) {
|
|
768
|
+
middlewareChain.push(...resolveMiddleware(middleware.global, middleware.named || {}));
|
|
964
769
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
cursor[key] = {};
|
|
770
|
+
if (route.controllerUse) {
|
|
771
|
+
middlewareChain.push(...resolveMiddleware(route.controllerUse, middleware.named || {}));
|
|
968
772
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
const pairs = cookieHeader.split(";");
|
|
976
|
-
for (const pair of pairs) {
|
|
977
|
-
const [name, ...valueParts] = pair.trim().split("=");
|
|
978
|
-
if (name) {
|
|
979
|
-
cookies[name] = valueParts.join("=");
|
|
773
|
+
if (route.use) {
|
|
774
|
+
middlewareChain.push(...resolveMiddleware(route.use, middleware.named || {}));
|
|
775
|
+
}
|
|
776
|
+
if (options.auth) {
|
|
777
|
+
const authMw = createAuthMiddleware(options.auth, route.auth, openapi.security || []);
|
|
778
|
+
middlewareChain.push(authMw);
|
|
980
779
|
}
|
|
780
|
+
router[method](route.fullPath, ...middlewareChain, async (req, res, next) => {
|
|
781
|
+
try {
|
|
782
|
+
const validationErrors = precompiledValidators ? validateRequestWithPrecompiled(route, req, precompiledValidators) : validateRequest(route, req, openapi, validator);
|
|
783
|
+
if (validationErrors) {
|
|
784
|
+
return res.status(400).json(formatValidationErrors(validationErrors));
|
|
785
|
+
}
|
|
786
|
+
const instance = getInstance(route.controllerCtor);
|
|
787
|
+
const handler = instance[route.methodName];
|
|
788
|
+
if (typeof handler !== "function") {
|
|
789
|
+
throw new Error(`Method ${route.methodName} not found on controller`);
|
|
790
|
+
}
|
|
791
|
+
const args = [];
|
|
792
|
+
if (route.args.body) {
|
|
793
|
+
const coercedBody = (coerceBodyDates.date || coerceBodyDates.dateTime) && bodySchema ? coerceDatesWithSchema(req.body, bodySchema, coerceBodyDates, openapi.components.schemas) : req.body;
|
|
794
|
+
args[route.args.body.index] = coercedBody;
|
|
795
|
+
}
|
|
796
|
+
for (const pathArg of route.args.path) {
|
|
797
|
+
const rawValue = req.params[pathArg.name];
|
|
798
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "path", pathArg.name) ?? (pathArg.schemaRef ? getSchemaByRef(openapi, pathArg.schemaRef) : null) ?? schemaFromType(pathArg.schemaType);
|
|
799
|
+
const coerced = coerceParamValue(rawValue, paramSchema, coercePathDates, openapi.components.schemas);
|
|
800
|
+
args[pathArg.index] = coerced;
|
|
801
|
+
}
|
|
802
|
+
if (route.args.query.length > 0) {
|
|
803
|
+
const jsonArgs = route.args.query.filter((q) => q.content === "application/json");
|
|
804
|
+
const standardArgs = route.args.query.filter((q) => q.content !== "application/json");
|
|
805
|
+
const queryArgIndex = standardArgs[0]?.index;
|
|
806
|
+
if (queryArgIndex !== void 0) {
|
|
807
|
+
args[queryArgIndex] = {};
|
|
808
|
+
}
|
|
809
|
+
if (jsonArgs.length > 0) {
|
|
810
|
+
for (const q of jsonArgs) {
|
|
811
|
+
const rawValue = req.query[q.name];
|
|
812
|
+
if (rawValue === void 0 || rawValue === null) continue;
|
|
813
|
+
let parsed = rawValue;
|
|
814
|
+
if (typeof rawValue === "string" && rawValue.length > 0) {
|
|
815
|
+
try {
|
|
816
|
+
parsed = JSON.parse(rawValue);
|
|
817
|
+
} catch (e) {
|
|
818
|
+
parsed = rawValue;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(openapi, q.schemaRef) : null) ?? schemaFromType(q.schemaType);
|
|
822
|
+
const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
|
|
823
|
+
if (!args[q.index] || typeof args[q.index] !== "object") {
|
|
824
|
+
args[q.index] = {};
|
|
825
|
+
}
|
|
826
|
+
Object.assign(args[q.index], coerced);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (standardArgs.length > 0) {
|
|
830
|
+
for (const q of standardArgs) {
|
|
831
|
+
const rawValue = req.query[q.name];
|
|
832
|
+
if (rawValue === void 0) continue;
|
|
833
|
+
const parsed = parseQueryValue(rawValue, q);
|
|
834
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(openapi, q.schemaRef) : null) ?? schemaFromType(q.schemaType);
|
|
835
|
+
const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
|
|
836
|
+
if (!args[q.index] || typeof args[q.index] !== "object") {
|
|
837
|
+
args[q.index] = {};
|
|
838
|
+
}
|
|
839
|
+
args[q.index][q.name] = coerced;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (queryArgIndex !== void 0 && args[queryArgIndex]) {
|
|
843
|
+
const queryObj = args[queryArgIndex];
|
|
844
|
+
if (queryObj.page === void 0) {
|
|
845
|
+
queryObj.page = 1;
|
|
846
|
+
}
|
|
847
|
+
if (queryObj.pageSize === void 0) {
|
|
848
|
+
queryObj.pageSize = defaultPageSize;
|
|
849
|
+
}
|
|
850
|
+
if (queryObj.sort) {
|
|
851
|
+
queryObj.sort = normalizeSort(queryObj.sort);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (route.args.headers.length > 0) {
|
|
856
|
+
const firstHeaderIndex = route.args.headers[0].index;
|
|
857
|
+
const allSameIndex = route.args.headers.every((h) => h.index === firstHeaderIndex);
|
|
858
|
+
if (allSameIndex) {
|
|
859
|
+
args[firstHeaderIndex] = {};
|
|
860
|
+
for (const h of route.args.headers) {
|
|
861
|
+
const headerValue = req.headers[h.name.toLowerCase()];
|
|
862
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(openapi, h.schemaRef) : null) ?? schemaFromType(h.schemaType);
|
|
863
|
+
const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
|
|
864
|
+
args[firstHeaderIndex][h.name] = coerced ?? void 0;
|
|
865
|
+
}
|
|
866
|
+
} else {
|
|
867
|
+
for (const h of route.args.headers) {
|
|
868
|
+
const headerValue = req.headers[h.name.toLowerCase()];
|
|
869
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(openapi, h.schemaRef) : null) ?? schemaFromType(h.schemaType);
|
|
870
|
+
const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
|
|
871
|
+
args[h.index] = coerced ?? void 0;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
if (route.args.cookies.length > 0) {
|
|
876
|
+
const firstCookieIndex = route.args.cookies[0].index;
|
|
877
|
+
const allSameIndex = route.args.cookies.every((c) => c.index === firstCookieIndex);
|
|
878
|
+
const cookies = parseCookies(req.headers.cookie);
|
|
879
|
+
if (allSameIndex) {
|
|
880
|
+
args[firstCookieIndex] = {};
|
|
881
|
+
for (const c of route.args.cookies) {
|
|
882
|
+
const cookieValue = cookies[c.name];
|
|
883
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(openapi, c.name) : null) ?? schemaFromType(c.schemaType);
|
|
884
|
+
const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
|
|
885
|
+
args[firstCookieIndex][c.name] = coerced;
|
|
886
|
+
}
|
|
887
|
+
} else {
|
|
888
|
+
for (const c of route.args.cookies) {
|
|
889
|
+
const cookieValue = cookies[c.name];
|
|
890
|
+
const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(openapi, c.name) : null) ?? schemaFromType(c.schemaType);
|
|
891
|
+
const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
|
|
892
|
+
args[c.index] = coerced;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (args.length === 0) {
|
|
897
|
+
args.push(req);
|
|
898
|
+
}
|
|
899
|
+
const result = await handler.apply(instance, args);
|
|
900
|
+
const primaryResponse = route.responses[0];
|
|
901
|
+
const status = primaryResponse?.status ?? 200;
|
|
902
|
+
res.status(status).json(result);
|
|
903
|
+
} catch (error) {
|
|
904
|
+
next(error);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
981
907
|
}
|
|
982
|
-
return
|
|
908
|
+
return router;
|
|
983
909
|
}
|
|
910
|
+
|
|
911
|
+
// src/adapter/express/swagger.ts
|
|
912
|
+
import { Router as Router2 } from "express";
|
|
913
|
+
import { readFileSync } from "fs";
|
|
914
|
+
import { resolve, isAbsolute } from "path";
|
|
915
|
+
import swaggerUi from "swagger-ui-express";
|
|
984
916
|
function setupSwagger(options = {}) {
|
|
985
917
|
const {
|
|
986
918
|
artifactsDir = ".adorn",
|
|
@@ -988,7 +920,7 @@ function setupSwagger(options = {}) {
|
|
|
988
920
|
uiPath = "/docs",
|
|
989
921
|
swaggerOptions = {}
|
|
990
922
|
} = options;
|
|
991
|
-
const router =
|
|
923
|
+
const router = Router2();
|
|
992
924
|
router.get(jsonPath, (req, res) => {
|
|
993
925
|
const openApiPath = isAbsolute(artifactsDir) ? resolve(artifactsDir, "openapi.json") : resolve(process.cwd(), artifactsDir, "openapi.json");
|
|
994
926
|
const content = readFileSync(openApiPath, "utf-8");
|
|
@@ -1003,6 +935,97 @@ function setupSwagger(options = {}) {
|
|
|
1003
935
|
}));
|
|
1004
936
|
return router;
|
|
1005
937
|
}
|
|
938
|
+
|
|
939
|
+
// src/adapter/express/bootstrap.ts
|
|
940
|
+
import express from "express";
|
|
941
|
+
import path2 from "path";
|
|
942
|
+
function bootstrap(options) {
|
|
943
|
+
return new Promise(async (resolve2, reject) => {
|
|
944
|
+
try {
|
|
945
|
+
const {
|
|
946
|
+
controllers,
|
|
947
|
+
port: userPort,
|
|
948
|
+
host: userHost,
|
|
949
|
+
artifactsDir: userArtifactsDir = ".adorn",
|
|
950
|
+
enableSwagger = true,
|
|
951
|
+
swaggerPath = "/docs",
|
|
952
|
+
swaggerJsonPath = "/docs/openapi.json",
|
|
953
|
+
middleware,
|
|
954
|
+
auth,
|
|
955
|
+
coerce
|
|
956
|
+
} = options;
|
|
957
|
+
if (controllers.length === 0) {
|
|
958
|
+
reject(new Error("At least one controller must be provided to bootstrap()."));
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const envPort = process.env.PORT;
|
|
962
|
+
const port = userPort ?? (envPort !== void 0 ? Number(envPort) : 3e3);
|
|
963
|
+
const host = userHost ?? process.env.HOST ?? "0.0.0.0";
|
|
964
|
+
if (isNaN(port) || port < 0 || port > 65535) {
|
|
965
|
+
reject(new Error(`Invalid port: ${port}. Port must be between 0 and 65535.`));
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const absoluteArtifactsDir = path2.isAbsolute(userArtifactsDir) ? userArtifactsDir : path2.resolve(process.cwd(), userArtifactsDir);
|
|
969
|
+
const app = express();
|
|
970
|
+
app.use(express.json());
|
|
971
|
+
const router = await createExpressRouter({
|
|
972
|
+
controllers,
|
|
973
|
+
artifactsDir: absoluteArtifactsDir,
|
|
974
|
+
middleware,
|
|
975
|
+
auth,
|
|
976
|
+
coerce
|
|
977
|
+
});
|
|
978
|
+
app.use(router);
|
|
979
|
+
if (enableSwagger) {
|
|
980
|
+
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
981
|
+
const serverUrl = `http://${displayHost}:${port}`;
|
|
982
|
+
app.use(
|
|
983
|
+
setupSwagger({
|
|
984
|
+
artifactsDir: absoluteArtifactsDir,
|
|
985
|
+
jsonPath: swaggerJsonPath,
|
|
986
|
+
uiPath: swaggerPath,
|
|
987
|
+
swaggerOptions: {
|
|
988
|
+
servers: [{ url: serverUrl }]
|
|
989
|
+
}
|
|
990
|
+
})
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
const server = app.listen(port, host, () => {
|
|
994
|
+
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
995
|
+
const serverUrl = `http://${displayHost}:${port}`;
|
|
996
|
+
console.log(`\u{1F680} Server running on ${serverUrl}`);
|
|
997
|
+
if (enableSwagger) {
|
|
998
|
+
console.log(`\u{1F4DA} Swagger UI: ${serverUrl}${swaggerPath}`);
|
|
999
|
+
}
|
|
1000
|
+
const result = {
|
|
1001
|
+
server,
|
|
1002
|
+
app,
|
|
1003
|
+
url: serverUrl,
|
|
1004
|
+
port,
|
|
1005
|
+
host,
|
|
1006
|
+
close: () => new Promise((closeResolve) => {
|
|
1007
|
+
server.close(() => {
|
|
1008
|
+
console.log("Server closed gracefully");
|
|
1009
|
+
closeResolve();
|
|
1010
|
+
});
|
|
1011
|
+
})
|
|
1012
|
+
};
|
|
1013
|
+
resolve2(result);
|
|
1014
|
+
});
|
|
1015
|
+
server.on("error", (error) => {
|
|
1016
|
+
if (error.code === "EADDRINUSE") {
|
|
1017
|
+
reject(new Error(`Port ${port} already in use. Please choose a different port.`));
|
|
1018
|
+
} else if (error.code === "EACCES") {
|
|
1019
|
+
reject(new Error(`Permission denied for port ${port}. Ports below 1024 require root privileges.`));
|
|
1020
|
+
} else {
|
|
1021
|
+
reject(new Error(`Failed to start server: ${error.message}`));
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
reject(error);
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1006
1029
|
export {
|
|
1007
1030
|
ValidationErrorResponse,
|
|
1008
1031
|
bindRoutes,
|