adorn-api 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/express.cjs CHANGED
@@ -284,7 +284,8 @@ function bootstrap(options) {
284
284
  swaggerPath = "/docs",
285
285
  swaggerJsonPath = "/docs/openapi.json",
286
286
  middleware,
287
- auth
287
+ auth,
288
+ coerce
288
289
  } = options;
289
290
  if (controllers.length === 0) {
290
291
  reject(new Error("At least one controller must be provided to bootstrap()."));
@@ -304,7 +305,8 @@ function bootstrap(options) {
304
305
  controllers,
305
306
  artifactsDir: absoluteArtifactsDir,
306
307
  middleware,
307
- auth
308
+ auth,
309
+ coerce
308
310
  });
309
311
  app.use(router);
310
312
  if (enableSwagger) {
@@ -359,6 +361,17 @@ function bootstrap(options) {
359
361
  }
360
362
 
361
363
  // src/adapter/express/index.ts
364
+ function normalizeCoerceOptions(coerce) {
365
+ return {
366
+ body: coerce?.body ?? false,
367
+ query: coerce?.query ?? false,
368
+ path: coerce?.path ?? false,
369
+ header: coerce?.header ?? false,
370
+ cookie: coerce?.cookie ?? false,
371
+ dateTime: coerce?.dateTime ?? false,
372
+ date: coerce?.date ?? false
373
+ };
374
+ }
362
375
  async function createExpressRouter(options) {
363
376
  const { controllers, artifactsDir = ".adorn", middleware = {} } = options;
364
377
  let manifest;
@@ -385,6 +398,7 @@ async function createExpressRouter(options) {
385
398
  const validator = precompiledValidators ? null : createValidator();
386
399
  const router = (0, import_express2.Router)();
387
400
  const instanceCache = /* @__PURE__ */ new Map();
401
+ const coerce = normalizeCoerceOptions(options.coerce);
388
402
  function getInstance(Ctor) {
389
403
  if (!instanceCache.has(Ctor)) {
390
404
  instanceCache.set(Ctor, new Ctor());
@@ -447,6 +461,14 @@ async function createExpressRouter(options) {
447
461
  }
448
462
  for (const route of routes) {
449
463
  const method = route.httpMethod.toLowerCase();
464
+ const openapiOperation = getOpenApiOperation(openapi, route);
465
+ const paramSchemaIndex = buildParamSchemaIndex(openapiOperation);
466
+ const bodySchema = getRequestBodySchema(openapiOperation, route.args.body?.contentType) ?? (route.args.body ? getSchemaByRef(route.args.body.schemaRef) : null);
467
+ const coerceBodyDates = getDateCoercionOptions(coerce, "body");
468
+ const coerceQueryDates = getDateCoercionOptions(coerce, "query");
469
+ const coercePathDates = getDateCoercionOptions(coerce, "path");
470
+ const coerceHeaderDates = getDateCoercionOptions(coerce, "header");
471
+ const coerceCookieDates = getDateCoercionOptions(coerce, "cookie");
450
472
  const middlewareChain = [];
451
473
  if (middleware.global) {
452
474
  middlewareChain.push(...resolveMiddleware(middleware.global, middleware.named || {}));
@@ -474,29 +496,50 @@ async function createExpressRouter(options) {
474
496
  }
475
497
  const args = [];
476
498
  if (route.args.body) {
477
- args[route.args.body.index] = req.body;
499
+ const coercedBody = (coerceBodyDates.date || coerceBodyDates.dateTime) && bodySchema ? coerceDatesWithSchema(req.body, bodySchema, coerceBodyDates, openapi.components.schemas) : req.body;
500
+ args[route.args.body.index] = coercedBody;
478
501
  }
479
502
  for (const pathArg of route.args.path) {
480
- const coerced = coerceValue(req.params[pathArg.name], pathArg.schemaType);
503
+ const rawValue = req.params[pathArg.name];
504
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "path", pathArg.name) ?? (pathArg.schemaRef ? getSchemaByRef(pathArg.schemaRef) : null) ?? schemaFromType(pathArg.schemaType);
505
+ const coerced = coerceParamValue(rawValue, paramSchema, coercePathDates, openapi.components.schemas);
481
506
  args[pathArg.index] = coerced;
482
507
  }
483
508
  if (route.args.query.length > 0) {
484
- const firstQueryIndex = route.args.query[0].index;
485
- const allSameIndex = route.args.query.every((q) => q.index === firstQueryIndex);
486
- if (allSameIndex) {
487
- args[firstQueryIndex] = {};
488
- for (const q of route.args.query) {
489
- const parsed = parseQueryValue(req.query[q.name], q);
490
- const coerced = coerceValue(parsed, q.schemaType);
491
- args[firstQueryIndex][q.name] = coerced;
492
- }
493
- } else {
494
- for (const q of route.args.query) {
495
- const parsed = parseQueryValue(req.query[q.name], q);
496
- const coerced = coerceValue(parsed, q.schemaType);
509
+ const deepObjectArgs = route.args.query.filter((q) => q.serialization?.style === "deepObject");
510
+ const standardArgs = route.args.query.filter((q) => q.serialization?.style !== "deepObject");
511
+ if (deepObjectArgs.length > 0) {
512
+ const rawQuery = getRawQueryString(req);
513
+ const names = new Set(deepObjectArgs.map((q) => q.name));
514
+ const parsedDeep = parseDeepObjectParams(rawQuery, names);
515
+ for (const q of deepObjectArgs) {
516
+ const rawValue = parsedDeep[q.name];
517
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
518
+ const baseValue = rawValue === void 0 ? {} : rawValue;
519
+ const coerced = coerceParamValue(baseValue, paramSchema, coerceQueryDates, openapi.components.schemas);
497
520
  args[q.index] = coerced;
498
521
  }
499
522
  }
523
+ if (standardArgs.length > 0) {
524
+ const firstQueryIndex = standardArgs[0].index;
525
+ const allSameIndex = standardArgs.every((q) => q.index === firstQueryIndex);
526
+ if (allSameIndex) {
527
+ args[firstQueryIndex] = {};
528
+ for (const q of standardArgs) {
529
+ const parsed = parseQueryValue(req.query[q.name], q);
530
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
531
+ const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
532
+ args[firstQueryIndex][q.name] = coerced;
533
+ }
534
+ } else {
535
+ for (const q of standardArgs) {
536
+ const parsed = parseQueryValue(req.query[q.name], q);
537
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name) ?? (q.schemaRef ? getSchemaByRef(q.schemaRef) : null) ?? schemaFromType(q.schemaType);
538
+ const coerced = coerceParamValue(parsed, paramSchema, coerceQueryDates, openapi.components.schemas);
539
+ args[q.index] = coerced;
540
+ }
541
+ }
542
+ }
500
543
  }
501
544
  if (route.args.headers.length > 0) {
502
545
  const firstHeaderIndex = route.args.headers[0].index;
@@ -505,12 +548,16 @@ async function createExpressRouter(options) {
505
548
  args[firstHeaderIndex] = {};
506
549
  for (const h of route.args.headers) {
507
550
  const headerValue = req.headers[h.name.toLowerCase()];
508
- args[firstHeaderIndex][h.name] = headerValue ?? void 0;
551
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(h.schemaRef) : null) ?? schemaFromType(h.schemaType);
552
+ const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
553
+ args[firstHeaderIndex][h.name] = coerced ?? void 0;
509
554
  }
510
555
  } else {
511
556
  for (const h of route.args.headers) {
512
557
  const headerValue = req.headers[h.name.toLowerCase()];
513
- args[h.index] = headerValue ?? void 0;
558
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "header", h.name) ?? (h.schemaRef ? getSchemaByRef(h.schemaRef) : null) ?? schemaFromType(h.schemaType);
559
+ const coerced = coerceParamValue(headerValue, paramSchema, coerceHeaderDates, openapi.components.schemas);
560
+ args[h.index] = coerced ?? void 0;
514
561
  }
515
562
  }
516
563
  }
@@ -522,13 +569,15 @@ async function createExpressRouter(options) {
522
569
  args[firstCookieIndex] = {};
523
570
  for (const c of route.args.cookies) {
524
571
  const cookieValue = cookies[c.name];
525
- const coerced = coerceValue(cookieValue, c.schemaType);
572
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(c.schemaRef) : null) ?? schemaFromType(c.schemaType);
573
+ const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
526
574
  args[firstCookieIndex][c.name] = coerced;
527
575
  }
528
576
  } else {
529
577
  for (const c of route.args.cookies) {
530
578
  const cookieValue = cookies[c.name];
531
- const coerced = coerceValue(cookieValue, c.schemaType);
579
+ const paramSchema = getParamSchemaFromIndex(paramSchemaIndex, "cookie", c.name) ?? (c.schemaRef ? getSchemaByRef(c.schemaRef) : null) ?? schemaFromType(c.schemaType);
580
+ const coerced = coerceParamValue(cookieValue, paramSchema, coerceCookieDates, openapi.components.schemas);
532
581
  args[c.index] = coerced;
533
582
  }
534
583
  }
@@ -547,8 +596,53 @@ async function createExpressRouter(options) {
547
596
  }
548
597
  return router;
549
598
  }
599
+ function getDateCoercionOptions(coerce, location) {
600
+ const enabled = coerce[location];
601
+ return {
602
+ dateTime: enabled && coerce.dateTime,
603
+ date: enabled && coerce.date
604
+ };
605
+ }
606
+ function toOpenApiPath(path3) {
607
+ return path3.replace(/:([^/]+)/g, "{$1}");
608
+ }
609
+ function getOpenApiOperation(openapi, route) {
610
+ const pathKey = toOpenApiPath(route.fullPath);
611
+ const pathItem = openapi.paths?.[pathKey];
612
+ if (!pathItem) return null;
613
+ return pathItem[route.httpMethod.toLowerCase()] ?? null;
614
+ }
615
+ function buildParamSchemaIndex(operation) {
616
+ const index = /* @__PURE__ */ new Map();
617
+ const params = operation?.parameters ?? [];
618
+ for (const param of params) {
619
+ if (!param?.name || !param?.in) continue;
620
+ if (param.schema) {
621
+ index.set(`${param.in}:${param.name}`, param.schema);
622
+ }
623
+ }
624
+ return index;
625
+ }
626
+ function getParamSchemaFromIndex(index, location, name) {
627
+ return index.get(`${location}:${name}`) ?? null;
628
+ }
629
+ function getRequestBodySchema(operation, contentType) {
630
+ const content = operation?.requestBody?.content;
631
+ if (!content) return null;
632
+ if (contentType && content[contentType]?.schema) {
633
+ return content[contentType].schema;
634
+ }
635
+ const first = Object.values(content)[0];
636
+ return first?.schema ?? null;
637
+ }
638
+ function schemaFromType(schemaType) {
639
+ if (!schemaType) return null;
640
+ return { type: schemaType };
641
+ }
550
642
  function validateRequestWithPrecompiled(route, req, validators) {
551
643
  const errors = [];
644
+ const deepNames = new Set(route.args.query.filter((q) => q.serialization?.style === "deepObject").map((q) => q.name));
645
+ const deepValues = deepNames.size > 0 ? parseDeepObjectParams(getRawQueryString(req), deepNames) : {};
552
646
  if (route.args.body) {
553
647
  const validator = validators[route.operationId]?.body;
554
648
  if (validator) {
@@ -567,14 +661,10 @@ function validateRequestWithPrecompiled(route, req, validators) {
567
661
  }
568
662
  }
569
663
  for (const q of route.args.query) {
570
- const value = req.query[q.name];
664
+ const value = q.serialization?.style === "deepObject" ? deepValues[q.name] : req.query[q.name];
571
665
  if (value === void 0) continue;
572
- const schema = {};
573
- if (q.schemaType) {
574
- const type = Array.isArray(q.schemaType) ? q.schemaType[0] : q.schemaType;
575
- schema.type = type;
576
- }
577
- const coerced = coerceValue(value, q.schemaType);
666
+ const schema = schemaFromType(q.schemaType) ?? {};
667
+ const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, {});
578
668
  if (Object.keys(schema).length > 0 && coerced !== void 0) {
579
669
  errors.push({
580
670
  path: `#/query/${q.name}`,
@@ -587,12 +677,8 @@ function validateRequestWithPrecompiled(route, req, validators) {
587
677
  for (const p of route.args.path) {
588
678
  const value = req.params[p.name];
589
679
  if (value === void 0) continue;
590
- const schema = {};
591
- if (p.schemaType) {
592
- const type = Array.isArray(p.schemaType) ? p.schemaType[0] : p.schemaType;
593
- schema.type = type;
594
- }
595
- const coerced = coerceValue(value, p.schemaType);
680
+ const schema = schemaFromType(p.schemaType) ?? {};
681
+ const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, {});
596
682
  if (Object.keys(schema).length > 0 && coerced !== void 0) {
597
683
  errors.push({
598
684
  path: `#/path/${p.name}`,
@@ -610,6 +696,10 @@ function validateRequest(route, req, openapi, validator) {
610
696
  const schemaName = ref.replace("#/components/schemas/", "");
611
697
  return openapi.components.schemas[schemaName] || null;
612
698
  }
699
+ const openapiOperation = getOpenApiOperation(openapi, route);
700
+ const paramSchemaIndex = buildParamSchemaIndex(openapiOperation);
701
+ const deepNames = new Set(route.args.query.filter((q) => q.serialization?.style === "deepObject").map((q) => q.name));
702
+ const deepValues = deepNames.size > 0 ? parseDeepObjectParams(getRawQueryString(req), deepNames) : {};
613
703
  const errors = [];
614
704
  if (route.args.body) {
615
705
  const bodySchema = getSchemaByRef(route.args.body.schemaRef);
@@ -629,19 +719,24 @@ function validateRequest(route, req, openapi, validator) {
629
719
  }
630
720
  }
631
721
  for (const q of route.args.query) {
632
- const value = req.query[q.name];
633
- const schema = {};
634
- if (q.schemaType) {
635
- const type = Array.isArray(q.schemaType) ? q.schemaType[0] : q.schemaType;
636
- schema.type = type;
637
- }
638
- if (q.schemaRef && q.schemaRef.includes("Inline")) {
639
- const inlineSchema = getSchemaByRef(q.schemaRef);
640
- if (inlineSchema) {
641
- Object.assign(schema, inlineSchema);
722
+ const value = q.serialization?.style === "deepObject" ? deepValues[q.name] : req.query[q.name];
723
+ const openapiSchema = getParamSchemaFromIndex(paramSchemaIndex, "query", q.name);
724
+ let schema = {};
725
+ if (openapiSchema) {
726
+ schema = resolveSchema(openapiSchema, openapi.components.schemas);
727
+ } else {
728
+ if (q.schemaType) {
729
+ const type = Array.isArray(q.schemaType) ? q.schemaType[0] : q.schemaType;
730
+ schema.type = type;
731
+ }
732
+ if (q.schemaRef && q.schemaRef.includes("Inline")) {
733
+ const inlineSchema = getSchemaByRef(q.schemaRef);
734
+ if (inlineSchema) {
735
+ Object.assign(schema, inlineSchema);
736
+ }
642
737
  }
643
738
  }
644
- const coerced = coerceValue(value, q.schemaType);
739
+ const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, openapi.components.schemas);
645
740
  if (Object.keys(schema).length > 0 && coerced !== void 0) {
646
741
  const validate = validator.compile(schema);
647
742
  const valid = validate(coerced);
@@ -659,18 +754,23 @@ function validateRequest(route, req, openapi, validator) {
659
754
  }
660
755
  for (const p of route.args.path) {
661
756
  const value = req.params[p.name];
662
- const schema = {};
663
- if (p.schemaType) {
664
- const type = Array.isArray(p.schemaType) ? p.schemaType[0] : p.schemaType;
665
- schema.type = type;
666
- }
667
- if (p.schemaRef && p.schemaRef.includes("Inline")) {
668
- const inlineSchema = getSchemaByRef(p.schemaRef);
669
- if (inlineSchema) {
670
- Object.assign(schema, inlineSchema);
757
+ const openapiSchema = getParamSchemaFromIndex(paramSchemaIndex, "path", p.name);
758
+ let schema = {};
759
+ if (openapiSchema) {
760
+ schema = resolveSchema(openapiSchema, openapi.components.schemas);
761
+ } else {
762
+ if (p.schemaType) {
763
+ const type = Array.isArray(p.schemaType) ? p.schemaType[0] : p.schemaType;
764
+ schema.type = type;
765
+ }
766
+ if (p.schemaRef && p.schemaRef.includes("Inline")) {
767
+ const inlineSchema = getSchemaByRef(p.schemaRef);
768
+ if (inlineSchema) {
769
+ Object.assign(schema, inlineSchema);
770
+ }
671
771
  }
672
772
  }
673
- const coerced = coerceValue(value, p.schemaType);
773
+ const coerced = coerceParamValue(value, schema, { dateTime: false, date: false }, openapi.components.schemas);
674
774
  if (Object.keys(schema).length > 0 && coerced !== void 0) {
675
775
  const validate = validator.compile(schema);
676
776
  const valid = validate(coerced);
@@ -688,23 +788,127 @@ function validateRequest(route, req, openapi, validator) {
688
788
  }
689
789
  return errors.length > 0 ? errors : null;
690
790
  }
691
- function coerceValue(value, schemaType) {
791
+ function resolveSchema(schema, components, seen = /* @__PURE__ */ new Set()) {
792
+ const ref = schema.$ref;
793
+ if (typeof ref !== "string" || !ref.startsWith("#/components/schemas/")) {
794
+ return schema;
795
+ }
796
+ const name = ref.replace("#/components/schemas/", "");
797
+ if (seen.has(name)) return schema;
798
+ const next = components[name];
799
+ if (!next) return schema;
800
+ seen.add(name);
801
+ return resolveSchema(next, components, seen);
802
+ }
803
+ function coerceDatesWithSchema(value, schema, dateCoercion, components) {
804
+ if (!schema || !dateCoercion.date && !dateCoercion.dateTime) return value;
805
+ return coerceWithSchema(value, schema, dateCoercion, components, { coercePrimitives: false });
806
+ }
807
+ function coerceParamValue(value, schema, dateCoercion, components) {
808
+ if (!schema) return value;
809
+ return coerceWithSchema(value, schema, dateCoercion, components, { coercePrimitives: true });
810
+ }
811
+ function coerceWithSchema(value, schema, dateCoercion, components, options) {
692
812
  if (value === void 0 || value === null) return value;
693
- const type = Array.isArray(schemaType) ? schemaType[0] : schemaType;
694
- if (type === "number" || type === "integer") {
813
+ if (value instanceof Date) return value;
814
+ const resolved = resolveSchema(schema, components);
815
+ const override = resolved["x-adorn-coerce"];
816
+ const allowDateTime = override === true ? true : override === false ? false : dateCoercion.dateTime;
817
+ const allowDate = override === true ? true : override === false ? false : dateCoercion.date;
818
+ const byFormat = coerceDateString(value, resolved, allowDateTime, allowDate);
819
+ if (byFormat !== value) return byFormat;
820
+ const allOf = resolved.allOf;
821
+ if (Array.isArray(allOf)) {
822
+ let out = value;
823
+ for (const entry of allOf) {
824
+ out = coerceWithSchema(out, entry, { dateTime: allowDateTime, date: allowDate }, components, options);
825
+ }
826
+ return out;
827
+ }
828
+ const variants = resolved.oneOf ?? resolved.anyOf;
829
+ if (Array.isArray(variants)) {
830
+ for (const entry of variants) {
831
+ const out = coerceWithSchema(value, entry, { dateTime: allowDateTime, date: allowDate }, components, options);
832
+ if (out !== value) return out;
833
+ }
834
+ }
835
+ const schemaType = resolved.type;
836
+ const types = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
837
+ if ((types.includes("array") || resolved.items) && Array.isArray(value)) {
838
+ const itemSchema = resolved.items ?? {};
839
+ return value.map((item) => coerceWithSchema(item, itemSchema, { dateTime: allowDateTime, date: allowDate }, components, options));
840
+ }
841
+ if ((types.includes("object") || resolved.properties || resolved.additionalProperties) && isPlainObject(value)) {
842
+ const props = resolved.properties;
843
+ const out = { ...value };
844
+ if (props) {
845
+ for (const [key, propSchema] of Object.entries(props)) {
846
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
847
+ out[key] = coerceWithSchema(value[key], propSchema, { dateTime: allowDateTime, date: allowDate }, components, options);
848
+ }
849
+ }
850
+ }
851
+ const additional = resolved.additionalProperties;
852
+ if (additional && typeof additional === "object") {
853
+ for (const [key, entry] of Object.entries(value)) {
854
+ if (props && Object.prototype.hasOwnProperty.call(props, key)) continue;
855
+ out[key] = coerceWithSchema(entry, additional, { dateTime: allowDateTime, date: allowDate }, components, options);
856
+ }
857
+ }
858
+ return out;
859
+ }
860
+ if (options.coercePrimitives) {
861
+ return coercePrimitiveValue(value, types);
862
+ }
863
+ return value;
864
+ }
865
+ function coerceDateString(value, schema, allowDateTime, allowDate) {
866
+ if (typeof value !== "string") return value;
867
+ const format = schema.format;
868
+ const schemaType = schema.type;
869
+ const types = Array.isArray(schemaType) ? schemaType : schemaType ? [schemaType] : [];
870
+ const allowsString = types.length === 0 || types.includes("string");
871
+ if (format === "date-time" && allowDateTime && allowsString) {
872
+ const parsed = new Date(value);
873
+ if (Number.isNaN(parsed.getTime())) {
874
+ throw new Error(`Invalid date-time: ${value}`);
875
+ }
876
+ return parsed;
877
+ }
878
+ if (format === "date" && allowDate && allowsString) {
879
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
880
+ throw new Error(`Invalid date: ${value}`);
881
+ }
882
+ const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
883
+ if (Number.isNaN(parsed.getTime())) {
884
+ throw new Error(`Invalid date: ${value}`);
885
+ }
886
+ return parsed;
887
+ }
888
+ return value;
889
+ }
890
+ function coercePrimitiveValue(value, types) {
891
+ if (value === void 0 || value === null) return value;
892
+ if (types.includes("number") || types.includes("integer")) {
695
893
  const num = Number(value);
696
- if (isNaN(num)) {
894
+ if (Number.isNaN(num)) {
697
895
  throw new Error(`Invalid number: ${value}`);
698
896
  }
699
897
  return num;
700
898
  }
701
- if (type === "boolean") {
899
+ if (types.includes("boolean")) {
702
900
  if (value === "true") return true;
703
901
  if (value === "false") return false;
902
+ if (typeof value === "boolean") return value;
704
903
  throw new Error(`Invalid boolean: ${value}`);
705
904
  }
706
905
  return value;
707
906
  }
907
+ function isPlainObject(value) {
908
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
909
+ const proto = Object.getPrototypeOf(value);
910
+ return proto === Object.prototype || proto === null;
911
+ }
708
912
  function parseQueryValue(value, param) {
709
913
  if (value === void 0 || value === null) return value;
710
914
  const isArray = Array.isArray(param.schemaType) ? param.schemaType.includes("array") : param.schemaType === "array";
@@ -728,6 +932,69 @@ function parseQueryValue(value, param) {
728
932
  }
729
933
  return value;
730
934
  }
935
+ function getRawQueryString(req) {
936
+ const url = req.originalUrl ?? req.url ?? "";
937
+ const index = url.indexOf("?");
938
+ if (index === -1) return "";
939
+ return url.slice(index + 1);
940
+ }
941
+ function parseDeepObjectParams(rawQuery, names) {
942
+ const out = {};
943
+ if (!rawQuery || names.size === 0) return out;
944
+ const params = new URLSearchParams(rawQuery);
945
+ for (const [key, value] of params.entries()) {
946
+ const path3 = parseBracketPath(key);
947
+ if (path3.length === 0) continue;
948
+ const root = path3[0];
949
+ if (!names.has(root)) continue;
950
+ assignDeepValue(out, path3, value);
951
+ }
952
+ return out;
953
+ }
954
+ function parseBracketPath(key) {
955
+ const parts = [];
956
+ let current = "";
957
+ for (let i = 0; i < key.length; i++) {
958
+ const ch = key[i];
959
+ if (ch === "[") {
960
+ if (current) parts.push(current);
961
+ current = "";
962
+ continue;
963
+ }
964
+ if (ch === "]") {
965
+ if (current) parts.push(current);
966
+ current = "";
967
+ continue;
968
+ }
969
+ current += ch;
970
+ }
971
+ if (current) parts.push(current);
972
+ return parts;
973
+ }
974
+ function assignDeepValue(target, path3, value) {
975
+ let cursor = target;
976
+ for (let i = 0; i < path3.length; i++) {
977
+ const key = path3[i];
978
+ if (!key) continue;
979
+ const isLast = i === path3.length - 1;
980
+ if (isLast) {
981
+ const existing = cursor[key];
982
+ if (existing === void 0) {
983
+ cursor[key] = value;
984
+ } else if (Array.isArray(existing)) {
985
+ existing.push(value);
986
+ } else {
987
+ cursor[key] = [existing, value];
988
+ }
989
+ return;
990
+ }
991
+ const next = cursor[key];
992
+ if (!isPlainObject(next)) {
993
+ cursor[key] = {};
994
+ }
995
+ cursor = cursor[key];
996
+ }
997
+ }
731
998
  function parseCookies(cookieHeader) {
732
999
  if (!cookieHeader) return {};
733
1000
  const cookies = {};