@nxgt/typespec 0.11.0 → 0.13.0

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
@@ -2,7 +2,8 @@
2
2
 
3
3
  nxgt's HTTP conventions as a [TypeSpec](https://typespec.io) library, so an
4
4
  API spec states them in one word instead of rewriting them. Compile the spec to
5
- OpenAPI 3.1 or 3.2 with `@typespec/openapi3`, then generate the code with
5
+ OpenAPI 3.1 or 3.2 with its emitter, `@nxgt/typespec` (`@typespec/openapi3`, and a
6
+ real `QUERY` in 3.2), then generate the code with
6
7
  [`@nxgt/openapi-codegen`](https://github.com/softistx/nxgt-http/blob/develop/packages/openapi-codegen/README.md).
7
8
  Each shape here is one the `@nxgt/*` packages already send on the wire.
8
9
 
@@ -18,20 +19,22 @@ bun add -d @nxgt/typespec @typespec/compiler @typespec/http @typespec/openapi @t
18
19
 
19
20
  `@typespec/compiler`, `@typespec/http` and `@typespec/openapi` 1.16 or later
20
21
  are peer dependencies,
21
- and `typescript` 6 or 7 too, as for every `@nxgt` package. `@typespec/openapi3`
22
- compiles the spec, and `@nxgt/openapi-codegen` generates the code from it.
22
+ and `typescript` 6 or 7 too, as for every `@nxgt` package.
23
+ `@typespec/openapi3` 1.16 or later is an optional peer, loaded only when the
24
+ spec is emitted: install it to compile the spec to OpenAPI.
25
+ `@nxgt/openapi-codegen` generates the code from the document.
23
26
  The generator's CLI, `nxgt-openapi`, runs on [Bun](https://bun.sh): it needs
24
27
  `bun` on the PATH, even when called through `npx` or an npm script.
25
- Emit OpenAPI 3.1 or 3.2 in `tspconfig.yaml`: `@typespec/openapi3` emits 3.0
26
- by default, and the generator refuses it. Every convention here is compiled
28
+ Emit OpenAPI 3.1 or 3.2 in `tspconfig.yaml` with this package's emitter:
29
+ `@typespec/openapi3` emits 3.0 by default, and the generator refuses it. Every convention here is compiled
27
30
  to both in CI, and generates the same code from either.
28
31
 
29
32
  ```yaml
30
33
  # api/tspconfig.yaml
31
34
  emit:
32
- - '@typespec/openapi3'
35
+ - '@nxgt/typespec'
33
36
  options:
34
- '@typespec/openapi3':
37
+ '@nxgt/typespec':
35
38
  openapi-versions: ['3.1.0']
36
39
  emitter-output-dir: '{project-root}/../openapi'
37
40
  output-file: openapi.yaml
@@ -56,7 +59,7 @@ It installs the compiler, `@typespec/http`, `@typespec/openapi`,
56
59
  `@typespec/openapi3`, this package and `@nxgt/openapi-codegen`, and writes:
57
60
 
58
61
  - `tspconfig.yaml`: the linter's recommended rules, and OpenAPI 3.1 written
59
- to `openapi/openapi.yaml`;
62
+ to `openapi/openapi.yaml` by the emitter `@nxgt/typespec`;
60
63
  - `main.tsp`: the service in the namespace `Api`, with `@operationIds`, and a
61
64
  `Users` resource with a paged, sorted list, `get`, `create`, `update` and
62
65
  `delete`, and their error aliases;
@@ -456,6 +459,58 @@ The verbs `verbs` adds are not checked, nor an operation with its own
456
459
  `@operationId`. More, with each verb's methods, in
457
460
  [Operation ids](docs/guide/operation-ids.md).
458
461
 
462
+ ### A search with a body
463
+
464
+ `QUERY` is the HTTP method for a safe request with a body, and
465
+ `@typespec/http` 1.17 declares none. Mark a `@post` with `@queryMethod`, and
466
+ emit OpenAPI 3.2 with the emitter [`@nxgt/typespec`](#the-emitter): the
467
+ document gets a real `query` operation. Only 3.2 has one, so the mark with
468
+ any older version in `openapi-versions`, or with the plain `@typespec/openapi3`
469
+ emitter, is an error, not a `POST`: `query-method-needs-openapi-3.2`,
470
+ `query-method-needs-nxgt-emitter`.
471
+
472
+ ```tsp
473
+ @route("/authors")
474
+ interface Authors {
475
+ @post @queryMethod @route("/search")
476
+ search(@body criteria: AuthorCriteria): Page<Author> | BadRequest; // searchAuthors
477
+ }
478
+ ```
479
+
480
+ `search` and `query` accept a `GET`, a `POST` or a marked `POST` without the
481
+ `verb-method-mismatch` warning; `findById` or `create` marked warn, as a
482
+ `QUERY` is not their method. The decorator on anything but a `@post` is the
483
+ error `query-method-not-post`. When `@typespec/http` declares `QUERY`, the
484
+ decorator is what changes, not your spec. More, in
485
+ [Operation ids](docs/guide/operation-ids.md#a-query-in-openapi-32) and
486
+ [The emitter](docs/guide/emitter.md).
487
+
488
+ ### The emitter
489
+
490
+ `@nxgt/typespec` is also a TypeSpec emitter. It runs `@typespec/openapi3` with
491
+ the options and the files you give that one, and in an OpenAPI 3.2 document
492
+ moves each `@post` marked `@queryMethod` to a `query` operation, in place and
493
+ without `x-nxgt-method`. Without a marked operation its output is
494
+ byte-identical to `@typespec/openapi3`'s. With a marked operation and an
495
+ older version in `openapi-versions` (unset means 3.0), it reports
496
+ `query-method-needs-openapi-3.2` and emits nothing.
497
+
498
+ ```yaml
499
+ # api/tspconfig.yaml
500
+ emit:
501
+ - '@nxgt/typespec'
502
+ options:
503
+ '@nxgt/typespec':
504
+ openapi-versions: ['3.2.0'] # 3.1.0 is an error beside a @queryMethod
505
+ emitter-output-dir: '{project-root}/../openapi'
506
+ output-file: openapi.yaml
507
+ ```
508
+
509
+ `@nxgt/openapi-codegen` then generates `method: 'query'`, `@nxgt/openapi-hono`
510
+ serves `.query(...)`, and the `@nxgt/httpyz` client sends `QUERY`. A real
511
+ `QUERY` needs a server stack that accepts the method, and a preflight under
512
+ CORS: [The emitter](docs/guide/emitter.md#a-query-on-the-wire).
513
+
459
514
  ### Linter
460
515
 
461
516
  Extend the library's ruleset in `tspconfig.yaml`, and `tsp compile` warns
@@ -490,12 +545,21 @@ passing example of each rule, in [Linter](docs/guide/linter.md).
490
545
 
491
546
  | Decorator | On | What it does |
492
547
  | --- | --- | --- |
548
+ | `@queryMethod` | an operation | marks a `@post` as a `QUERY`: needs the emitter `@nxgt/typespec` and OpenAPI 3.2, where the document gets a `query` operation, and is an error otherwise; `@operationIds` checks its verb as a `QUERY`'s |
493
549
  | `@operationIds(options?: OperationIdsOptions)` | a namespace or an interface | makes each operation's id its name, as written, or a known verb with the interface's resource (`list` in `Users` is `listUsers`), in the namespace however deep or in the interface, unless it has an `@operationId` |
494
550
 
495
551
  `OperationIdsOptions` is `#{ singular?: string, plural?: string, verbs?: Record<"singular" | "plural"> }`:
496
552
  `singular` and `plural` name an interface's resource, and `verbs` adds or
497
553
  overrides verbs, on a namespace or an interface.
498
554
 
555
+ `@queryMethod` has no options.
556
+
557
+ ### Emitter
558
+
559
+ | Name | In | What it does |
560
+ | --- | --- | --- |
561
+ | `@nxgt/typespec` | `emit:` in `tspconfig.yaml` | `@typespec/openapi3` with its options and files; each marked `@post` becomes a `query` operation in a 3.2 document, and a marked one with an older version is an error. Needs the optional peer `@typespec/openapi3` |
562
+
499
563
  ### Diagnostics
500
564
 
501
565
  | Code | Reported when |
@@ -504,7 +568,11 @@ overrides verbs, on a namespace or an interface.
504
568
  | `resource-name-on-namespace` | error: `@operationIds` on a namespace is given `singular` or `plural`, which name an interface's resource |
505
569
  | `duplicate-status-reply` | error: an operation declares a reply without a body and one with a body of one status, which the emitter merges, losing the one without a body |
506
570
  | `merged-status-reply` | warning: an operation declares two replies with a body of one status code, which the emitter merges under the first one's description |
507
- | `verb-method-mismatch` | warning: an operation `@operationIds` names after one of the library's verbs, in an interface, is sent with a method that verb does not name: `create` with a `GET` |
571
+ | `verb-method-mismatch` | warning: an operation `@operationIds` names after one of the library's verbs, in an interface, is sent with a method that verb does not name: `create` with a `GET`, or marked `@queryMethod` |
572
+ | `query-method-not-post` | error: an operation marked `@queryMethod` is not a `@post` |
573
+ | `query-method-needs-openapi-3.2` | error: the emitter `@nxgt/typespec` is asked for a version below 3.2 (or none, which is 3.0) beside a `@queryMethod`; nothing is emitted |
574
+ | `query-method-needs-nxgt-emitter` | error: `@typespec/openapi3` is in `emit` beside a `@queryMethod` |
575
+ | `openapi3-missing` | error: the emitter `@nxgt/typespec` runs `@typespec/openapi3`, which is not installed |
508
576
 
509
577
  ### Linter rules
510
578
 
@@ -663,6 +731,8 @@ envelope and the rate limit's headers.
663
731
 
664
732
  - [Getting started](docs/guide/getting-started.md): the `tsp init`
665
733
  template, the files it writes, and the steps from the spec to a Hono route;
734
+ - [The emitter](docs/guide/emitter.md): `@nxgt/typespec` in `tspconfig.yaml`,
735
+ a `@queryMethod` as a real `QUERY` in OpenAPI 3.2, and what that needs;
666
736
  - [Error replies](docs/guide/errors.md): the envelope, each response, the
667
737
  aliases by verb, and what the generator makes of them;
668
738
  - [Pagination](docs/guide/pagination.md): offset and cursor pages, and
@@ -681,7 +751,7 @@ envelope and the rate limit's headers.
681
751
  and `PatchRequest`, and naming your own;
682
752
  - [Operation ids](docs/guide/operation-ids.md): `@operationIds`, each
683
753
  operation named as written or a verb with its resource, the method each
684
- verb is sent with, the options, and the ids it refuses;
754
+ verb is sent with, `@queryMethod`, the options, and the ids it refuses;
685
755
  - [Linter](docs/guide/linter.md): the ruleset `@nxgt/typespec/recommended`,
686
756
  each rule with a failing and a passing spec, and how to turn one off;
687
757
  - [troubleshooting](docs/troubleshooting.md);
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The emitter `@nxgt/typespec`: `@typespec/openapi3`, with its options,
3
+ * checked by its own schema, and its files, and each `POST` marked
4
+ * `@queryMethod` a real `QUERY` in an OpenAPI 3.2 document
5
+ * (`query-operations.ts`). Only 3.2 has a `QUERY`: a marked operation with
6
+ * an older version asked for is an error, and nothing is emitted.
7
+ * `@typespec/openapi3` is loaded only here, so a spec that only imports the
8
+ * library does not need it.
9
+ */
10
+ import { type EmitContext } from '@typespec/compiler';
11
+ export declare function $onEmit(context: EmitContext): Promise<void>;
12
+ //# sourceMappingURL=emitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../src/emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAGN,KAAK,WAAW,EAEhB,MAAM,oBAAoB,CAAC;AAmE5B,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BjE"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Each HTTP operation of each service, once, as the emitter sends it: a
3
+ * service nested in another lists its operations in both. The services' own
4
+ * diagnostics are the emitter's to report.
5
+ */
6
+ import type { Program } from '@typespec/compiler';
7
+ import { type HttpOperation } from '@typespec/http';
8
+ export declare function eachHttpOperation(program: Program, visit: (operation: HttpOperation) => void): void;
9
+ //# sourceMappingURL=http-operations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-operations.d.ts","sourceRoot":"","sources":["../src/http-operations.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAExE,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,GACvC,IAAI,CAUN"}
package/dist/index.d.ts CHANGED
@@ -8,12 +8,15 @@
8
8
  */
9
9
  import type { Program } from '@typespec/compiler';
10
10
  import { operationIds } from './operation-ids';
11
+ import { queryMethod } from './query-method';
12
+ export { $onEmit } from './emitter';
11
13
  export { $lib } from './lib';
12
14
  export { $linter } from './linter';
13
15
  export declare function $onValidate(program: Program): void;
14
16
  export declare const $decorators: {
15
17
  Nxgt: {
16
18
  operationIds: typeof operationIds;
19
+ queryMethod: typeof queryMethod;
17
20
  };
18
21
  };
19
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAwB,MAAM,iBAAiB,CAAC;AAGrE,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAMlD;AAED,eAAO,MAAM,WAAW;;;;CAEvB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAwB,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,WAAW,EAAwB,MAAM,gBAAgB,CAAC;AAGnE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAOlD;AAED,eAAO,MAAM,WAAW;;;;;CAEvB,CAAC"}
package/dist/index.js CHANGED
@@ -1,8 +1,20 @@
1
1
  // src/one-reply-per-status.ts
2
2
  import { isVoidType } from "@typespec/compiler";
3
- import {
4
- getAllHttpServices
5
- } from "@typespec/http";
3
+
4
+ // src/http-operations.ts
5
+ import { getAllHttpServices } from "@typespec/http";
6
+ function eachHttpOperation(program, visit) {
7
+ const [services] = getAllHttpServices(program);
8
+ const visited = new Set;
9
+ for (const service of services) {
10
+ for (const operation of service.operations) {
11
+ if (visited.has(operation.operation))
12
+ continue;
13
+ visited.add(operation.operation);
14
+ visit(operation);
15
+ }
16
+ }
17
+ }
6
18
 
7
19
  // src/lib.ts
8
20
  import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler";
@@ -27,6 +39,30 @@ var $lib = createTypeSpecLibrary({
27
39
  default: paramMessage`${"operation"} is sent with ${"method"}, where its verb ${"verb"} is sent with ${"expected"}. Give it that method, or a name that is not a verb.`
28
40
  }
29
41
  },
42
+ "query-method-not-post": {
43
+ severity: "error",
44
+ messages: {
45
+ default: paramMessage`${"operation"} is marked @queryMethod and sent with ${"method"}: @queryMethod marks a @post, which @nxgt/typespec writes as a QUERY in OpenAPI 3.2. Make it a @post.`
46
+ }
47
+ },
48
+ "query-method-needs-openapi-3.2": {
49
+ severity: "error",
50
+ messages: {
51
+ default: paramMessage`${"operation"} is marked @queryMethod: only OpenAPI 3.2 has a QUERY operation, and openapi-versions asks for ${"version"}. Emit 3.2 only, or take off @queryMethod.`
52
+ }
53
+ },
54
+ "query-method-needs-nxgt-emitter": {
55
+ severity: "error",
56
+ messages: {
57
+ default: paramMessage`${"operation"} is marked @queryMethod: emit with @nxgt/typespec, which writes it as a QUERY; @typespec/openapi3 would write a POST.`
58
+ }
59
+ },
60
+ "openapi3-missing": {
61
+ severity: "error",
62
+ messages: {
63
+ default: "The emitter @nxgt/typespec runs @typespec/openapi3, which is not installed: add it beside @nxgt/typespec."
64
+ }
65
+ },
30
66
  "duplicate-status-reply": {
31
67
  severity: "error",
32
68
  messages: {
@@ -40,7 +76,11 @@ var $lib = createTypeSpecLibrary({
40
76
  }
41
77
  }
42
78
  },
79
+ capabilities: { dryRun: true },
43
80
  state: {
81
+ queryMethod: {
82
+ description: "The operations marked with @queryMethod"
83
+ },
44
84
  operationIds: {
45
85
  description: "The interfaces and namespaces marked with @operationIds"
46
86
  },
@@ -75,28 +115,21 @@ function mergeOf({
75
115
  return;
76
116
  }
77
117
  function validateOneReplyPerStatus(program) {
78
- const [services] = getAllHttpServices(program);
79
- const checked = new Set;
80
- for (const service of services) {
81
- for (const operation of service.operations) {
82
- if (checked.has(operation.operation))
118
+ eachHttpOperation(program, (operation) => {
119
+ for (const response of operation.responses) {
120
+ const code = mergeOf(response);
121
+ if (code === undefined)
83
122
  continue;
84
- checked.add(operation.operation);
85
- for (const response of operation.responses) {
86
- const code = mergeOf(response);
87
- if (code === undefined)
88
- continue;
89
- $lib.reportDiagnostic(program, {
90
- code,
91
- format: {
92
- operation: operation.operation.name,
93
- status: statusOf(response.statusCodes)
94
- },
95
- target: operation.operation
96
- });
97
- }
123
+ $lib.reportDiagnostic(program, {
124
+ code,
125
+ format: {
126
+ operation: operation.operation.name,
127
+ status: statusOf(response.statusCodes)
128
+ },
129
+ target: operation.operation
130
+ });
98
131
  }
99
- }
132
+ });
100
133
  }
101
134
 
102
135
  // src/operation-ids.ts
@@ -137,8 +170,8 @@ var METHODS = {
137
170
  find: ["get", "head"],
138
171
  count: ["get", "head"],
139
172
  get: ["get", "head"],
140
- search: ["get", "post"],
141
- query: ["get", "post"],
173
+ search: ["get", "post", "query"],
174
+ query: ["get", "post", "query"],
142
175
  create: ["post"],
143
176
  createMany: ["post"],
144
177
  update: ["put", "patch"],
@@ -314,41 +347,182 @@ function validateOperationIds(program) {
314
347
  });
315
348
  }
316
349
 
350
+ // src/query-method.ts
351
+ import { setExtension } from "@typespec/openapi";
352
+ function queryMethod(context, target) {
353
+ const { program } = context;
354
+ program.stateSet($lib.stateKeys.queryMethod).add(target);
355
+ setExtension(program, target, "x-nxgt-method", "query");
356
+ }
357
+ function isQueryMethod(program, operation) {
358
+ return program.stateSet($lib.stateKeys.queryMethod).has(operation);
359
+ }
360
+ function queryMethods(program) {
361
+ return [...program.stateSet($lib.stateKeys.queryMethod)];
362
+ }
363
+ function validateQueryMethods(program) {
364
+ eachHttpOperation(program, ({ operation, verb: method }) => {
365
+ if (!isQueryMethod(program, operation) || method === "post")
366
+ return;
367
+ $lib.reportDiagnostic(program, {
368
+ code: "query-method-not-post",
369
+ format: { operation: operation.name, method: method.toUpperCase() },
370
+ target: operation
371
+ });
372
+ });
373
+ if (!(program.compilerOptions.emit ?? []).includes("@typespec/openapi3")) {
374
+ return;
375
+ }
376
+ for (const operation of queryMethods(program)) {
377
+ $lib.reportDiagnostic(program, {
378
+ code: "query-method-needs-nxgt-emitter",
379
+ format: { operation: operation.name },
380
+ target: operation
381
+ });
382
+ }
383
+ }
384
+
317
385
  // src/verb-methods.ts
318
- import { getAllHttpServices as getAllHttpServices2 } from "@typespec/http";
319
386
  import { getOperationId as getOperationId2 } from "@typespec/openapi";
320
387
  function methodsOf(methods) {
321
388
  return methods.map((method) => method.toUpperCase()).join(" or ");
322
389
  }
323
390
  function validateVerbMethods(program) {
324
- const [services] = getAllHttpServices2(program);
325
- const checked = new Set;
326
- for (const service of services) {
327
- for (const { operation, verb: method } of service.operations) {
328
- if (checked.has(operation))
329
- continue;
330
- checked.add(operation);
331
- if (!isNamed(program, operation))
332
- continue;
333
- if (getOperationId2(program, operation) !== undefined)
334
- continue;
335
- const verb = verbOf(program, operation);
336
- if (verb === undefined || !Object.hasOwn(METHODS, verb))
337
- continue;
338
- const expected = METHODS[verb];
339
- if (expected.includes(method))
340
- continue;
341
- $lib.reportDiagnostic(program, {
342
- code: "verb-method-mismatch",
343
- format: {
344
- operation: operation.name,
345
- method: method.toUpperCase(),
346
- verb,
347
- expected: methodsOf(expected)
348
- },
349
- target: operation
350
- });
391
+ eachHttpOperation(program, ({ operation, verb: sent }) => {
392
+ if (!isNamed(program, operation))
393
+ return;
394
+ if (getOperationId2(program, operation) !== undefined)
395
+ return;
396
+ const verb = verbOf(program, operation);
397
+ if (verb === undefined || !Object.hasOwn(METHODS, verb))
398
+ return;
399
+ const method = sent === "post" && isQueryMethod(program, operation) ? "query" : sent;
400
+ const expected = METHODS[verb];
401
+ if (expected.includes(method))
402
+ return;
403
+ $lib.reportDiagnostic(program, {
404
+ code: "verb-method-mismatch",
405
+ format: {
406
+ operation: operation.name,
407
+ method: method.toUpperCase(),
408
+ verb,
409
+ expected: methodsOf(expected)
410
+ },
411
+ target: operation
412
+ });
413
+ });
414
+ }
415
+
416
+ // src/emitter.ts
417
+ import {
418
+ NoTarget
419
+ } from "@typespec/compiler";
420
+
421
+ // src/query-operations.ts
422
+ import { parse, stringify } from "yaml";
423
+ function moveQueries(document) {
424
+ if (!String(document["openapi"]).startsWith("3.2"))
425
+ return false;
426
+ const paths = document["paths"];
427
+ if (typeof paths !== "object" || paths === null)
428
+ return false;
429
+ let moved = false;
430
+ for (const [path, item] of Object.entries(paths)) {
431
+ const post = item["post"];
432
+ if (post?.["x-nxgt-method"] !== "query" || item["query"] !== undefined) {
433
+ continue;
434
+ }
435
+ const { "x-nxgt-method": _, ...operation } = post;
436
+ paths[path] = Object.fromEntries(Object.entries(item).map(([key, value]) => key === "post" ? ["query", operation] : [key, value]));
437
+ moved = true;
438
+ }
439
+ return moved;
440
+ }
441
+ function withQueryOperations(content) {
442
+ const json = content.trimStart().startsWith("{");
443
+ const document = json ? JSON.parse(content) : parse(content);
444
+ if (typeof document !== "object" || document === null)
445
+ return content;
446
+ if (!moveQueries(document))
447
+ return content;
448
+ const text = json ? `${JSON.stringify(document, null, 2)}
449
+ ` : stringify(document, {
450
+ singleQuote: true,
451
+ aliasDuplicateObjects: false,
452
+ lineWidth: 0,
453
+ compat: "yaml-1.1"
454
+ });
455
+ return content.includes(`\r
456
+ `) ? text.replace(/\n/g, `\r
457
+ `) : text;
458
+ }
459
+
460
+ // src/emitter.ts
461
+ var DEFAULT_VERSIONS = ["3.0.0"];
462
+ function invalidOptions(openapi3, options) {
463
+ const lib = openapi3.$lib;
464
+ const validator = "emitterOptionValidator" in lib ? lib.emitterOptionValidator : undefined;
465
+ if (typeof validator !== "object" || validator === null || !("validate" in validator) || typeof validator.validate !== "function") {
466
+ return [];
467
+ }
468
+ return validator.validate(options, NoTarget);
469
+ }
470
+ async function loadOpenAPI3(context) {
471
+ try {
472
+ return await import("@typespec/openapi3");
473
+ } catch {
474
+ $lib.reportDiagnostic(context.program, {
475
+ code: "openapi3-missing",
476
+ target: NoTarget
477
+ });
478
+ return;
479
+ }
480
+ }
481
+ function queriesFit(context) {
482
+ const { program } = context;
483
+ const marked = queryMethods(program);
484
+ if (marked.length === 0)
485
+ return true;
486
+ const versions = context.options["openapi-versions"] ?? DEFAULT_VERSIONS;
487
+ const older = versions.find((version) => !version.startsWith("3.2"));
488
+ if (older === undefined)
489
+ return true;
490
+ for (const operation of marked) {
491
+ $lib.reportDiagnostic(program, {
492
+ code: "query-method-needs-openapi-3.2",
493
+ format: { operation: operation.name, version: older },
494
+ target: operation
495
+ });
496
+ }
497
+ return false;
498
+ }
499
+ async function $onEmit(context) {
500
+ const openapi3 = await loadOpenAPI3(context);
501
+ if (openapi3 === undefined)
502
+ return;
503
+ const { program } = context;
504
+ const invalid = invalidOptions(openapi3, context.options);
505
+ if (invalid.length > 0) {
506
+ program.reportDiagnostics(invalid);
507
+ return;
508
+ }
509
+ if (!queriesFit(context))
510
+ return;
511
+ const host = program.host;
512
+ const writing = new Proxy(host, {
513
+ get(target, key) {
514
+ if (key === "writeFile") {
515
+ return (path, content) => target.writeFile(path, withQueryOperations(content));
516
+ }
517
+ const value = Reflect.get(target, key, target);
518
+ return typeof value === "function" ? value.bind(target) : value;
351
519
  }
520
+ });
521
+ program.host = writing;
522
+ try {
523
+ await openapi3.$onEmit(context);
524
+ } finally {
525
+ program.host = host;
352
526
  }
353
527
  }
354
528
  // src/linter.ts
@@ -475,19 +649,21 @@ var $linter = defineLinter({
475
649
 
476
650
  // src/index.ts
477
651
  function $onValidate(program) {
652
+ validateQueryMethods(program);
478
653
  validateVerbMethods(program);
479
654
  validateOperationIds(program);
480
655
  validateOneReplyPerStatus(program);
481
656
  }
482
657
  var $decorators = {
483
- Nxgt: { operationIds }
658
+ Nxgt: { operationIds, queryMethod }
484
659
  };
485
660
  export {
486
661
  $decorators,
487
662
  $lib,
488
663
  $linter,
664
+ $onEmit,
489
665
  $onValidate
490
666
  };
491
667
 
492
- //# debugId=FB766460AB7EEFAD64756E2164756E21
668
+ //# debugId=3B14A4A66C69185964756E2164756E21
493
669
  //# sourceMappingURL=index.js.map