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/README.md CHANGED
@@ -140,6 +140,58 @@ export class ProductsController {
140
140
  }
141
141
  ```
142
142
 
143
+ ### Query Objects
144
+
145
+ Use an object-typed parameter to bind flat query keys:
146
+
147
+ ```typescript
148
+ import type { Query } from "adorn-api";
149
+
150
+ type Filters = {
151
+ status?: string;
152
+ responsavelId?: number;
153
+ };
154
+
155
+ @Get("/")
156
+ async list(query?: Query<Filters>) {
157
+ return query;
158
+ }
159
+ ```
160
+
161
+ `GET /posts?status=published&responsavelId=1`
162
+
163
+ ### Deep Object Query (opt-in)
164
+
165
+ For bracketed query serialization, opt in with `@QueryStyle({ style: "deepObject" })`:
166
+
167
+ ```typescript
168
+ import { QueryStyle } from "adorn-api";
169
+
170
+ type WhereFilter = {
171
+ responsavel?: {
172
+ perfil?: {
173
+ nome?: string;
174
+ };
175
+ };
176
+ tags?: string[];
177
+ };
178
+
179
+ @Get("/")
180
+ @QueryStyle({ style: "deepObject" })
181
+ async list(where?: WhereFilter) {
182
+ return where;
183
+ }
184
+ ```
185
+
186
+ - `GET /posts?where[responsavel][perfil][nome]=Admin`
187
+ - `GET /posts?where[tags]=a&where[tags]=b`
188
+ - `GET /posts?where[comments][author][name]=Ali` (matches `Alice`)
189
+
190
+ Notes:
191
+ - Deep object is explicit and only applies to the query object parameter on that method.
192
+ - Repeated keys become arrays (for example, `where[tags]=a&where[tags]=b`).
193
+ - The `[]` shorthand is not supported; use repeated keys instead.
194
+
143
195
  ## Examples
144
196
 
145
197
  ### Basic Example
@@ -275,8 +327,8 @@ import { Controller, Get, Post, Put, Delete } from "adorn-api";
275
327
  import { BlogPost } from "../entities/index.js";
276
328
  import { getSession, selectFromEntity, eq } from "metal-orm";
277
329
 
278
- @Controller("/posts")
279
- export class PostsController {
330
+ @Controller("/blog-posts")
331
+ export class BlogPostsController {
280
332
  @Get("/")
281
333
  async getPosts(query?: { authorId?: number; status?: string }) {
282
334
  const session = getSession();
@@ -652,6 +704,7 @@ await bootstrap({
652
704
  swaggerJsonPath?: string, // Default: "/docs/openapi.json"
653
705
  middleware?: CreateRouterOptions["middleware"],
654
706
  auth?: CreateRouterOptions["auth"],
707
+ coerce?: CreateRouterOptions["coerce"],
655
708
  });
656
709
  ```
657
710
 
