@redocly/client-generator 0.7.11 → 0.7.13

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.
Files changed (142) hide show
  1. package/eject-assets/generators/cli/docs.ts +1 -1
  2. package/eject-assets/generators/cli/engine-source.ts +1 -1
  3. package/eject-assets/generators/cli/index.ts +2 -2
  4. package/eject-assets/generators/cli/render.ts +1 -1
  5. package/eject-assets/generators/go/client.ts +1 -1
  6. package/eject-assets/generators/go/descriptor.ts +1 -1
  7. package/eject-assets/generators/go/index.ts +2 -2
  8. package/eject-assets/generators/go/models.ts +7 -2
  9. package/eject-assets/generators/go/naming.ts +1 -1
  10. package/eject-assets/generators/go/operations.ts +1 -1
  11. package/eject-assets/generators/go/pagination.ts +1 -1
  12. package/eject-assets/generators/go/types.ts +1 -1
  13. package/eject-assets/generators/mock/faker.ts +17 -9
  14. package/eject-assets/generators/mock/index.ts +4 -3
  15. package/eject-assets/generators/mock/render.ts +128 -9
  16. package/eject-assets/generators/mock/sample.ts +87 -4
  17. package/eject-assets/generators/mock/values.ts +1 -1
  18. package/eject-assets/generators/php/client.ts +1 -1
  19. package/eject-assets/generators/php/descriptor.ts +10 -1
  20. package/eject-assets/generators/php/index.ts +2 -2
  21. package/eject-assets/generators/php/models.ts +8 -3
  22. package/eject-assets/generators/php/naming.ts +1 -1
  23. package/eject-assets/generators/php/operations.ts +11 -6
  24. package/eject-assets/generators/php/pagination.ts +3 -2
  25. package/eject-assets/generators/php/types.ts +12 -2
  26. package/eject-assets/generators/python/client.ts +1 -1
  27. package/eject-assets/generators/python/descriptor.ts +1 -1
  28. package/eject-assets/generators/python/index.ts +3 -3
  29. package/eject-assets/generators/python/models.ts +9 -3
  30. package/eject-assets/generators/python/naming.ts +1 -1
  31. package/eject-assets/generators/python/operations.ts +6 -5
  32. package/eject-assets/generators/python/pagination.ts +1 -1
  33. package/eject-assets/generators/python/types.ts +1 -1
  34. package/eject-assets/generators/swr/index.ts +2 -2
  35. package/eject-assets/generators/swr/render.ts +1 -1
  36. package/eject-assets/generators/tanstack-query/index.ts +2 -2
  37. package/eject-assets/generators/tanstack-query/render.ts +6 -5
  38. package/eject-assets/generators/transformers/index.ts +2 -2
  39. package/eject-assets/generators/transformers/render.ts +34 -7
  40. package/eject-assets/generators/typescript/banner.ts +1 -1
  41. package/eject-assets/generators/typescript/client.ts +1 -1
  42. package/eject-assets/generators/typescript/descriptor.ts +1 -1
  43. package/eject-assets/generators/typescript/index.ts +2 -2
  44. package/eject-assets/generators/typescript/inline-runtime.ts +1 -1
  45. package/eject-assets/generators/typescript/operation-signature.ts +1 -1
  46. package/eject-assets/generators/typescript/operation-types.ts +1 -1
  47. package/eject-assets/generators/typescript/operations.ts +1 -1
  48. package/eject-assets/generators/typescript/response-headers.ts +1 -1
  49. package/eject-assets/generators/typescript/type-guards.ts +1 -1
  50. package/eject-assets/generators/typescript/types.ts +1 -1
  51. package/eject-assets/generators/zod/index.ts +2 -2
  52. package/eject-assets/generators/zod/schemas.ts +1 -1
  53. package/eject-assets/skills/go-generator/SKILL.md +3 -0
  54. package/eject-assets/skills/mock-generator/SKILL.md +22 -0
  55. package/eject-assets/skills/php-generator/SKILL.md +9 -2
  56. package/eject-assets/skills/python-generator/SKILL.md +6 -0
  57. package/eject-assets/skills/tanstack-query-generator/SKILL.md +2 -1
  58. package/eject-assets/skills/transformers-generator/SKILL.md +4 -0
  59. package/eject-assets/skills/typescript-generator/SKILL.md +5 -0
  60. package/lib/authoring/schema.d.ts.map +1 -1
  61. package/lib/authoring/schema.js +4 -1
  62. package/lib/authoring/schema.js.map +1 -1
  63. package/lib/generators/go/models.d.ts.map +1 -1
  64. package/lib/generators/go/models.js +4 -1
  65. package/lib/generators/go/models.js.map +1 -1
  66. package/lib/generators/mock/faker.js +18 -8
  67. package/lib/generators/mock/faker.js.map +1 -1
  68. package/lib/generators/mock/index.d.ts.map +1 -1
  69. package/lib/generators/mock/index.js +2 -1
  70. package/lib/generators/mock/index.js.map +1 -1
  71. package/lib/generators/mock/render.d.ts +3 -1
  72. package/lib/generators/mock/render.d.ts.map +1 -1
  73. package/lib/generators/mock/render.js +119 -9
  74. package/lib/generators/mock/render.js.map +1 -1
  75. package/lib/generators/mock/sample.d.ts +18 -1
  76. package/lib/generators/mock/sample.d.ts.map +1 -1
  77. package/lib/generators/mock/sample.js +71 -2
  78. package/lib/generators/mock/sample.js.map +1 -1
  79. package/lib/generators/php/descriptor.d.ts +6 -0
  80. package/lib/generators/php/descriptor.d.ts.map +1 -1
  81. package/lib/generators/php/descriptor.js +8 -0
  82. package/lib/generators/php/descriptor.js.map +1 -1
  83. package/lib/generators/php/models.d.ts.map +1 -1
  84. package/lib/generators/php/models.js +5 -2
  85. package/lib/generators/php/models.js.map +1 -1
  86. package/lib/generators/php/operations.d.ts.map +1 -1
  87. package/lib/generators/php/operations.js +5 -5
  88. package/lib/generators/php/operations.js.map +1 -1
  89. package/lib/generators/php/pagination.d.ts.map +1 -1
  90. package/lib/generators/php/pagination.js +2 -1
  91. package/lib/generators/php/pagination.js.map +1 -1
  92. package/lib/generators/php/types.d.ts.map +1 -1
  93. package/lib/generators/php/types.js +13 -2
  94. package/lib/generators/php/types.js.map +1 -1
  95. package/lib/generators/python/index.js +1 -1
  96. package/lib/generators/python/index.js.map +1 -1
  97. package/lib/generators/python/models.d.ts.map +1 -1
  98. package/lib/generators/python/models.js +6 -2
  99. package/lib/generators/python/models.js.map +1 -1
  100. package/lib/generators/python/operations.d.ts.map +1 -1
  101. package/lib/generators/python/operations.js +5 -4
  102. package/lib/generators/python/operations.js.map +1 -1
  103. package/lib/generators/tanstack-query/render.js +5 -4
  104. package/lib/generators/tanstack-query/render.js.map +1 -1
  105. package/lib/generators/transformers/render.d.ts +1 -1
  106. package/lib/generators/transformers/render.d.ts.map +1 -1
  107. package/lib/generators/transformers/render.js +24 -5
  108. package/lib/generators/transformers/render.js.map +1 -1
  109. package/lib/generators/typescript/runtime/create-client.js +1 -1
  110. package/lib/generators/typescript/runtime/create-client.js.map +1 -1
  111. package/lib/generators/typescript/runtime/paginate.d.ts.map +1 -1
  112. package/lib/generators/typescript/runtime/paginate.js +5 -4
  113. package/lib/generators/typescript/runtime/paginate.js.map +1 -1
  114. package/lib/generators/typescript/runtime/send.d.ts.map +1 -1
  115. package/lib/generators/typescript/runtime/send.js +10 -4
  116. package/lib/generators/typescript/runtime/send.js.map +1 -1
  117. package/lib/generators/typescript/runtime/sse.d.ts.map +1 -1
  118. package/lib/generators/typescript/runtime/sse.js +6 -1
  119. package/lib/generators/typescript/runtime/sse.js.map +1 -1
  120. package/lib/generators/typescript/telemetry/telemetry.d.ts.map +1 -1
  121. package/lib/generators/typescript/telemetry/telemetry.js +72 -11
  122. package/lib/generators/typescript/telemetry/telemetry.js.map +1 -1
  123. package/lib/intermediate-representation/build.d.ts.map +1 -1
  124. package/lib/intermediate-representation/build.js +51 -26
  125. package/lib/intermediate-representation/build.js.map +1 -1
  126. package/lib/intermediate-representation/model.d.ts +6 -2
  127. package/lib/intermediate-representation/model.d.ts.map +1 -1
  128. package/lib/intermediate-representation/model.js.map +1 -1
  129. package/lib/runtime-contract.d.ts +1 -1
  130. package/lib/runtime-contract.d.ts.map +1 -1
  131. package/lib/runtime-contract.js.map +1 -1
  132. package/lib/runtime-sources/python.d.ts +2 -2
  133. package/lib/runtime-sources/python.js +2 -2
  134. package/lib/runtime-sources/python.js.map +1 -1
  135. package/lib/runtime-sources/telemetry.d.ts +1 -1
  136. package/lib/runtime-sources/telemetry.d.ts.map +1 -1
  137. package/lib/runtime-sources/telemetry.js +1 -1
  138. package/lib/runtime-sources/telemetry.js.map +1 -1
  139. package/lib/runtime-sources/typescript.d.ts +10 -10
  140. package/lib/runtime-sources/typescript.js +10 -10
  141. package/lib/runtime-sources/typescript.js.map +1 -1
  142. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "cli" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "cli" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator cli --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "cli" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "cli" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator cli --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "cli" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "cli" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator cli --update`.
@@ -86,5 +86,5 @@ export default {
86
86
  docs: cliDocs,
87
87
  requires: ["typescript","zod"],
88
88
  errorModes: ["throw"],
89
- requiresGenerator: '^0.7.11',
89
+ requiresGenerator: '^0.7.13',
90
90
  };
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "cli" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "cli" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator cli --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "go" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "go" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator go --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "go" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "go" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator go --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "go" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "go" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator go --update`.
@@ -332,5 +332,5 @@ export default {
332
332
  "argsStyle": "inputs follow the target language's own idiom",
333
333
  "importExt": "the generated file has no relative imports"
334
334
  },
