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