@@ -672,6 +725,15 @@ const router = await createExpressRouter({
672
725
  auth?: {
673
726
  schemes: Record<string, AuthSchemeRuntime>,
674
727
  },
728
+ coerce?: {
729
+ body?: boolean,
730
+ query?: boolean,
731
+ path?: boolean,
732
+ header?: boolean,
733
+ cookie?: boolean,
734
+ dateTime?: boolean,
735
+ date?: boolean,
736
+ },
675
737
  middleware?: {
676
738
  global?: Middleware[],
677
739
  named?: Record<string, Middleware>,
@@ -679,6 +741,8 @@ const router = await createExpressRouter({
679
741
  });
680
742
  ```
681
743
 
744
+ Date properties in TypeScript map to OpenAPI `type: "string"` with `format: "date-time"`. Enable `coerce` to convert ISO 8601 date-time strings into `Date` instances before your handler runs. For date-only strings, use `@Format("date")` and keep `date` coercion off to avoid timezone shifts. You can disable per-field coercion with `@Schema({ "x-adorn-coerce": false })`.
745
+
682
746
  ### setupSwagger()
683
747
 
684
748
  Add Swagger UI to any Express app:
@@ -11,6 +11,7 @@ export interface BootstrapOptions {
11
11
  swaggerJsonPath?: string;
12
12
  middleware?: CreateRouterOptions["middleware"];
13
13
  auth?: CreateRouterOptions["auth"];
14
+ coerce?: CreateRouterOptions["coerce"];
14
15
  }
15
16
  export interface BootstrapResult {
16
17
  server: Server;
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/adapter/express/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAuB,KAAK,mBAAmB,EAA0C,MAAM,YAAY,CAAC;AAGnH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC;IAC/C,IAAI,CAAC,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAqG7E"}
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../../src/adapter/express/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAuB,KAAK,mBAAmB,EAA0C,MAAM,YAAY,CAAC;AAGnH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC;IAC/C,IAAI,CAAC,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAuG7E"}
@@ -7,8 +7,18 @@ interface OpenAPI31 {
7
7
  schemas: Record<string, Record<string, unknown>>;
8
8
  securitySchemes?: Record<string, Record<string, unknown>>;
9
9
  };
10
+ paths?: Record<string, Record<string, any>>;
10
11
  security?: Array<Record<string, string[]>>;
11
12
  }
13
+ export interface CoerceOptions {
14
+ body?: boolean;
15
+ query?: boolean;
16
+ path?: boolean;
17
+ header?: boolean;
18
+ cookie?: boolean;
19
+ dateTime?: boolean;
20
+ date?: boolean;
21
+ }
12
22
  export interface CreateRouterOptions {
13
23
  controllers: Array<new (...args: any[]) => any>;
14
24
  artifactsDir?: string;
@@ -17,6 +27,7 @@ export interface CreateRouterOptions {
17
27
  auth?: {
18
28
  schemes: Record<string, AuthSchemeRuntime>;
19
29
  };
30
+ coerce?: CoerceOptions;
20
31
  middleware?: {
21
32
  global?: Array<string | ((req: any, res: any, next: (err?: any) => void) => any)>;
22
33
  named?: Record<string, (req: any, res: any, next: (err?: any) => void) => any>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapter/express/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAIjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAGpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAIvE,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE;QACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KAC3D,CAAC;IACF,QAAQ,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CAC5C;AAMD,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE;QACL,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;KAC5C,CAAC;IACF,UAAU,CAAC,EAAE;QACX,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;QAClF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC;KAChF,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE;QACf,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACvD,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8OvF;AA8PD,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElE,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CA4BtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapter/express/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAIjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAGpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAIvE,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE;QACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KAC3D,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CAC5C;AAMD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE;QACL,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;KAC5C,CAAC;IACF,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,UAAU,CAAC,EAAE;QACX,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;QAClF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC;KAChF,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE;QACf,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACvD,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AAcD,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwSvF;AAijBD,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElE,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,CA4BtE"}
package/dist/cli.cjs CHANGED
@@ -214,7 +214,8 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
214
214
  for (let i = 0; i < parameters.length; i++) {
215
215
  const param = parameters[i];
216
216
  if (usedIndices.has(i)) continue;
217
- const typeStr = param.type.getSymbol()?.getName() ?? "";
217
+ const nonNullableType = checker.getNonNullableType(param.type);
218
+ const typeStr = getTypeName(param.type) || getTypeName(nonNullableType);
218
219
  if (typeStr === "Body") {
219
220
  bodyParamIndex = i;
220
221
  usedIndices.add(i);
@@ -240,7 +241,7 @@ function classifyParameters(parameters, httpMethod, pathParamIndices, checker) {
240
241
  usedIndices.add(i);
241
242
  continue;
242
243
  }
243
- const isObj = isObjectType(param.type, checker);
244
+ const isObj = isObjectType(nonNullableType, checker);
244
245
  if (isObj && queryObjectParamIndex === null && !isBodyMethod) {
245
246
  queryObjectParamIndex = i;
246
247
  usedIndices.add(i);
@@ -269,6 +270,12 @@ function isObjectType(type, checker) {
269
270
  if (callSignatures && callSignatures.length > 0) return false;
270
271
  return true;
271
272
  }
273
+ function getTypeName(type) {
274
+ const aliasSymbol = type.aliasSymbol ?? type.aliasSymbol;
275
+ if (aliasSymbol) return aliasSymbol.getName();
276
+ const symbol = type.getSymbol();
277
+ return symbol?.getName() ?? "";
278
+ }
272
279
  function findDecorator(node, name) {
273
280
  const decorators = import_typescript2.default.getDecorators(node);
274
281
  if (!decorators) return null;
@@ -303,7 +310,7 @@ function unwrapPromise(type, checker) {
303
310
  }
304
311
 
305
312
  // src/compiler/schema/openapi.ts
306
- var import_typescript5 = __toESM(require("typescript"), 1);
313
+ var import_typescript6 = __toESM(require("typescript"), 1);
307
314
 
308
315
  // src/compiler/schema/typeToJsonSchema.ts
309
316
  var import_typescript3 = __toESM(require("typescript"), 1);
@@ -315,6 +322,9 @@ function typeToJsonSchema(type, ctx, typeNode) {
315
322
  if (type.flags & import_typescript3.default.TypeFlags.Null) {
316
323
  return { type: "null" };
317
324
  }
325
+ if (isDateType(type, checker)) {
326
+ return { type: "string", format: "date-time" };
327
+ }
318
328
  if (type.flags & import_typescript3.default.TypeFlags.String) {
319
329
  return { type: "string" };
320
330
  }
@@ -364,6 +374,19 @@ function typeToJsonSchema(type, ctx, typeNode) {
364
374
  }
365
375
  return {};
366
376
  }
377
+ function isDateType(type, checker) {
378
+ const symbol = type.getSymbol();
379
+ const aliasSymbol = type.aliasSymbol;
380
+ if (aliasSymbol && aliasSymbol.flags & import_typescript3.default.SymbolFlags.Alias) {
381
+ const aliased = checker.getAliasedSymbol(aliasSymbol);
382
+ return aliased?.getName() === "Date";
383
+ }
384
+ if (symbol && symbol.flags & import_typescript3.default.SymbolFlags.Alias) {
385
+ const aliased = checker.getAliasedSymbol(symbol);
386
+ return aliased?.getName() === "Date";
387
+ }
388
+ return symbol?.getName() === "Date";
389
+ }
367
390
  function isSetType(type, checker) {
368
391
  const symbol = type.getSymbol();
369
392
  if (!symbol) return false;
@@ -761,6 +784,56 @@ function mergeFragments(base, ...frags) {
761
784
  return result;
762
785
  }
763
786
 
787
+ // src/compiler/analyze/extractQueryStyle.ts
788
+ var import_typescript5 = __toESM(require("typescript"), 1);
789
+ function extractQueryStyleOptions(checker, method) {
790
+ if (!import_typescript5.default.canHaveDecorators(method)) return null;
791
+ const decorators = import_typescript5.default.getDecorators(method);
792
+ if (!decorators || decorators.length === 0) return null;
793
+ for (const decorator of decorators) {
794
+ const expr = decorator.expression;
795
+ const isCall = import_typescript5.default.isCallExpression(expr);
796
+ const callee = isCall ? expr.expression : expr;
797
+ const args = isCall ? expr.arguments : import_typescript5.default.factory.createNodeArray([]);
798
+ const sym = checker.getSymbolAtLocation(callee);
799
+ if (!sym) continue;
800
+ const resolved = sym.flags & import_typescript5.default.SymbolFlags.Alias ? checker.getAliasedSymbol(sym) : sym;
801
+ const name = resolved.getName();
802
+ if (name !== "QueryStyle") continue;
803
+ const optsNode = args[0];
804
+ if (!optsNode || !import_typescript5.default.isObjectLiteralExpression(optsNode)) {
805
+ return {};
806
+ }
807
+ return parseQueryStyleOptions(optsNode);
808
+ }
809
+ return null;
810
+ }
811
+ function parseQueryStyleOptions(node) {
812
+ const opts = {};
813
+ for (const prop of node.properties) {
814
+ if (!import_typescript5.default.isPropertyAssignment(prop)) continue;
815
+ const name = getPropName(prop.name);
816
+ if (!name) continue;
817
+ if (name === "style" && import_typescript5.default.isStringLiteral(prop.initializer)) {
818
+ const style = prop.initializer.text;
819
+ opts.style = style;
820
+ } else if (name === "explode" && isBooleanLiteral(prop.initializer)) {
821
+ opts.explode = prop.initializer.kind === import_typescript5.default.SyntaxKind.TrueKeyword;
822
+ } else if (name === "allowReserved" && isBooleanLiteral(prop.initializer)) {
823
+ opts.allowReserved = prop.initializer.kind === import_typescript5.default.SyntaxKind.TrueKeyword;
824
+ }
825
+ }
826
+ return opts;
827
+ }
828
+ function getPropName(name) {
829
+ if (import_typescript5.default.isIdentifier(name)) return name.text;
830
+ if (import_typescript5.default.isStringLiteral(name)) return name.text;
831
+ return null;
832
+ }
833
+ function isBooleanLiteral(node) {
834
+ return node.kind === import_typescript5.default.SyntaxKind.TrueKeyword || node.kind === import_typescript5.default.SyntaxKind.FalseKeyword;
835
+ }
836
+
764
837
  // src/compiler/schema/openapi.ts
765
838
  function generateOpenAPI(controllers, checker, options = {}) {
766
839
  const components = /* @__PURE__ */ new Map();
@@ -852,12 +925,12 @@ function mergeBodySchemaAnnotations(bodyParam, ctx, schema) {
852
925
  const declarations = typeSymbol.getDeclarations();
853
926
  if (!declarations || declarations.length === 0) return schema;
854
927
  const classDecl = declarations[0];
855
- if (!import_typescript5.default.isClassDeclaration(classDecl)) return schema;
928
+ if (!import_typescript6.default.isClassDeclaration(classDecl)) return schema;
856
929
  const result = { ...schema };
857
930
  const props = { ...result.properties };
858
931
  for (const member of classDecl.members) {
859
- if (!import_typescript5.default.isPropertyDeclaration(member) || !member.name) continue;
860
- const propName = import_typescript5.default.isIdentifier(member.name) ? member.name.text : null;
932
+ if (!import_typescript6.default.isPropertyDeclaration(member) || !member.name) continue;
933
+ const propName = import_typescript6.default.isIdentifier(member.name) ? member.name.text : null;
861
934
  if (!propName) continue;
862
935
  if (!props[propName]) continue;
863
936
  const frags = extractPropertySchemaFragments(ctx.checker, member);
@@ -892,19 +965,36 @@ function buildQueryParameters(operation, ctx, parameters) {
892
965
  if (operation.queryObjectParamIndex !== null) {
893
966
  const queryParam = operation.parameters[operation.queryObjectParamIndex];
894
967
  if (!queryParam) return;
968
+ const queryStyle = extractQueryStyleOptions(ctx.checker, operation.methodDeclaration);
895
969
  const querySchema = typeToJsonSchema(queryParam.type, ctx);
896
- if (!querySchema.properties) return;
897
- const queryObjProps = querySchema.properties;
898
- for (const [propName, propSchema] of Object.entries(queryObjProps)) {
899
- const isRequired = querySchema.required?.includes(propName) ?? false;
900
- const serialization = determineQuerySerialization(propSchema.type);
901
- parameters.push({
902
- name: propName,
970
+ if (queryStyle?.style === "deepObject") {
971
+ const explode = queryStyle.explode ?? true;
972
+ const deepParam = {
973
+ name: queryParam.name,
903
974
  in: "query",
904
- required: isRequired,
905
- schema: propSchema,
906
- ...Object.keys(serialization).length > 0 ? serialization : {}
907
- });
975
+ required: !queryParam.isOptional,
976
+ schema: querySchema.$ref ? { $ref: querySchema.$ref } : querySchema,
977
+ style: "deepObject",
978
+ explode
979
+ };
980
+ if (queryStyle.allowReserved !== void 0) {
981
+ deepParam.allowReserved = queryStyle.allowReserved;
982
+ }
983
+ parameters.push(deepParam);
984
+ } else {
985
+ if (!querySchema.properties) return;
986
+ const queryObjProps = querySchema.properties;
987
+ for (const [propName, propSchema] of Object.entries(queryObjProps)) {
988
+ const isRequired = querySchema.required?.includes(propName) ?? false;
989
+ const serialization = determineQuerySerialization(propSchema.type);
990
+ parameters.push({
991
+ name: propName,
992
+ in: "query",
993
+ required: isRequired,
994
+ schema: propSchema,
995
+ ...Object.keys(serialization).length > 0 ? serialization : {}
996
+ });
997
+ }
908
998
  }
909
999
  }
910
1000
  for (const paramIndex of operation.queryParamIndices) {
@@ -974,7 +1064,7 @@ function buildCookieParameters(operation, ctx, parameters) {
974
1064
  }
975
1065
 
976
1066
  // src/compiler/manifest/emit.ts
977
- var import_typescript6 = __toESM(require("typescript"), 1);
1067
+ var import_typescript7 = __toESM(require("typescript"), 1);
978
1068
  function generateManifest(controllers, checker, version, validationMode = "ajv-runtime") {
979
1069
  const components = /* @__PURE__ */ new Map();
980
1070
  const ctx = {
@@ -995,7 +1085,7 @@ function generateManifest(controllers, checker, version, validationMode = "ajv-r
995
1085
  generator: {
996
1086
  name: "adorn-api",
997
1087
  version,
998
- typescript: import_typescript6.default.version
1088
+ typescript: import_typescript7.default.version
999
1089
  },
1000
1090
  schemas: {
1001
1091
  kind: "openapi-3.1",
@@ -1080,21 +1170,38 @@ function buildQueryArgs(op, ctx, args) {
1080
1170
  if (op.queryObjectParamIndex !== null) {
1081
1171
  const queryParam = op.parameters[op.queryObjectParamIndex];
1082
1172
  if (queryParam) {
1173
+ const queryStyle = extractQueryStyleOptions(ctx.checker, op.methodDeclaration);
1083
1174
  const querySchema = typeToJsonSchema(queryParam.type, ctx);
1084
- if (!querySchema.properties) return;
1085
- for (const [propName, propSchema] of Object.entries(querySchema.properties)) {
1086
- const isRequired = querySchema.required?.includes(propName) ?? false;
1087
- let schemaRef = propSchema.$ref;
1088
- if (!schemaRef) {
1089
- schemaRef = "#/components/schemas/InlineQueryParam";
1090
- }
1175
+ if (queryStyle?.style === "deepObject") {
1176
+ const schemaRef = querySchema.$ref ?? "#/components/schemas/InlineQueryParam";
1091
1177
  args.query.push({
1092
- name: propName,
1178
+ name: queryParam.name,
1093
1179
  index: queryParam.index,
1094
- required: !isRequired,
1180
+ required: !queryParam.isOptional,
1095
1181
  schemaRef,
1096
- schemaType: propSchema.type
1182
+ schemaType: querySchema.type,
1183
+ serialization: {
1184
+ style: "deepObject",
1185
+ explode: queryStyle.explode ?? true,
1186
+ allowReserved: queryStyle.allowReserved
1187
+ }
1097
1188
  });
1189
+ } else {
1190
+ if (!querySchema.properties) return;
1191
+ for (const [propName, propSchema] of Object.entries(querySchema.properties)) {
1192
+ const isRequired = querySchema.required?.includes(propName) ?? false;
1193
+ let schemaRef = propSchema.$ref;
1194
+ if (!schemaRef) {
1195
+ schemaRef = "#/components/schemas/InlineQueryParam";
1196
+ }
1197
+ args.query.push({
1198
+ name: propName,
1199
+ index: queryParam.index,
1200
+ required: !isRequired,
1201
+ schemaRef,
1202
+ schemaType: propSchema.type
1203
+ });
1204
+ }
1098
1205
  }
1099
1206
  }
1100
1207
  }
@@ -1297,7 +1404,7 @@ export function validateResponse(operationId, status, contentType, data) {
1297
1404
  // src/compiler/cache/isStale.ts
1298
1405
  var import_node_fs3 = __toESM(require("fs"), 1);
1299
1406
  var import_node_path3 = __toESM(require("path"), 1);
1300
- var import_typescript7 = require("typescript");
1407
+ var import_typescript8 = require("typescript");
1301
1408
  var import_meta = {};
1302
1409
  function readJson(p) {
1303
1410
  try {
@@ -1404,7 +1511,7 @@ async function isStale(params) {
1404
1511
  // src/compiler/cache/writeCache.ts
1405
1512
  var import_node_fs4 = __toESM(require("fs"), 1);
1406
1513
  var import_node_path4 = __toESM(require("path"), 1);
1407
- var import_typescript8 = __toESM(require("typescript"), 1);
1514
+ var import_typescript9 = __toESM(require("typescript"), 1);
1408
1515
  function statMtimeMs2(p) {
1409
1516
  return import_node_fs4.default.statSync(p).mtimeMs;
1410
1517
  }
@@ -1437,7 +1544,7 @@ function writeCache(params) {
1437
1544
  generator: {
1438
1545
  name: "adorn-api",
1439
1546
  version: params.adornVersion,
1440
- typescript: import_typescript8.default.version
1547
+ typescript: import_typescript9.default.version
1441
1548
  },
1442
1549
  project: {
1443
1550
  tsconfigPath: params.tsconfigAbs,
@@ -1450,7 +1557,7 @@ function writeCache(params) {
1450
1557
  }
1451
1558
 
1452
1559
  // src/cli.ts
1453
- var import_typescript9 = __toESM(require("typescript"), 1);
1560
+ var import_typescript10 = __toESM(require("typescript"), 1);
1454
1561
  var import_node_process = __toESM(require("process"), 1);
1455
1562
  var ADORN_VERSION = "0.1.0";
1456
1563
  function log(msg) {
@@ -1478,7 +1585,7 @@ async function buildCommand(args) {
1478
1585
  outDir: outputDir,
1479
1586
  project: projectPath,
1480
1587
  adornVersion: ADORN_VERSION,
1481
- typescriptVersion: import_typescript9.default.version
1588
+ typescriptVersion: import_typescript10.default.version
1482
1589
  });
1483
1590
  if (!stale.stale) {
1484
1591
  log("adorn-api: artifacts up-to-date");