335
- requiresGenerator: '^0.7.11',
335
+ requiresGenerator: '^0.7.13',
336
336
  };
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "go" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "go" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator go --update`.
@@ -26,11 +26,16 @@ function writeStruct(
26
26
  dateType: DateType,
27
27
  description?: string
28
28
  ): void {
29
+ // A response never carries a `writeOnly` property, and responses decode into this same
30
+ // struct, so the property is optional even when the description requires it.
31
+ const fields = properties.map((property) =>
32
+ property.writeOnly ? { ...property, required: false } : property
33
+ );
29
34
  printer.doc(exported(name), description);
30
35
  printer.block(
31
36
  `type ${exported(name)} struct {`,
32
37
  () => {
33
- for (const property of properties) {
38
+ for (const property of fields) {
34
39
  const field = exported(property.name);
35
40
  let fieldType = goType(property.schema, dateType);
36
41
  let tag = `\`json:"${property.name}"\``;
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "go" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "go" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator go --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "go" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "go" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator go --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "go" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "go" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator go --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "go" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "go" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator go --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "mock" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "mock" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator mock --update`.
@@ -22,7 +22,7 @@ import {
22
22
  } from '@redocly/client-generator';
23
23
  import { codeLiteral } from '@redocly/client-generator/printers/typescript';
24
24
 
25
- import { splitIntersection } from './sample.ts';
25
+ import { numericRange, omitsFromResponse, splitIntersection } from './sample.ts';
26
26
  import { expr, isObjectValue, type MockEntry, type MockValue, objectValue } from './values.ts';
27
27
 
28
28
  /** The faker-call value for an IR schema. Refs resolve against `schemas`;
@@ -69,6 +69,7 @@ function walk(
69
69
  case 'object':
70
70
  return objectValue(
71
71
  schema.properties.flatMap((p): MockEntry[] => {
72
+ if (omitsFromResponse(p)) return [];
72
73
  const value = walk(p.schema, byName, visiting, dateType);
73
74
  // A cyclic optional property is omitted; a cyclic required property is
74
75
  // uninhabitable, so null is the only stand-in.
@@ -142,8 +143,8 @@ function scalarExpr(
142
143
  ): string {
143
144
  if (meta?.format === 'binary') return 'new Blob([])';
144
145
  if (scalar === 'boolean') return 'faker.datatype.boolean()';
145
- if (scalar === 'integer') return `faker.number.int(${boundsArg(meta)})`;
146
- if (scalar === 'number') return `faker.number.float(${boundsArg(meta)})`;
146
+ if (scalar === 'integer') return `faker.number.int(${boundsArg(scalar, meta)})`;
147
+ if (scalar === 'number') return `faker.number.float(${boundsArg(scalar, meta)})`;
147
148
  switch (meta?.format) {
148
149
  case 'email':
149
150
  return 'faker.internet.email()';
@@ -173,13 +174,20 @@ function dateExpr(dateType: DateType, dateOnly: boolean): string {
173
174
  return dateOnly ? `${iso}.slice(0, 10)` : iso;
174
175
  }
175
176
 
176
- /** `{ min, max }` arg for a bounded numeric, or empty when neither bound is set. */
177
- function boundsArg(meta: SchemaMetadata | undefined): string {
177
+ /**
178
+ * `{ min, max, multipleOf }` for a bounded numeric, or empty when unbounded. Both ends are
179
+ * always set, because faker's defaults (`0`, and `1` for floats) can sit on the wrong side
180
+ * of a single bound: the missing end is 100 steps away from the set one.
181
+ */
182
+ function boundsArg(scalar: 'integer' | 'number', meta: SchemaMetadata | undefined): string {
183
+ const { min, max, step = 1 } = numericRange(scalar, meta);
184
+ if (min === undefined && max === undefined) return '';
178
185
  const props = [
179
- ...(meta?.minimum !== undefined ? [`min: ${meta.minimum}`] : []),
180
- ...(meta?.maximum !== undefined ? [`max: ${meta.maximum}`] : []),
186
+ `min: ${min ?? (max ?? 0) - 100 * step}`,
187
+ `max: ${max ?? (min ?? 0) + 100 * step}`,
188
+ ...(meta?.multipleOf !== undefined ? [`multipleOf: ${meta.multipleOf}`] : []),
181
189
  ];
182
- return props.length > 0 ? `{ ${props.join(', ')} }` : '';
190
+ return `{ ${props.join(', ')} }`;
183
191
  }
184
192
 
185
193
  /** `faker.helpers.multiple(() => <item>, { count: 1 })` — one element keeps output small.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "mock" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "mock" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator mock --update`.
@@ -13,13 +13,14 @@ import { renderMockModule } from './render.ts';
13
13
  * sdk client stays dependency-free. Output-mode-agnostic in v1 — one module beside
14
14
  * the client. Emits nothing when there are no operations.
15
15
  */
16
- export const mockGenerator: Generator = ({ model, output, banner, emit }) => {
16
+ export const mockGenerator: Generator = ({ model, output, banner, emit, pagination }) => {
17
17
  const header = banner.map((line) => `// ${line}`).join('\n');
18
18
  const content = renderMockModule(model, {
19
19
  sdkModule: `./${output.stem}.${emit.importExt ?? 'js'}`,
20
20
  dateType: emit.dateType,
21
21
  mockData: emit.mockData,
22
22
  mockSeed: emit.mockSeed,
23
+ pagination,
23
24
  });
24
25
  if (content === '') return [];
25
26
  return [
@@ -31,5 +32,5 @@ export default {
31
32
  name: 'mock',
32
33
  run: mockGenerator,
33
34
  requires: ["typescript"],
34
- requiresGenerator: '^0.7.11',
35
+ requiresGenerator: '^0.7.13',
35
36
  };
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "mock" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "mock" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator mock --update`.
@@ -13,9 +13,13 @@ import {
13
13
  allOperations,
14
14
  type ApiModel,
15
15
  type DateType,
16
+ flattenAllOf,
17
+ isNullable,
18
+ type ModelPagination,
16
19
  type NamedSchemaModel,
17
20
  type OperationModel,
18
21
  type ResponseBodyModel,
22
+ schemaAtPointer,
19
23
  type SchemaModel,
20
24
  } from '@redocly/client-generator';
21
25
  import {
@@ -30,6 +34,7 @@ import { sampleValue, SampleExpression } from './sample.ts';
30
34
  import {
31
35
  expr,
32
36
  isObjectValue,
37
+ type MockEntry,
33
38
  type MockValue,
34
39
  objectValue,
35
40
  renderMockValue,
@@ -54,6 +59,8 @@ export type MockOptions = {
54
59
  mockData?: 'static' | 'faker';
55
60
  /** When set in `'faker'` mode, emit a top-level `faker.seed(<n>);` so runs reproduce. */
56
61
  mockSeed?: number;
62
+ /** The run's resolved pagination: a paginated handler serves the last page. */
63
+ pagination?: ModelPagination;
57
64
  };
58
65
 
59
66
  /** The body value for `schema` under the active data mode: a static literal tree
@@ -150,7 +157,7 @@ function codeIdent(value: string): string {
150
157
  function handlerFor(op: OperationModel, model: ApiModel, opts: MockOptions): string {
151
158
  const override = overrideParam(op, model, opts);
152
159
  const params = override ?? '';
153
- const call = `http.${codeIdent(op.method)}(${JSON.stringify(mswPath(op.path))}, () => ${responseExpression(op, model, opts)})`;
160
+ const call = `http.${codeIdent(op.method)}(${JSON.stringify(mswPath(op.path))}, ${resolverFor(op, model, opts)})`;
154
161
  return `export const ${codeIdent(op.name)}Handler = (${params}) => ${call};`;
155
162
  }
156
163
 
@@ -212,8 +219,8 @@ function errorBodyType(op: OperationModel): string {
212
219
  * A `ref` success body forwards `override` to `create<Schema>(override)`, so its type
213
220
  * mirrors the factory's parameter (`Partial<Schema>`, or the full type when the factory
214
221
  * replaces wholesale — see `factoryFor`). An inline object body spreads `override`, typed
215
- * `Record<string, unknown>`. A body-less or non-object inline response has nothing to
216
- * override, so the handler takes no parameter.
222
+ * `Record<string, unknown>`, or `Partial<Omit<Schema, …>>` for an `omit` body. A body-less
223
+ * or non-object inline response has nothing to override, so the handler takes no parameter.
217
224
  */
218
225
  function overrideParam(op: OperationModel, model: ApiModel, opts: MockOptions): string | undefined {
219
226
  const success = op.successResponses[0];
@@ -226,16 +233,128 @@ function overrideParam(op: OperationModel, model: ApiModel, opts: MockOptions):
226
233
  return `override?: ${type}`;
227
234
  }
228
235
  if (!isObjectValue(bodyValue(success.schema, model, opts))) return undefined;
236
+ if (success.schema.kind === 'omit') {
237
+ const keys = success.schema.keys.map((key) => JSON.stringify(key)).join(' | ');
238
+ return `override?: Partial<Omit<${success.schema.base}, ${keys}>>`;
239
+ }
229
240
  return 'override?: Record<string, unknown>';
230
241
  }
231
242
 
243
+ /**
244
+ * The handler's resolver. A paginated operation serves the LAST page, so `.pages()` over
245
+ * the default handlers ends after one page: a cursor page carries no next cursor (and a
246
+ * `false` "has more" flag), and a `body-link` page no next link; an offset or page-number
247
+ * page answers any position past the first with an empty item list, because those styles
248
+ * stop only on an empty page.
249
+ */
250
+ function resolverFor(op: OperationModel, model: ApiModel, opts: MockOptions): string {
251
+ const spec = opts.pagination?.get(op.name)?.spec;
252
+ const success = op.successResponses[0];
253
+ if (!spec || spec.style === 'link' || !success)
254
+ return `() => ${responseExpression(op, model, opts)}`;
255
+ const page = bodyValue(success.schema, model, opts);
256
+ const status = statusCode(success.status);
257
+ // An object page spreads `override` in; a page that is the item array is replaced by it.
258
+ const takesOverride = overrideParam(op, model, opts) !== undefined;
259
+ const withOverride = (value: MockValue): MockValue => {
260
+ if (isObjectValue(value)) return spreadInto(value, 'override');
261
+ return takesOverride ? { kind: 'wrap', before: 'override ?? ', value, after: '' } : value;
262
+ };
263
+ const json = (value: MockValue): string => {
264
+ const data = renderMockValue(value, '');
265
+ return status === 200
266
+ ? `HttpResponse.json(${data})`
267
+ : `HttpResponse.json(${data}, { status: ${status} })`;
268
+ };
269
+ if (spec.style === 'cursor' || spec.style === 'body-link') {
270
+ const next = spec.style === 'cursor' ? spec.nextCursor : spec.nextLink;
271
+ let last = withPointer(page, pointerKeys(next), cursorStop(success.schema, next, model));
272
+ if (spec.style === 'cursor' && spec.hasMore !== undefined)
273
+ last = withPointer(last, pointerKeys(spec.hasMore), expr('false'));
274
+ return `() => ${json(withOverride(last))}`;
275
+ }
276
+ const first = spec.style === 'page' ? 1 : 0;
277
+ const position = `Number(new URL(request.url).searchParams.get(${codeLiteral(spec.param)}) ?? ${first})`;
278
+ const empty = emptyPage(page, pointerKeys(spec.items));
279
+ return `({ request }) => ${position} > ${first} ? ${json(empty)} : ${json(withOverride(page))}`;
280
+ }
281
+
282
+ /**
283
+ * The page past the last one: the page with `override` spread in, and the items at `keys`
284
+ * set AFTER the spread, so an override that sets the items cannot refill the empty page.
285
+ * The top-level property that holds the items is written out from the sample.
286
+ */
287
+ function emptyPage(page: MockValue, keys: string[]): MockValue {
288
+ if (keys.length === 0 || !isObjectValue(page)) return withPointer(page, keys, expr('[]'));
289
+ const [key, ...rest] = keys;
290
+ const isKey = (entry: MockEntry) => !('spread' in entry) && entry.key === key;
291
+ const holder = page.entries.find(isKey);
292
+ const inner = holder && !('spread' in holder) ? holder.value : objectValue([]);
293
+ return objectValue([
294
+ ...page.entries.filter((entry) => !isKey(entry)),
295
+ { spread: 'override' },
296
+ { key, value: withPointer(inner, rest, expr('[]')) },
297
+ ]);
298
+ }
299
+
300
+ /**
301
+ * What ends a cursor or `body-link` walk at `pointer`: `null` when the value is nullable,
302
+ * nothing (the property is left out) when it is optional, and an empty string otherwise.
303
+ */
304
+ function cursorStop(schema: SchemaModel, pointer: string, model: ApiModel): MockValue | undefined {
305
+ const cursor = schemaAtPointer(schema, pointer, model);
306
+ if (cursor?.kind === 'null' || (cursor && isNullable(cursor))) return expr('null');
307
+ const keys = pointerKeys(pointer);
308
+ const parent = schemaAtPointer(schema, pointer.slice(0, pointer.lastIndexOf('/')), model);
309
+ const property =
310
+ parent &&
311
+ flattenAllOf(parent, model)?.properties.find((candidate) => candidate.name === keys.at(-1));
312
+ return property?.required === false ? undefined : expr('""');
313
+ }
314
+
315
+ /** RFC 6901 pointer tokens (`~1` → `/`, `~0` → `~`); none for `''`, the whole value. */
316
+ function pointerKeys(pointer: string): string[] {
317
+ if (pointer === '') return [];
318
+ return pointer
319
+ .slice(1)
320
+ .split('/')
321
+ .map((token) => token.replaceAll('~1', '/').replaceAll('~0', '~'));
322
+ }
323
+
324
+ /**
325
+ * `value` with the property at the `keys` path replaced (added when missing), or removed
326
+ * when `replacement` is `undefined`. An empty path replaces the whole value (a page that
327
+ * is the item array itself). Steps only through objects: a path that reaches into
328
+ * anything else leaves the value unchanged.
329
+ */
330
+ function withPointer(
331
+ value: MockValue,
332
+ keys: string[],
333
+ replacement: MockValue | undefined
334
+ ): MockValue {
335
+ if (keys.length === 0) return replacement ?? value;
336
+ if (!isObjectValue(value)) return value;
337
+ const [key, ...rest] = keys;
338
+ const found = value.entries.some((entry) => !('spread' in entry) && entry.key === key);
339
+ const entries = value.entries.flatMap((entry) => {
340
+ if ('spread' in entry || entry.key !== key) return [entry];
341
+ if (rest.length > 0) return [{ key, value: withPointer(entry.value, rest, replacement) }];
342
+ return replacement === undefined ? [] : [{ key, value: replacement }];
343
+ });
344
+ if (!found && rest.length === 0 && replacement !== undefined) {
345
+ entries.push({ key, value: replacement });
346
+ }
347
+ return objectValue(entries);
348
+ }
349
+
232
350
  /**
233
351
  * The handler's response. A primary success body becomes `HttpResponse.json(…)`:
234
- * a `ref` body calls its named `create<Schema>(override)` factory; an inline body
235
- * is sampled and printed in place with `...override` spread in. A success with no
236
- * usable body (an `unknown` schema, or no success response at all) becomes a
237
- * body-less `new HttpResponse(null, { status })`. The status is the success
238
- * response's declared code, or 200 when it's `default`/absent.
352
+ * a `ref` body calls its named `create<Schema>(override)` factory; an inline body is
353
+ * sampled and printed in place with `...override` spread in. An `omit` body (a named
354
+ * schema without its `writeOnly` keys) is inline too, because the factory keeps the
355
+ * required ones. A success with no usable body (an `unknown` schema, or no success
356
+ * response at all) becomes a body-less `new HttpResponse(null, { status })`. The status
357
+ * is the success response's declared code, or 200 when it's `default`/absent.
239
358
  */
240
359
  function responseExpression(op: OperationModel, model: ApiModel, opts: MockOptions): string {
241
360
  const success = op.successResponses[0];
@@ -1,10 +1,11 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "mock" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "mock" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator mock --update`.
5
5
  import {
6
6
  type DateType,
7
7
  type NamedSchemaModel,
8
+ type PropertyModel,
8
9
  type ScalarKind,
9
10
  type SchemaMetadata,
10
11
  type SchemaModel,
@@ -97,7 +98,10 @@ export function splitIntersection(
97
98
  members: SchemaModel[],
98
99
  byName: Map<string, SchemaModel>
99
100
  ): { merged: SchemaModel | undefined; rest: SchemaModel[] } {
100
- const properties = new Map<string, { schema: SchemaModel; required: boolean; rank: number }>();
101
+ const properties = new Map<
102
+ string,
103
+ { schema: SchemaModel; required: boolean; writeOnly: boolean; rank: number }
104
+ >();
101
105
  const rest: SchemaModel[] = [];
102
106
  let sawObject = false;
103
107
  for (const member of members) {
@@ -116,6 +120,7 @@ export function splitIntersection(
116
120
  properties.set(property.name, {
117
121
  schema: property.schema,
118
122
  required: property.required || (existing?.required ?? false),
123
+ writeOnly: property.writeOnly === true || (existing?.writeOnly ?? false),
119
124
  rank,
120
125
  });
121
126
  }
@@ -129,6 +134,7 @@ export function splitIntersection(
129
134
  name,
130
135
  schema: p.schema,
131
136
  required: p.required,
137
+ ...(p.writeOnly ? { writeOnly: true } : {}),
132
138
  })),
133
139
  },
134
140
  rest,
@@ -195,7 +201,9 @@ function walk(
195
201
  const meta = schema.metadata;
196
202
  // An example/default is honored only when it inhabits the generated type — real specs
197
203
  // carry `example: null` on non-nullable fields and defaults outside a narrowed enum
198
- // (`enum: [cram-md5], default: none`); baking those would not type-check.
204
+ // (`enum: [cram-md5], default: none`); baking those would not type-check. A number
205
+ // outside its bounds or off its `multipleOf` step would fail the zod schema, so it is
206
+ // skipped too.
199
207
  const inhabits = (value: unknown): boolean => {
200
208
  if (value === null) {
201
209
  return (
@@ -205,6 +213,16 @@ function walk(
205
213
  }
206
214
  if (schema.kind === 'enum') return (schema.values as unknown[]).includes(value);
207
215
  if (schema.kind === 'literal') return value === schema.value;
216
+ if (
217
+ schema.kind === 'scalar' &&
218
+ (schema.scalar === 'integer' || schema.scalar === 'number') &&
219
+ typeof value === 'number'
220
+ ) {
221
+ const { min, max, step } = numericRange(schema.scalar, meta);
222
+ // `toPrecision` drops float noise, so `0.3` counts as a multiple of `0.1`.
223
+ const onStep = step === undefined || Number.isInteger(Number((value / step).toPrecision(12)));
224
+ return onStep && (min === undefined || value >= min) && (max === undefined || value <= max);
225
+ }
208
226
  return true;
209
227
  };
210
228
  if (meta?.example !== undefined && inhabits(meta.example)) return meta.example;
@@ -221,6 +239,7 @@ function walk(
221
239
  case 'object':
222
240
  return Object.fromEntries(
223
241
  schema.properties.flatMap((p) => {
242
+ if (omitsFromResponse(p)) return [];
224
243
  const value = walk(p.schema, byName, visiting, dateType);
225
244
  // A cyclic optional property is omitted (a null would not satisfy `T | undefined`);
226
245
  // a cyclic required property is uninhabitable, so null is the only stand-in.
@@ -292,9 +311,73 @@ function walk(
292
311
  }
293
312
  }
294
313
 
314
+ /**
315
+ * Whether a mock leaves a property out. Mocks are response data, and a response never
316
+ * carries a `writeOnly` property. A required one stays, because the named type still
317
+ * demands it; a handler whose response omits it samples that `omit` shape instead.
318
+ */
319
+ export function omitsFromResponse(property: PropertyModel): boolean {
320
+ return property.writeOnly === true && !property.required;
321
+ }
322
+
323
+ /**
324
+ * The inclusive range a numeric schema allows. `minimum`/`maximum` and the exclusive
325
+ * bounds narrow it, aligned to `multipleOf` (an integer steps by 1 without one). An
326
+ * exclusive bound on a number without a step moves in by 1, or to the middle of the
327
+ * range when the range is narrower than that. An unbounded side is `undefined`.
328
+ */
329
+ export function numericRange(
330
+ scalar: 'integer' | 'number',
331
+ meta: SchemaMetadata | undefined
332
+ ): { min?: number; max?: number; step?: number } {
333
+ const step = meta?.multipleOf ?? (scalar === 'integer' ? 1 : undefined);
334
+ // The multiple of `step` that `round` picks for `bound`; `toPrecision` drops float noise.
335
+ const onStep = (bound: number, size: number, round: (steps: number) => number): number =>
336
+ Number((round(bound / size) * size).toPrecision(15));
337
+ const lows: number[] = [];
338
+ const highs: number[] = [];
339
+ if (meta?.minimum !== undefined) {
340
+ const bound = meta.minimum;
341
+ lows.push(step === undefined ? bound : onStep(bound, step, Math.ceil));
342
+ }
343
+ if (meta?.exclusiveMinimum !== undefined) {
344
+ const bound = meta.exclusiveMinimum;
345
+ lows.push(
346
+ step === undefined ? bound + 1 : onStep(bound, step, (steps) => Math.floor(steps) + 1)
347
+ );
348
+ }
349
+ if (meta?.maximum !== undefined) {
350
+ const bound = meta.maximum;
351
+ highs.push(step === undefined ? bound : onStep(bound, step, Math.floor));
352
+ }
353
+ if (meta?.exclusiveMaximum !== undefined) {
354
+ const bound = meta.exclusiveMaximum;
355
+ highs.push(
356
+ step === undefined ? bound - 1 : onStep(bound, step, (steps) => Math.ceil(steps) - 1)
357
+ );
358
+ }
359
+ const min = lows.length > 0 ? Math.max(...lows) : undefined;
360
+ const max = highs.length > 0 ? Math.min(...highs) : undefined;
361
+ if (step === undefined && min !== undefined && max !== undefined && min > max) {
362
+ const low = Math.max(meta?.minimum ?? -Infinity, meta?.exclusiveMinimum ?? -Infinity);
363
+ const high = Math.min(meta?.maximum ?? Infinity, meta?.exclusiveMaximum ?? Infinity);
364
+ const middle = (low + high) / 2;
365
+ return { min: middle, max: middle };
366
+ }
367
+ return { min, max, step };
368
+ }
369
+
370
+ /** `0` when the range allows it, else the end of the range nearest to `0`. */
371
+ function numberSample(scalar: 'integer' | 'number', meta: SchemaMetadata | undefined): number {
372
+ const { min, max } = numericRange(scalar, meta);
373
+ if (min !== undefined && min > 0) return min;
374
+ if (max !== undefined && max < 0) return max;
375
+ return 0;
376
+ }
377
+
295
378
  function scalarSample(scalar: ScalarKind, meta: SchemaMetadata | undefined): unknown {
296
379
  if (scalar === 'boolean') return true;
297
- if (scalar === 'integer' || scalar === 'number') return 0;
380
+ if (scalar === 'integer' || scalar === 'number') return numberSample(scalar, meta);
298
381
  // Type-demanding formats (binary, and date/date-time under `dateType: 'Date'`) are
299
382
  // handled earlier in `walk` via `typeDemandedExpression`; here the date formats fall
300
383
  // through to the ISO-string path (the `dateType: 'string'` types them as `string`).
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "mock" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "mock" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator mock --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "php" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "php" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator php --update`.
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "php" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "php" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator php --update`.
@@ -30,6 +30,15 @@ export function phpSecurityLiteral(op: OperationModel, model: ApiModel): string
30
30
  return `[${alternatives.join(', ')}]`;
31
31
  }
32
32
 
33
+ /**
34
+ * The `resolveAuth` argument: the table's `security` entry, or `[]` for an operation the
35
+ * table gives none. Reading an absent key with `?? []` reads as dead code to PHPStan,
36
+ * which knows the table's exact shape.
37
+ */
38
+ export function phpSecurityArg(op: OperationModel, model: ApiModel): string {
39
+ return phpSecurityLiteral(op, model) === undefined ? '[]' : "$op['security']";
40
+ }
41
+
33
42
  export function phpPaginationLiteral(rule: NeutralPaginationRule): string {
34
43
  const fields = [
35
44
  `'style' => ${phpString(rule.style)}`,
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "php" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "php" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator php --update`.
@@ -234,5 +234,5 @@ export default {
234
234
  "argsStyle": "inputs follow the target language's own idiom",
235
235
  "importExt": "the generated file has no relative imports"
236
236
  },
237
- requiresGenerator: '^0.7.11',
237
+ requiresGenerator: '^0.7.13',
238
238
  };
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "php" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "php" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator php --update`.
@@ -111,10 +111,15 @@ function writeClass(
111
111
  dateType: DateType,
112
112
  description?: string
113
113
  ): void {
114
+ // A response never carries a `writeOnly` property, and responses decode into this same
115
+ // class, so the property is optional even when the description requires it.
116
+ const fields = properties.map((property) =>
117
+ property.writeOnly ? { ...property, required: false } : property
118
+ );
114
119
  // PHP requires defaulted parameters after required ones.
115
120
  const ordered = [
116
- ...properties.filter((property) => property.required),
117
- ...properties.filter((property) => !property.required),
121
+ ...fields.filter((property) => property.required),
122
+ ...fields.filter((property) => !property.required),
118
123
  ];
119
124
  printer.doc(className(name), description);
120
125
  printer.line(`final class ${className(name)}`);
@@ -1,4 +1,4 @@
1
- // Ejected from @redocly/client-generator@0.7.11 — the built-in "php" generator.
1
+ // Ejected from @redocly/client-generator@0.7.13 — the built-in "php" generator.
2
2
  // This file is yours: edit freely; the generated client stays machine-owned and is
3
3
  // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
4
  // `redocly eject-generator php --update`.