apollo-conn-gen 0.12.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.
@@ -26,13 +26,15 @@ export class ErrorsWriter {
26
26
  // `message: "$.message"` — the path form yields the field's VALUE; a bare `message`
27
27
  // selection would build the object `{message: …}`, which errors.message rejects. R4
28
28
  const message = this.errorMessageField(context, op);
29
- const spacing = ' '.repeat(indent + 6);
29
+ // label/close at the @connect arg level, the extensions body one level deeper (like queryParams)
30
+ const labelSpacing = ' '.repeat(indent + 6);
31
+ const bodySpacing = ' '.repeat(indent + 8);
30
32
  writer
31
- .write(spacing)
33
+ .write(labelSpacing)
32
34
  .write(message ? `errors: { message: "$.${message}" extensions: """\n` : 'errors: { extensions: """\n')
33
- .write(spacing)
35
+ .write(bodySpacing)
34
36
  .write('statusCode: $status\n')
35
- .write(spacing)
37
+ .write(labelSpacing)
36
38
  .write('""" }\n');
37
39
  }
38
40
  // True when the operation documents an HTTP error response. Accepts both concrete numeric statuses
@@ -1 +1 @@
1
- {"version":3,"file":"errorsWriter.js","sourceRoot":"","sources":["../../../src/oas/io/errorsWriter.ts"],"names":[],"mappings":"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","sourcesContent":["import _ from 'lodash';\nimport { ResponseObject, SchemaObject } from 'oas/types';\nimport { OasContext } from '../oasContext.js';\nimport { OasGen } from '../oasGen.js';\nimport { Op } from '../nodes/internal.js';\nimport { Writer } from './writer.js';\nimport { DEFAULT_VERSIONS, meetsMinimum } from '../../versions.js';\nimport { warn } from '../log/trace.js';\n\n// R4 (opt-in): the `errors:` block of a connector — `message` from the documented error body,\n// `extensions` carrying `$status`.\nexport class ErrorsWriter {\n // corpus-measured priority for the error-body message field: `message` (755 error schemas),\n // `error` (362), `detail` (7)\n private static readonly MESSAGE_FIELDS = ['message', 'error', 'detail'];\n\n constructor(private gen: OasGen) {}\n\n // emit `errors: { message: \"$.message\" extensions: \"\"\"statusCode: $status\"\"\" }` for operations\n // that document HTTP error responses. errors is a connect v0.2+ feature; below that we skip\n // with a logged downgrade rather than emit invalid output.\n public write(context: OasContext, writer: Writer, op: Op, indent: number): void {\n if (!context.generateOptions?.emitConnectorErrors || !this.hasDocumentedErrors(op)) {\n return;\n }\n\n const version = this.gen.options.connectorSpecVersion || DEFAULT_VERSIONS.connectorSpecVersion;\n if (!meetsMinimum(version, 'v0.2')) {\n warn(\n context,\n '[errors]',\n `@connect(errors:) requires connect v0.2, but target is ${version} — not emitted for ${op.verb} ${op.operation.path}`,\n );\n return;\n }\n\n // `message: \"$.message\"` — the path form yields the field's VALUE; a bare `message`\n // selection would build the object `{message: …}`, which errors.message rejects. R4\n const message = this.errorMessageField(context, op);\n\n const spacing = ' '.repeat(indent + 6);\n writer\n .write(spacing)\n .write(message ? `errors: { message: \"$.${message}\" extensions: \"\"\"\\n` : 'errors: { extensions: \"\"\"\\n')\n .write(spacing)\n .write('statusCode: $status\\n')\n .write(spacing)\n .write('\"\"\" }\\n');\n }\n\n // True when the operation documents an HTTP error response. Accepts both concrete numeric statuses\n // (4xx/5xx) and the OAS range keys `4XX`/`5XX` (case-insensitive). The `default` key is excluded —\n // it also covers 2xx/3xx, so it is not specifically an error indicator.\n private hasDocumentedErrors(op: Op): boolean {\n return op.operation.getResponseStatusCodes().some((code: string) => /^[45](\\d\\d|XX)$/i.test(code));\n }\n\n // the field must be a string on EVERY documented JSON error shape — a field missing on some\n // status would yield a null message there. Non-JSON / shapeless error responses don't veto.\n private errorMessageField(context: OasContext, op: Op): string | undefined {\n const responses = op.operation.schema.responses ?? {};\n const errorShapes: Array<Record<string, unknown>> = [];\n\n for (const [code, response] of Object.entries(responses)) {\n if (!/^[45](\\d\\d|XX)$/i.test(code)) {\n continue;\n }\n const resolved = this.deref(context, response) as ResponseObject | null;\n const mediaKey = Object.keys(resolved?.content ?? {}).find((k) => /^application\\/(?:.*\\+)?json/i.test(k));\n const schema = mediaKey\n ? (this.deref(context, resolved!.content![mediaKey].schema) as SchemaObject | null)\n : null;\n if (schema?.properties) {\n errorShapes.push(schema.properties);\n }\n }\n\n if (errorShapes.length === 0) {\n return undefined;\n }\n return ErrorsWriter.MESSAGE_FIELDS.find((field) =>\n errorShapes.every((props) => (this.deref(context, props[field]) as SchemaObject | null)?.type === 'string'),\n );\n }\n\n // read-only $ref hop — resolvePointer, NOT lookupRef: this sniff must not bump refCount\n private deref(context: OasContext, node: unknown): unknown {\n return _.has(node, '$ref') ? context.resolvePointer(_.get(node, '$ref') as string) : node;\n }\n}\n"]}
1
+ {"version":3,"file":"errorsWriter.js","sourceRoot":"","sources":["../../../src/oas/io/errorsWriter.ts"],"names":[],"mappings":"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","sourcesContent":["import _ from 'lodash';\nimport { ResponseObject, SchemaObject } from 'oas/types';\nimport { OasContext } from '../oasContext.js';\nimport { OasGen } from '../oasGen.js';\nimport { Op } from '../nodes/internal.js';\nimport { Writer } from './writer.js';\nimport { DEFAULT_VERSIONS, meetsMinimum } from '../../versions.js';\nimport { warn } from '../log/trace.js';\n\n// R4 (opt-in): the `errors:` block of a connector — `message` from the documented error body,\n// `extensions` carrying `$status`.\nexport class ErrorsWriter {\n // corpus-measured priority for the error-body message field: `message` (755 error schemas),\n // `error` (362), `detail` (7)\n private static readonly MESSAGE_FIELDS = ['message', 'error', 'detail'];\n\n constructor(private gen: OasGen) {}\n\n // emit `errors: { message: \"$.message\" extensions: \"\"\"statusCode: $status\"\"\" }` for operations\n // that document HTTP error responses. errors is a connect v0.2+ feature; below that we skip\n // with a logged downgrade rather than emit invalid output.\n public write(context: OasContext, writer: Writer, op: Op, indent: number): void {\n if (!context.generateOptions?.emitConnectorErrors || !this.hasDocumentedErrors(op)) {\n return;\n }\n\n const version = this.gen.options.connectorSpecVersion || DEFAULT_VERSIONS.connectorSpecVersion;\n if (!meetsMinimum(version, 'v0.2')) {\n warn(\n context,\n '[errors]',\n `@connect(errors:) requires connect v0.2, but target is ${version} — not emitted for ${op.verb} ${op.operation.path}`,\n );\n return;\n }\n\n // `message: \"$.message\"` — the path form yields the field's VALUE; a bare `message`\n // selection would build the object `{message: …}`, which errors.message rejects. R4\n const message = this.errorMessageField(context, op);\n\n // label/close at the @connect arg level, the extensions body one level deeper (like queryParams)\n const labelSpacing = ' '.repeat(indent + 6);\n const bodySpacing = ' '.repeat(indent + 8);\n writer\n .write(labelSpacing)\n .write(message ? `errors: { message: \"$.${message}\" extensions: \"\"\"\\n` : 'errors: { extensions: \"\"\"\\n')\n .write(bodySpacing)\n .write('statusCode: $status\\n')\n .write(labelSpacing)\n .write('\"\"\" }\\n');\n }\n\n // True when the operation documents an HTTP error response. Accepts both concrete numeric statuses\n // (4xx/5xx) and the OAS range keys `4XX`/`5XX` (case-insensitive). The `default` key is excluded —\n // it also covers 2xx/3xx, so it is not specifically an error indicator.\n private hasDocumentedErrors(op: Op): boolean {\n return op.operation.getResponseStatusCodes().some((code: string) => /^[45](\\d\\d|XX)$/i.test(code));\n }\n\n // the field must be a string on EVERY documented JSON error shape — a field missing on some\n // status would yield a null message there. Non-JSON / shapeless error responses don't veto.\n private errorMessageField(context: OasContext, op: Op): string | undefined {\n const responses = op.operation.schema.responses ?? {};\n const errorShapes: Array<Record<string, unknown>> = [];\n\n for (const [code, response] of Object.entries(responses)) {\n if (!/^[45](\\d\\d|XX)$/i.test(code)) {\n continue;\n }\n const resolved = this.deref(context, response) as ResponseObject | null;\n const mediaKey = Object.keys(resolved?.content ?? {}).find((k) => /^application\\/(?:.*\\+)?json/i.test(k));\n const schema = mediaKey\n ? (this.deref(context, resolved!.content![mediaKey].schema) as SchemaObject | null)\n : null;\n if (schema?.properties) {\n errorShapes.push(schema.properties);\n }\n }\n\n if (errorShapes.length === 0) {\n return undefined;\n }\n return ErrorsWriter.MESSAGE_FIELDS.find((field) =>\n errorShapes.every((props) => (this.deref(context, props[field]) as SchemaObject | null)?.type === 'string'),\n );\n }\n\n // read-only $ref hop — resolvePointer, NOT lookupRef: this sniff must not bump refCount\n private deref(context: OasContext, node: unknown): unknown {\n return _.has(node, '$ref') ? context.resolvePointer(_.get(node, '$ref') as string) : node;\n }\n}\n"]}
@@ -13,6 +13,7 @@ export declare class OperationWriter {
13
13
  private templatedPath;
14
14
  private writeQueryParams;
15
15
  private writeHeaders;
16
+ private authHeaderEntry;
16
17
  private mergeOverrides;
17
18
  private headerExample;
18
19
  private arrayJoin;
@@ -2,6 +2,7 @@ import _ from 'lodash';
2
2
  import { T } from '../nodes/internal.js';
3
3
  import { Naming } from '../utils/naming.js';
4
4
  import { ErrorsWriter } from './errorsWriter.js';
5
+ import { anyOperationDeclaresSecurity, globalSecurity, securitySchemes, resolveAuthHeader } from './security.js';
5
6
  export class OperationWriter {
6
7
  gen;
7
8
  errorsWriter;
@@ -60,23 +61,36 @@ export class OperationWriter {
60
61
  const override = context.generateOptions.overrides?.[op.id];
61
62
  // the verb + path first, then query params, headers and lastly the body
62
63
  writer.write(`{ ${op.verb}: `).write('"' + this.templatedPath(op, override) + '"');
63
- this.writeQueryParams(context, writer, op, override);
64
- this.writeHeaders(writer, op, override);
64
+ // each block starts on its own line and ends without a trailing newline
65
+ const wroteQueryParams = this.writeQueryParams(context, writer, op, override);
66
+ const wroteHeaders = this.writeHeaders(writer, op, override);
65
67
  // body (POST, PUT, etc.): an override (raw JSONSelection) replaces the inferred
66
68
  // `$args.input { … }` mapping; null drops the body altogether. see ROADMAP R9
67
69
  if (typeof override?.body === 'string') {
70
+ // body emits its own trailing indent, so close the object right after it
68
71
  this.writeBodyOverride(writer, override.body);
72
+ writer.write('}');
69
73
  }
70
74
  else if (override?.body === undefined && op.body) {
75
+ // body emits its own trailing indent, so close the object right after it
71
76
  this.writeBodySelection(context, writer, op.body, selection);
77
+ writer.write('}');
78
+ }
79
+ else if (wroteQueryParams || wroteHeaders) {
80
+ // no body: close on its own line, aligned under `http:` (avoids a column-0 brace)
81
+ writer.write('\n' + ' '.repeat(6) + '}');
82
+ }
83
+ else {
84
+ // nothing but the path: keep the compact single-line form `{ GET: "/x"}`
85
+ writer.write('}');
72
86
  }
73
- writer.write('}');
74
87
  }
75
88
  // template each {elem} as {$args.<sanitised>} (the arg name), not the raw OAS key. see docs/issues.md #2
76
89
  // an override path may already template (`{$args.id}`, `{$config.v}`) — leave `$` segments alone
77
90
  templatedPath(op, override) {
78
91
  return (override?.path ?? op.operation.path).replace(/\{([^}]+)\}/g, (m, name) => name.startsWith('$') ? m : `{$args.${Naming.genParamName(name)}}`);
79
92
  }
93
+ // returns true when a queryParams block was written (so the caller can place the closing brace)
80
94
  writeQueryParams(context, writer, op, override) {
81
95
  // we now include all query params, not just required ones. if they are not set,
82
96
  // then the connectors will not include them in the request.
@@ -90,7 +104,7 @@ export class OperationWriter {
90
104
  // e.g. `"api-version": $('2024-01')` appended, `"ids": ids->joinNotNull(";")` replaced
91
105
  const entries = this.mergeOverrides(queryParams, override?.queryParams ?? {}, (p) => `${Naming.genParamName(p.name)}${this.arrayJoin(p)}`);
92
106
  if (entries.length === 0) {
93
- return;
107
+ return false;
94
108
  }
95
109
  writer.write('\n');
96
110
  let spacing = ' '.repeat(6);
@@ -104,22 +118,69 @@ export class OperationWriter {
104
118
  spacing = ' '.repeat(8);
105
119
  writer.write(spacing).write('}\n');
106
120
  spacing = ' '.repeat(6);
107
- writer.write(spacing).write('"""\n');
121
+ // no trailing newline — the caller adds the separator/brace
122
+ writer.write(spacing).write('"""');
123
+ return true;
108
124
  }
125
+ // returns true when a headers block was written (so the caller can place the closing brace)
109
126
  writeHeaders(writer, op, override) {
127
+ // OAS `header` params, with user overrides merged in (string replaces, null drops)
110
128
  const headers = op.operation.getParameters().filter((p) => p.in && p.in.toLowerCase() === 'header');
111
- const entries = this.mergeOverrides(headers, override?.headers ?? {}, (p) => this.headerExample(p));
129
+ let entries = this.mergeOverrides(headers, override?.headers ?? {}, (p) => this.headerExample(p));
130
+ // R5 slice 2: add this operation's effective auth header (only when the spec is in per-op mode).
131
+ // HTTP header names are case-insensitive: an explicit user override of the same name wins;
132
+ // otherwise the resolved auth replaces any inferred OAS header of that name (so we never emit
133
+ // a real credential alongside a placeholder that differs only in case).
134
+ const auth = this.authHeaderEntry(op);
135
+ if (auth) {
136
+ const sameName = (name) => name.toLowerCase() === auth[0].toLowerCase();
137
+ const overridden = Object.keys(override?.headers ?? {}).some(sameName);
138
+ if (!overridden) {
139
+ entries = [auth, ...entries.filter(([name]) => !sameName(name))];
140
+ }
141
+ }
112
142
  if (entries.length === 0) {
113
- return;
143
+ return false;
114
144
  }
115
145
  let spacing = ' '.repeat(6);
116
- writer.write(spacing + 'headers: [\n');
146
+ // leading newline so the block starts on its own line (after the path or a queryParams block)
147
+ writer.write('\n' + spacing + 'headers: [\n');
117
148
  spacing = ' '.repeat(8);
118
149
  for (const [key, value] of entries) {
119
150
  writer.write(spacing + `{ name: "${key}", value: "${value}" }\n`);
120
151
  }
121
152
  spacing = ' '.repeat(6);
153
+ // no trailing newline — the caller adds the separator/brace
122
154
  writer.write(spacing + ']');
155
+ return true;
156
+ }
157
+ // R5 slice 2: this operation's effective auth header as a [name, value] entry, or null.
158
+ //
159
+ // Emits only in *per-op mode* — when some operation in the spec declares its own `security`,
160
+ // the shared @source auth header is suppressed, so each @connect must carry its own. The
161
+ // effective requirement is the op's own `security` when present, else the global default:
162
+ // security: [{ AdminBearer: [] }] (own) -> that scheme's header (e.g. Authorization: Bearer)
163
+ // security: [] (public) -> no header (the op correctly sends nothing)
164
+ // no own `security` -> the inherited global header
165
+ authHeaderEntry(op) {
166
+ const api = this.gen.parser;
167
+ // uniform mode: @source already carries the shared auth, nothing to add per operation
168
+ if (!anyOperationDeclaresSecurity(api)) {
169
+ return null;
170
+ }
171
+ // the op's own requirement; `undefined` = inherit the global, `[]` = public
172
+ const own = op.operation.schema.security;
173
+ // `??` not `||`: an explicit `[]` (public) must NOT fall back to the global
174
+ const effective = own ?? globalSecurity(api);
175
+ // read the scheme definitions the requirement refers to
176
+ const schemes = securitySchemes(api);
177
+ // resolve the effective requirement to one header, collecting per-scheme drop warnings
178
+ const { header, warnings } = resolveAuthHeader(effective, schemes);
179
+ // surface dropped schemes loudly, tagged with this operation
180
+ for (const w of warnings) {
181
+ console.warn(`${w} (operation ${op.verb} ${op.operation.path})`);
182
+ }
183
+ return header ? [header.name, header.value] : null;
123
184
  }
124
185
  // merge user overrides over the inferred params: a string replaces the inferred value,
125
186
  // null drops the entry, an unknown key is appended. see ROADMAP R8
@@ -1 +1 @@
1
- {"version":3,"file":"operationWriter.js","sourceRoot":"","sources":["../../../src/oas/io/operationWriter.ts"],"names":[],"mappings":"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","sourcesContent":["import _ from 'lodash';\nimport { ParameterObject } from 'oas/types';\nimport { OasContext, RequestOverride } from '../oasContext.js';\nimport { OasGen } from '../oasGen.js';\nimport { Body, IType, Op, Param, T } from '../nodes/internal.js';\nimport { Naming } from '../utils/naming.js';\nimport { ErrorsWriter } from './errorsWriter.js';\nimport { Writer } from './writer.js';\n\nexport class OperationWriter {\n private errorsWriter: ErrorsWriter;\n\n constructor(private gen: OasGen) {\n this.errorsWriter = new ErrorsWriter(gen);\n }\n\n public writeQuery(context: OasContext, writer: Writer, collected: Map<string, IType>, selection: string[]): void {\n const selectionSet = new Set<string>(selection.map((s) => s.split('>')[0]));\n\n const paths = Array.from(collected.values()).filter((path) => selectionSet.has(path.id));\n if (_.isEmpty(paths)) return;\n\n writer.write('type Query {\\n');\n\n for (const path of paths) {\n path.generate(context, writer, []);\n this.writeConnector(context, writer, path, selection);\n context.generatedSet.add(path.id);\n }\n\n writer.write('}\\n\\n');\n }\n\n public writeMutations(context: OasContext, writer: Writer, collected: Map<string, IType>, selection: string[]): void {\n const selectionSet = new Set<string>(selection.map((s) => s.split('>')[0]));\n\n const paths = Array.from(collected.values()).filter((path) => selectionSet.has(path.id));\n if (_.isEmpty(paths)) return;\n\n writer.write('type Mutation {\\n');\n\n for (const path of paths) {\n path.generate(context, writer, []);\n this.writeConnector(context, writer, path, selection);\n context.generatedSet.add(path.id);\n }\n\n writer.write('}\\n\\n');\n }\n\n public writeConnector(context: OasContext, writer: Writer, type: IType, selection: string[]): void {\n if (!T.isOp(type)) {\n throw new Error(`expected an operation node, got ${type.id}`);\n }\n\n const op = type;\n const indent = 0;\n let spacing = ' '.repeat(indent + 4);\n writer.write(spacing).write('@connect(\\n');\n\n spacing = ' '.repeat(indent + 6);\n writer.write(spacing).write('source: \"api\"\\n').write(spacing).write('http: ');\n\n this.requestMethod(context, writer, op, selection, indent);\n\n writer.write('\\n').write(spacing).write('selection: \"\"\"\\n');\n\n // truthiness, not _.has — the declared-but-unset field is still an own property. #33\n if (op.resultType) {\n this.writeSelection(context, writer, op.resultType, selection);\n }\n\n writer.write(spacing).write('\"\"\"\\n');\n\n this.errorsWriter.write(context, writer, op, indent);\n\n spacing = ' '.repeat(indent + 4);\n writer.write(spacing).write(')\\n');\n }\n\n private requestMethod(context: OasContext, writer: Writer, op: Op, selection: string[], _indent: number): void {\n const override = context.generateOptions.overrides?.[op.id];\n\n // the verb + path first, then query params, headers and lastly the body\n writer.write(`{ ${op.verb}: `).write('\"' + this.templatedPath(op, override) + '\"');\n this.writeQueryParams(context, writer, op, override);\n this.writeHeaders(writer, op, override);\n\n // body (POST, PUT, etc.): an override (raw JSONSelection) replaces the inferred\n // `$args.input { … }` mapping; null drops the body altogether. see ROADMAP R9\n if (typeof override?.body === 'string') {\n this.writeBodyOverride(writer, override.body);\n } else if (override?.body === undefined && op.body) {\n this.writeBodySelection(context, writer, op.body, selection);\n }\n\n writer.write('}');\n }\n\n // template each {elem} as {$args.<sanitised>} (the arg name), not the raw OAS key. see docs/issues.md #2\n // an override path may already template (`{$args.id}`, `{$config.v}`) — leave `$` segments alone\n private templatedPath(op: Op, override?: RequestOverride): string {\n return (override?.path ?? op.operation.path).replace(/\\{([^}]+)\\}/g, (m, name) =>\n name.startsWith('$') ? m : `{$args.${Naming.genParamName(name)}}`,\n );\n }\n\n private writeQueryParams(context: OasContext, writer: Writer, op: Op, override?: RequestOverride): void {\n // we now include all query params, not just required ones. if they are not set,\n // then the connectors will not include them in the request.\n let queryParams = op.params.filter((p: Param) => {\n return p.parameter.in && p.parameter.in.toLowerCase() === 'query';\n });\n\n // Skip optional params if skipOptionalArgs is true\n if (context.generateOptions?.skipOptionalArgs) {\n queryParams = queryParams.filter((p: Param) => p.required);\n }\n\n // e.g. `\"api-version\": $('2024-01')` appended, `\"ids\": ids->joinNotNull(\";\")` replaced\n const entries = this.mergeOverrides(\n queryParams,\n override?.queryParams ?? {},\n (p) => `${Naming.genParamName(p.name)}${this.arrayJoin(p)}`,\n );\n if (entries.length === 0) {\n return;\n }\n\n writer.write('\\n');\n let spacing = ' '.repeat(6);\n\n writer.write(spacing).write(`queryParams: \"\"\"\\n`);\n spacing = ' '.repeat(8);\n writer.write(spacing).write(`$args {\\n`);\n spacing = ' '.repeat(10);\n for (const [key, value] of entries) {\n writer.write(spacing).write(`\"${key}\": ${value}\\n`);\n }\n spacing = ' '.repeat(8);\n writer.write(spacing).write('}\\n');\n spacing = ' '.repeat(6);\n writer.write(spacing).write('\"\"\"\\n');\n }\n\n private writeHeaders(writer: Writer, op: Op, override?: RequestOverride): void {\n const headers = op.operation.getParameters().filter((p) => p.in && p.in.toLowerCase() === 'header');\n const entries = this.mergeOverrides(headers, override?.headers ?? {}, (p) => this.headerExample(p));\n if (entries.length === 0) {\n return;\n }\n\n let spacing = ' '.repeat(6);\n writer.write(spacing + 'headers: [\\n');\n spacing = ' '.repeat(8);\n\n for (const [key, value] of entries) {\n writer.write(spacing + `{ name: \"${key}\", value: \"${value}\" }\\n`);\n }\n\n spacing = ' '.repeat(6);\n writer.write(spacing + ']');\n }\n\n // merge user overrides over the inferred params: a string replaces the inferred value,\n // null drops the entry, an unknown key is appended. see ROADMAP R8\n private mergeOverrides<T extends { name: string }>(\n inferred: T[],\n overrides: Record<string, string | null>,\n inferredValue: (item: T) => string,\n ): Array<[string, string]> {\n const kept = inferred\n .filter((item) => overrides[item.name] !== null)\n .map((item): [string, string] => [item.name, overrides[item.name] ?? inferredValue(item)]);\n\n const known = new Set(inferred.map((item) => item.name));\n const appended = Object.entries(overrides).filter(\n (entry): entry is [string, string] => !known.has(entry[0]) && entry[1] != null,\n );\n\n return [...kept, ...appended];\n }\n\n // inferred header value: the example, the example keys, or a placeholder for the user to fill\n private headerExample(p: ParameterObject): string {\n if (p.example != null) {\n return p.example.toString();\n }\n if (p.examples && Object.keys(p.examples).length > 0) {\n return Object.keys(p.examples).join(',');\n }\n return '<placeholder>';\n }\n\n // a non-exploded array param (`?ids=1,2,3`) needs its values joined: `ids->joinNotNull(\",\")`. see ROADMAP R8\n // exploded arrays (the OAS default) already work as a plain array value\n private arrayJoin(p: Param): string {\n const parameter = p.parameter;\n if (_.get(parameter, 'schema.type') !== 'array' || parameter.explode !== false) {\n return '';\n }\n const delimiter = parameter.style === 'spaceDelimited' ? ' ' : parameter.style === 'pipeDelimited' ? '|' : ',';\n return `->joinNotNull(\"${delimiter}\")`;\n }\n\n private writeSelection(context: OasContext, writer: Writer, type: IType, selection: string[]): void {\n context.indent = 6;\n type.select(context, writer, selection);\n }\n\n // mirrors Body.select formatting, but the user's raw JSONSelection replaces the whole mapping\n private writeBodyOverride(writer: Writer, body: string): void {\n writer.write(',\\n');\n const spacing = ' '.repeat(6);\n writer.write(spacing + 'body: \"\"\"\\n');\n for (const line of body.split('\\n')) {\n writer.write(spacing + ' ' + line + '\\n');\n }\n writer.write(spacing + '\"\"\"\\n' + ' '.repeat(5));\n }\n\n private writeBodySelection(context: OasContext, writer: Writer, body: Body, selection: string[]): void {\n writer.write(',\\n');\n context.indent = 6;\n if (body) {\n body.select(context, writer, selection);\n }\n }\n}\n"]}
1
+ {"version":3,"file":"operationWriter.js","sourceRoot":"","sources":["../../../src/oas/io/operationWriter.ts"],"names":[],"mappings":"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","sourcesContent":["import _ from 'lodash';\nimport { ParameterObject } from 'oas/types';\nimport { OasContext, RequestOverride } from '../oasContext.js';\nimport { OasGen } from '../oasGen.js';\nimport { Body, IType, Op, Param, T } from '../nodes/internal.js';\nimport { Naming } from '../utils/naming.js';\nimport { ErrorsWriter } from './errorsWriter.js';\nimport { Writer } from './writer.js';\nimport { anyOperationDeclaresSecurity, globalSecurity, securitySchemes, resolveAuthHeader } from './security.js';\n\nexport class OperationWriter {\n private errorsWriter: ErrorsWriter;\n\n constructor(private gen: OasGen) {\n this.errorsWriter = new ErrorsWriter(gen);\n }\n\n public writeQuery(context: OasContext, writer: Writer, collected: Map<string, IType>, selection: string[]): void {\n const selectionSet = new Set<string>(selection.map((s) => s.split('>')[0]));\n\n const paths = Array.from(collected.values()).filter((path) => selectionSet.has(path.id));\n if (_.isEmpty(paths)) return;\n\n writer.write('type Query {\\n');\n\n for (const path of paths) {\n path.generate(context, writer, []);\n this.writeConnector(context, writer, path, selection);\n context.generatedSet.add(path.id);\n }\n\n writer.write('}\\n\\n');\n }\n\n public writeMutations(context: OasContext, writer: Writer, collected: Map<string, IType>, selection: string[]): void {\n const selectionSet = new Set<string>(selection.map((s) => s.split('>')[0]));\n\n const paths = Array.from(collected.values()).filter((path) => selectionSet.has(path.id));\n if (_.isEmpty(paths)) return;\n\n writer.write('type Mutation {\\n');\n\n for (const path of paths) {\n path.generate(context, writer, []);\n this.writeConnector(context, writer, path, selection);\n context.generatedSet.add(path.id);\n }\n\n writer.write('}\\n\\n');\n }\n\n public writeConnector(context: OasContext, writer: Writer, type: IType, selection: string[]): void {\n if (!T.isOp(type)) {\n throw new Error(`expected an operation node, got ${type.id}`);\n }\n\n const op = type;\n const indent = 0;\n let spacing = ' '.repeat(indent + 4);\n writer.write(spacing).write('@connect(\\n');\n\n spacing = ' '.repeat(indent + 6);\n writer.write(spacing).write('source: \"api\"\\n').write(spacing).write('http: ');\n\n this.requestMethod(context, writer, op, selection, indent);\n\n writer.write('\\n').write(spacing).write('selection: \"\"\"\\n');\n\n // truthiness, not _.has — the declared-but-unset field is still an own property. #33\n if (op.resultType) {\n this.writeSelection(context, writer, op.resultType, selection);\n }\n\n writer.write(spacing).write('\"\"\"\\n');\n\n this.errorsWriter.write(context, writer, op, indent);\n\n spacing = ' '.repeat(indent + 4);\n writer.write(spacing).write(')\\n');\n }\n\n private requestMethod(context: OasContext, writer: Writer, op: Op, selection: string[], _indent: number): void {\n const override = context.generateOptions.overrides?.[op.id];\n\n // the verb + path first, then query params, headers and lastly the body\n writer.write(`{ ${op.verb}: `).write('\"' + this.templatedPath(op, override) + '\"');\n // each block starts on its own line and ends without a trailing newline\n const wroteQueryParams = this.writeQueryParams(context, writer, op, override);\n const wroteHeaders = this.writeHeaders(writer, op, override);\n\n // body (POST, PUT, etc.): an override (raw JSONSelection) replaces the inferred\n // `$args.input { … }` mapping; null drops the body altogether. see ROADMAP R9\n if (typeof override?.body === 'string') {\n // body emits its own trailing indent, so close the object right after it\n this.writeBodyOverride(writer, override.body);\n writer.write('}');\n } else if (override?.body === undefined && op.body) {\n // body emits its own trailing indent, so close the object right after it\n this.writeBodySelection(context, writer, op.body, selection);\n writer.write('}');\n } else if (wroteQueryParams || wroteHeaders) {\n // no body: close on its own line, aligned under `http:` (avoids a column-0 brace)\n writer.write('\\n' + ' '.repeat(6) + '}');\n } else {\n // nothing but the path: keep the compact single-line form `{ GET: \"/x\"}`\n writer.write('}');\n }\n }\n\n // template each {elem} as {$args.<sanitised>} (the arg name), not the raw OAS key. see docs/issues.md #2\n // an override path may already template (`{$args.id}`, `{$config.v}`) — leave `$` segments alone\n private templatedPath(op: Op, override?: RequestOverride): string {\n return (override?.path ?? op.operation.path).replace(/\\{([^}]+)\\}/g, (m, name) =>\n name.startsWith('$') ? m : `{$args.${Naming.genParamName(name)}}`,\n );\n }\n\n // returns true when a queryParams block was written (so the caller can place the closing brace)\n private writeQueryParams(context: OasContext, writer: Writer, op: Op, override?: RequestOverride): boolean {\n // we now include all query params, not just required ones. if they are not set,\n // then the connectors will not include them in the request.\n let queryParams = op.params.filter((p: Param) => {\n return p.parameter.in && p.parameter.in.toLowerCase() === 'query';\n });\n\n // Skip optional params if skipOptionalArgs is true\n if (context.generateOptions?.skipOptionalArgs) {\n queryParams = queryParams.filter((p: Param) => p.required);\n }\n\n // e.g. `\"api-version\": $('2024-01')` appended, `\"ids\": ids->joinNotNull(\";\")` replaced\n const entries = this.mergeOverrides(\n queryParams,\n override?.queryParams ?? {},\n (p) => `${Naming.genParamName(p.name)}${this.arrayJoin(p)}`,\n );\n if (entries.length === 0) {\n return false;\n }\n\n writer.write('\\n');\n let spacing = ' '.repeat(6);\n\n writer.write(spacing).write(`queryParams: \"\"\"\\n`);\n spacing = ' '.repeat(8);\n writer.write(spacing).write(`$args {\\n`);\n spacing = ' '.repeat(10);\n for (const [key, value] of entries) {\n writer.write(spacing).write(`\"${key}\": ${value}\\n`);\n }\n spacing = ' '.repeat(8);\n writer.write(spacing).write('}\\n');\n spacing = ' '.repeat(6);\n // no trailing newline — the caller adds the separator/brace\n writer.write(spacing).write('\"\"\"');\n return true;\n }\n\n // returns true when a headers block was written (so the caller can place the closing brace)\n private writeHeaders(writer: Writer, op: Op, override?: RequestOverride): boolean {\n // OAS `header` params, with user overrides merged in (string replaces, null drops)\n const headers = op.operation.getParameters().filter((p) => p.in && p.in.toLowerCase() === 'header');\n let entries = this.mergeOverrides(headers, override?.headers ?? {}, (p) => this.headerExample(p));\n\n // R5 slice 2: add this operation's effective auth header (only when the spec is in per-op mode).\n // HTTP header names are case-insensitive: an explicit user override of the same name wins;\n // otherwise the resolved auth replaces any inferred OAS header of that name (so we never emit\n // a real credential alongside a placeholder that differs only in case).\n const auth = this.authHeaderEntry(op);\n if (auth) {\n const sameName = (name: string) => name.toLowerCase() === auth[0].toLowerCase();\n const overridden = Object.keys(override?.headers ?? {}).some(sameName);\n if (!overridden) {\n entries = [auth, ...entries.filter(([name]) => !sameName(name))];\n }\n }\n\n if (entries.length === 0) {\n return false;\n }\n\n let spacing = ' '.repeat(6);\n // leading newline so the block starts on its own line (after the path or a queryParams block)\n writer.write('\\n' + spacing + 'headers: [\\n');\n spacing = ' '.repeat(8);\n\n for (const [key, value] of entries) {\n writer.write(spacing + `{ name: \"${key}\", value: \"${value}\" }\\n`);\n }\n\n spacing = ' '.repeat(6);\n // no trailing newline — the caller adds the separator/brace\n writer.write(spacing + ']');\n return true;\n }\n\n // R5 slice 2: this operation's effective auth header as a [name, value] entry, or null.\n //\n // Emits only in *per-op mode* — when some operation in the spec declares its own `security`,\n // the shared @source auth header is suppressed, so each @connect must carry its own. The\n // effective requirement is the op's own `security` when present, else the global default:\n // security: [{ AdminBearer: [] }] (own) -> that scheme's header (e.g. Authorization: Bearer)\n // security: [] (public) -> no header (the op correctly sends nothing)\n // no own `security` -> the inherited global header\n private authHeaderEntry(op: Op): [string, string] | null {\n const api = this.gen.parser;\n // uniform mode: @source already carries the shared auth, nothing to add per operation\n if (!anyOperationDeclaresSecurity(api)) {\n return null;\n }\n\n // the op's own requirement; `undefined` = inherit the global, `[]` = public\n const own = op.operation.schema.security;\n // `??` not `||`: an explicit `[]` (public) must NOT fall back to the global\n const effective = own ?? globalSecurity(api);\n\n // read the scheme definitions the requirement refers to\n const schemes = securitySchemes(api);\n // resolve the effective requirement to one header, collecting per-scheme drop warnings\n const { header, warnings } = resolveAuthHeader(effective, schemes);\n // surface dropped schemes loudly, tagged with this operation\n for (const w of warnings) {\n console.warn(`${w} (operation ${op.verb} ${op.operation.path})`);\n }\n return header ? [header.name, header.value] : null;\n }\n\n // merge user overrides over the inferred params: a string replaces the inferred value,\n // null drops the entry, an unknown key is appended. see ROADMAP R8\n private mergeOverrides<T extends { name: string }>(\n inferred: T[],\n overrides: Record<string, string | null>,\n inferredValue: (item: T) => string,\n ): Array<[string, string]> {\n const kept = inferred\n .filter((item) => overrides[item.name] !== null)\n .map((item): [string, string] => [item.name, overrides[item.name] ?? inferredValue(item)]);\n\n const known = new Set(inferred.map((item) => item.name));\n const appended = Object.entries(overrides).filter(\n (entry): entry is [string, string] => !known.has(entry[0]) && entry[1] != null,\n );\n\n return [...kept, ...appended];\n }\n\n // inferred header value: the example, the example keys, or a placeholder for the user to fill\n private headerExample(p: ParameterObject): string {\n if (p.example != null) {\n return p.example.toString();\n }\n if (p.examples && Object.keys(p.examples).length > 0) {\n return Object.keys(p.examples).join(',');\n }\n return '<placeholder>';\n }\n\n // a non-exploded array param (`?ids=1,2,3`) needs its values joined: `ids->joinNotNull(\",\")`. see ROADMAP R8\n // exploded arrays (the OAS default) already work as a plain array value\n private arrayJoin(p: Param): string {\n const parameter = p.parameter;\n if (_.get(parameter, 'schema.type') !== 'array' || parameter.explode !== false) {\n return '';\n }\n const delimiter = parameter.style === 'spaceDelimited' ? ' ' : parameter.style === 'pipeDelimited' ? '|' : ',';\n return `->joinNotNull(\"${delimiter}\")`;\n }\n\n private writeSelection(context: OasContext, writer: Writer, type: IType, selection: string[]): void {\n context.indent = 6;\n type.select(context, writer, selection);\n }\n\n // mirrors Body.select formatting, but the user's raw JSONSelection replaces the whole mapping\n private writeBodyOverride(writer: Writer, body: string): void {\n writer.write(',\\n');\n const spacing = ' '.repeat(6);\n writer.write(spacing + 'body: \"\"\"\\n');\n for (const line of body.split('\\n')) {\n writer.write(spacing + ' ' + line + '\\n');\n }\n writer.write(spacing + '\"\"\"\\n' + ' '.repeat(5));\n }\n\n private writeBodySelection(context: OasContext, writer: Writer, body: Body, selection: string[]): void {\n writer.write(',\\n');\n context.indent = 6;\n if (body) {\n body.select(context, writer, selection);\n }\n }\n}\n"]}
@@ -6,17 +6,15 @@ export declare class SchemaWriter {
6
6
  writeJSONScalar(writer: Writer): void;
7
7
  writeDirectives(writer: Writer): void;
8
8
  /**
9
- * R5 (slice 1): derive an `@source`-level auth header from the spec's *global*
10
- * security requirement. Returns the header literal (e.g.
11
- * `{ name: "Authorization", value: "Bearer {$config.token}" }`) or null when no
12
- * header should be emitted. Everything deferred is warned about, never silently dropped.
9
+ * R5: derive an `@source`-level auth header from the spec's *global* security requirement.
10
+ * Returns the header literal (e.g. `{ name: "Authorization", value: "Bearer {$config.token}" }`)
11
+ * or null when no header should be emitted on `@source`.
12
+ *
13
+ * Mode switch (slice 2): if any operation declares its own `security`, auth moves to each
14
+ * operation's `@connect` (see operationWriter) and nothing is emitted here — a shared `@source`
15
+ * header is unsafe then (a public op would still send it; a different-named override would send
16
+ * both). Otherwise (uniform mode, slice 1) the global requirement is emitted once on `@source`.
13
17
  */
14
18
  private securityHeader;
15
- /** Map a single security scheme to its `@source` header literal, or null when it produces no header. */
16
- private mapScheme;
17
- /** Build a loud warning for a scheme that was not emitted on `@source`. */
18
- private dropWarning;
19
- /** Returns "METHOD /path" for every operation that declares its own `security` (including `security: []`). */
20
- private operationsWithSecurity;
21
19
  private getServerUrl;
22
20
  }
@@ -1,4 +1,5 @@
1
1
  import { DEFAULT_VERSIONS } from '../../versions.js';
2
+ import { anyOperationDeclaresSecurity, globalSecurity, securitySchemes, resolveAuthHeader } from './security.js';
2
3
  export class SchemaWriter {
3
4
  gen;
4
5
  constructor(gen) {
@@ -29,117 +30,42 @@ export class SchemaWriter {
29
30
  }
30
31
  }
31
32
  /**
32
- * R5 (slice 1): derive an `@source`-level auth header from the spec's *global*
33
- * security requirement. Returns the header literal (e.g.
34
- * `{ name: "Authorization", value: "Bearer {$config.token}" }`) or null when no
35
- * header should be emitted. Everything deferred is warned about, never silently dropped.
33
+ * R5: derive an `@source`-level auth header from the spec's *global* security requirement.
34
+ * Returns the header literal (e.g. `{ name: "Authorization", value: "Bearer {$config.token}" }`)
35
+ * or null when no header should be emitted on `@source`.
36
+ *
37
+ * Mode switch (slice 2): if any operation declares its own `security`, auth moves to each
38
+ * operation's `@connect` (see operationWriter) and nothing is emitted here — a shared `@source`
39
+ * header is unsafe then (a public op would still send it; a different-named override would send
40
+ * both). Otherwise (uniform mode, slice 1) the global requirement is emitted once on `@source`.
36
41
  */
37
42
  securityHeader(api) {
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- const def = api.getDefinition();
40
- const global = def.security;
41
- // OAS3 components.securitySchemes, falling back to Swagger 2.0 securityDefinitions
42
- // (oas-normalize usually up-converts, but keep the fallback for safety).
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
- const schemes = (def.components?.securitySchemes ?? def.securityDefinitions ?? {});
45
- // Concern-2 guard: a per-operation `security` (including anonymous `security: []`)
46
- // *replaces* the global requirement, so the requirement is not uniform across
47
- // endpoints. A shared @source header would wrongly attach auth to overriding
48
- // endpoints — emit nothing and warn per affected operation. Checked before the
49
- // global guard so per-op-only specs (e.g. petstore) still warn loudly.
50
- const overriding = this.operationsWithSecurity(api);
51
- if (overriding.length > 0) {
52
- for (const op of overriding) {
53
- console.warn(`[security] operation ${op} declares its own \`security\`; not emitting a global @source auth header (per-operation auth deferred to a future slice).`);
54
- }
43
+ // per-op mode: some operation overrides the global → let each @connect carry its own auth
44
+ if (anyOperationDeclaresSecurity(api)) {
55
45
  return null;
56
46
  }
57
- // No global requirement → keep the current headerless @source byte-for-byte.
47
+ // read the document-level (global) security requirement
48
+ const global = globalSecurity(api);
49
+ // no global requirement → keep the headerless @source byte-for-byte
58
50
  if (!global || global.length === 0) {
59
51
  return null;
60
52
  }
61
- // Flatten the global requirement to referenced scheme names, in order: across
62
- // requirement objects (logical OR) and within each object (logical AND).
63
- const names = [];
64
- for (const req of global) {
65
- for (const name of Object.keys(req)) {
66
- names.push(name);
67
- }
68
- }
69
- // Pick the first scheme that maps to a header; warn about every other scheme.
70
- let header = null;
71
- for (const name of names) {
72
- const scheme = schemes[name];
73
- const mapped = scheme ? this.mapScheme(scheme) : null;
74
- if (mapped && !header) {
75
- header = mapped;
76
- }
77
- else {
78
- console.warn(this.dropWarning(name, scheme));
79
- }
53
+ // read the scheme definitions (apiKey/http/oauth2/...) the requirement refers to
54
+ const schemes = securitySchemes(api);
55
+ // resolve the global requirement to one header, collecting per-scheme drop warnings
56
+ const { header, warnings } = resolveAuthHeader(global, schemes);
57
+ // surface every dropped scheme loudly (never silently)
58
+ for (const w of warnings) {
59
+ console.warn(w);
80
60
  }
81
61
  if (!header) {
62
+ // flatten the referenced scheme names for the summary message
63
+ const names = global.flatMap((req) => Object.keys(req));
82
64
  console.warn(`[security] global security requirement (${names.join(', ')}) maps to no header-producing scheme; no auth header emitted on @source.`);
83
65
  return null;
84
66
  }
85
- return header;
86
- }
87
- /** Map a single security scheme to its `@source` header literal, or null when it produces no header. */
88
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
- mapScheme(scheme) {
90
- switch (scheme.type) {
91
- case 'apiKey':
92
- if (scheme.in === 'header' && scheme.name) {
93
- return `{ name: "${scheme.name}", value: "{$config.apiKey}" }`;
94
- }
95
- return null; // query / cookie handled via warnings
96
- case 'http':
97
- if (scheme.scheme === 'bearer') {
98
- return `{ name: "Authorization", value: "Bearer {$config.token}" }`;
99
- }
100
- if (scheme.scheme === 'basic') {
101
- return `{ name: "Authorization", value: "Basic {$config.token}" }`;
102
- }
103
- return null;
104
- case 'oauth2':
105
- case 'openIdConnect':
106
- return `{ name: "Authorization", value: "Bearer {$config.token}" }`;
107
- default:
108
- return null;
109
- }
110
- }
111
- /** Build a loud warning for a scheme that was not emitted on `@source`. */
112
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
- dropWarning(name, scheme) {
114
- if (!scheme) {
115
- return `[security] scheme "${name}" is referenced by global security but not defined; skipped.`;
116
- }
117
- if (scheme.type === 'apiKey' && scheme.in === 'query') {
118
- return `[security] scheme "${name}" is apiKey in query — not emitted as a header (query-param auth deferred).`;
119
- }
120
- if (scheme.type === 'apiKey' && scheme.in === 'cookie') {
121
- return `[security] scheme "${name}" is apiKey in cookie — not emitted as a header (cookie auth deferred).`;
122
- }
123
- return `[security] scheme "${name}" (${scheme.type}) not emitted — only the first header-producing scheme of the global requirement is mapped on @source.`;
124
- }
125
- /** Returns "METHOD /path" for every operation that declares its own `security` (including `security: []`). */
126
- operationsWithSecurity(api) {
127
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
- const paths = api.getDefinition().paths ?? {};
129
- const methods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
130
- const result = [];
131
- for (const p of Object.keys(paths)) {
132
- const item = paths[p];
133
- if (!item)
134
- continue;
135
- for (const m of methods) {
136
- const op = item[m];
137
- if (op && Object.prototype.hasOwnProperty.call(op, 'security')) {
138
- result.push(`${m.toUpperCase()} ${p}`);
139
- }
140
- }
141
- }
142
- return result;
67
+ // format the resolved header into the @source literal
68
+ return `{ name: "${header.name}", value: "${header.value}" }`;
143
69
  }
144
70
  getServerUrl(server) {
145
71
  if (!server) {
@@ -1 +1 @@
1
- {"version":3,"file":"schemaWriter.js","sourceRoot":"","sources":["../../../src/oas/io/schemaWriter.ts"],"names":[],"mappings":"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","sourcesContent":["import Oas from 'oas';\nimport { ServerObject } from 'oas/types';\nimport { DEFAULT_VERSIONS } from '../../versions.js';\nimport { OasGen } from '../oasGen.js';\nimport { Writer } from './writer.js';\n\nexport class SchemaWriter {\n constructor(private gen: OasGen) {}\n\n public writeJSONScalar(writer: Writer): void {\n writer.write('\\nscalar JSON\\n\\n');\n }\n\n public writeDirectives(writer: Writer): void {\n const api: Oas = this.gen.parser;\n // a spec's servers[0] can be stale or wrong (petstore) — an explicit baseURL wins\n const host = this.gen.options.baseURL ?? this.getServerUrl(api.getDefinition().servers?.[0]);\n const federationVersion = this.gen.options.federationVersion || DEFAULT_VERSIONS.federationVersion;\n const connectorSpecVersion = this.gen.options.connectorSpecVersion || DEFAULT_VERSIONS.connectorSpecVersion;\n const authHeader = this.securityHeader(api);\n writer\n .write('extend schema\\n')\n .write(` @link(url: \"https://specs.apollo.dev/federation/${federationVersion}\", import: [\"@key\"])\\n`)\n .write(' @link(\\n')\n .write(` url: \"https://specs.apollo.dev/connect/${connectorSpecVersion}\"\\n`)\n .write(' import: [\"@connect\", \"@source\"]\\n')\n .write(' )\\n');\n\n if (authHeader) {\n writer.write(` @source(name: \"api\", http: { baseURL: \"${host}\", headers: [${authHeader}] })\\n\\n`);\n } else {\n writer.write(' @source(name: \"api\", http: { baseURL: \"').write(host).write('\" })\\n\\n');\n }\n }\n\n /**\n * R5 (slice 1): derive an `@source`-level auth header from the spec's *global*\n * security requirement. Returns the header literal (e.g.\n * `{ name: \"Authorization\", value: \"Bearer {$config.token}\" }`) or null when no\n * header should be emitted. Everything deferred is warned about, never silently dropped.\n */\n private securityHeader(api: Oas): string | null {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const def = api.getDefinition() as any;\n const global = def.security as Array<Record<string, string[]>> | undefined;\n // OAS3 components.securitySchemes, falling back to Swagger 2.0 securityDefinitions\n // (oas-normalize usually up-converts, but keep the fallback for safety).\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const schemes = (def.components?.securitySchemes ?? def.securityDefinitions ?? {}) as Record<string, any>;\n\n // Concern-2 guard: a per-operation `security` (including anonymous `security: []`)\n // *replaces* the global requirement, so the requirement is not uniform across\n // endpoints. A shared @source header would wrongly attach auth to overriding\n // endpoints — emit nothing and warn per affected operation. Checked before the\n // global guard so per-op-only specs (e.g. petstore) still warn loudly.\n const overriding = this.operationsWithSecurity(api);\n if (overriding.length > 0) {\n for (const op of overriding) {\n console.warn(\n `[security] operation ${op} declares its own \\`security\\`; not emitting a global @source auth header (per-operation auth deferred to a future slice).`,\n );\n }\n return null;\n }\n\n // No global requirement → keep the current headerless @source byte-for-byte.\n if (!global || global.length === 0) {\n return null;\n }\n\n // Flatten the global requirement to referenced scheme names, in order: across\n // requirement objects (logical OR) and within each object (logical AND).\n const names: string[] = [];\n for (const req of global) {\n for (const name of Object.keys(req)) {\n names.push(name);\n }\n }\n\n // Pick the first scheme that maps to a header; warn about every other scheme.\n let header: string | null = null;\n for (const name of names) {\n const scheme = schemes[name];\n const mapped = scheme ? this.mapScheme(scheme) : null;\n if (mapped && !header) {\n header = mapped;\n } else {\n console.warn(this.dropWarning(name, scheme));\n }\n }\n\n if (!header) {\n console.warn(\n `[security] global security requirement (${names.join(', ')}) maps to no header-producing scheme; no auth header emitted on @source.`,\n );\n return null;\n }\n\n return header;\n }\n\n /** Map a single security scheme to its `@source` header literal, or null when it produces no header. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private mapScheme(scheme: any): string | null {\n switch (scheme.type) {\n case 'apiKey':\n if (scheme.in === 'header' && scheme.name) {\n return `{ name: \"${scheme.name}\", value: \"{$config.apiKey}\" }`;\n }\n return null; // query / cookie handled via warnings\n case 'http':\n if (scheme.scheme === 'bearer') {\n return `{ name: \"Authorization\", value: \"Bearer {$config.token}\" }`;\n }\n if (scheme.scheme === 'basic') {\n return `{ name: \"Authorization\", value: \"Basic {$config.token}\" }`;\n }\n return null;\n case 'oauth2':\n case 'openIdConnect':\n return `{ name: \"Authorization\", value: \"Bearer {$config.token}\" }`;\n default:\n return null;\n }\n }\n\n /** Build a loud warning for a scheme that was not emitted on `@source`. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private dropWarning(name: string, scheme: any): string {\n if (!scheme) {\n return `[security] scheme \"${name}\" is referenced by global security but not defined; skipped.`;\n }\n if (scheme.type === 'apiKey' && scheme.in === 'query') {\n return `[security] scheme \"${name}\" is apiKey in query — not emitted as a header (query-param auth deferred).`;\n }\n if (scheme.type === 'apiKey' && scheme.in === 'cookie') {\n return `[security] scheme \"${name}\" is apiKey in cookie — not emitted as a header (cookie auth deferred).`;\n }\n return `[security] scheme \"${name}\" (${scheme.type}) not emitted — only the first header-producing scheme of the global requirement is mapped on @source.`;\n }\n\n /** Returns \"METHOD /path\" for every operation that declares its own `security` (including `security: []`). */\n private operationsWithSecurity(api: Oas): string[] {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const paths = (api.getDefinition() as any).paths ?? {};\n const methods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];\n const result: string[] = [];\n for (const p of Object.keys(paths)) {\n const item = paths[p];\n if (!item) continue;\n for (const m of methods) {\n const op = item[m];\n if (op && Object.prototype.hasOwnProperty.call(op, 'security')) {\n result.push(`${m.toUpperCase()} ${p}`);\n }\n }\n }\n return result;\n }\n\n private getServerUrl(server: ServerObject | undefined): string {\n if (!server) {\n return 'http://localhost:4010';\n }\n let url: string = server.url;\n if (server.variables) {\n for (const key in server.variables) {\n url = url.replace('{' + key + '}', server.variables[key].default);\n }\n }\n return url;\n }\n}\n"]}
1
+ {"version":3,"file":"schemaWriter.js","sourceRoot":"","sources":["../../../src/oas/io/schemaWriter.ts"],"names":[],"mappings":"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","sourcesContent":["import Oas from 'oas';\nimport { ServerObject } from 'oas/types';\nimport { DEFAULT_VERSIONS } from '../../versions.js';\nimport { OasGen } from '../oasGen.js';\nimport { Writer } from './writer.js';\nimport { anyOperationDeclaresSecurity, globalSecurity, securitySchemes, resolveAuthHeader } from './security.js';\n\nexport class SchemaWriter {\n constructor(private gen: OasGen) {}\n\n public writeJSONScalar(writer: Writer): void {\n writer.write('\\nscalar JSON\\n\\n');\n }\n\n public writeDirectives(writer: Writer): void {\n const api: Oas = this.gen.parser;\n // a spec's servers[0] can be stale or wrong (petstore) — an explicit baseURL wins\n const host = this.gen.options.baseURL ?? this.getServerUrl(api.getDefinition().servers?.[0]);\n const federationVersion = this.gen.options.federationVersion || DEFAULT_VERSIONS.federationVersion;\n const connectorSpecVersion = this.gen.options.connectorSpecVersion || DEFAULT_VERSIONS.connectorSpecVersion;\n const authHeader = this.securityHeader(api);\n writer\n .write('extend schema\\n')\n .write(` @link(url: \"https://specs.apollo.dev/federation/${federationVersion}\", import: [\"@key\"])\\n`)\n .write(' @link(\\n')\n .write(` url: \"https://specs.apollo.dev/connect/${connectorSpecVersion}\"\\n`)\n .write(' import: [\"@connect\", \"@source\"]\\n')\n .write(' )\\n');\n\n if (authHeader) {\n writer.write(` @source(name: \"api\", http: { baseURL: \"${host}\", headers: [${authHeader}] })\\n\\n`);\n } else {\n writer.write(' @source(name: \"api\", http: { baseURL: \"').write(host).write('\" })\\n\\n');\n }\n }\n\n /**\n * R5: derive an `@source`-level auth header from the spec's *global* security requirement.\n * Returns the header literal (e.g. `{ name: \"Authorization\", value: \"Bearer {$config.token}\" }`)\n * or null when no header should be emitted on `@source`.\n *\n * Mode switch (slice 2): if any operation declares its own `security`, auth moves to each\n * operation's `@connect` (see operationWriter) and nothing is emitted here — a shared `@source`\n * header is unsafe then (a public op would still send it; a different-named override would send\n * both). Otherwise (uniform mode, slice 1) the global requirement is emitted once on `@source`.\n */\n private securityHeader(api: Oas): string | null {\n // per-op mode: some operation overrides the global → let each @connect carry its own auth\n if (anyOperationDeclaresSecurity(api)) {\n return null;\n }\n\n // read the document-level (global) security requirement\n const global = globalSecurity(api);\n // no global requirement → keep the headerless @source byte-for-byte\n if (!global || global.length === 0) {\n return null;\n }\n\n // read the scheme definitions (apiKey/http/oauth2/...) the requirement refers to\n const schemes = securitySchemes(api);\n // resolve the global requirement to one header, collecting per-scheme drop warnings\n const { header, warnings } = resolveAuthHeader(global, schemes);\n // surface every dropped scheme loudly (never silently)\n for (const w of warnings) {\n console.warn(w);\n }\n\n if (!header) {\n // flatten the referenced scheme names for the summary message\n const names = global.flatMap((req) => Object.keys(req));\n console.warn(\n `[security] global security requirement (${names.join(', ')}) maps to no header-producing scheme; no auth header emitted on @source.`,\n );\n return null;\n }\n\n // format the resolved header into the @source literal\n return `{ name: \"${header.name}\", value: \"${header.value}\" }`;\n }\n\n private getServerUrl(server: ServerObject | undefined): string {\n if (!server) {\n return 'http://localhost:4010';\n }\n let url: string = server.url;\n if (server.variables) {\n for (const key in server.variables) {\n url = url.replace('{' + key + '}', server.variables[key].default);\n }\n }\n return url;\n }\n}\n"]}
@@ -0,0 +1,35 @@
1
+ import Oas from 'oas';
2
+ import { SecurityRequirementObject, SecuritySchemeObject, SecuritySchemesObject } from 'oas/types';
3
+ export type AuthHeader = {
4
+ name: string;
5
+ value: string;
6
+ };
7
+ /**
8
+ * Map one OAS security scheme to its connector header, or null when it makes no header.
9
+ * Shared by the @source writer (slice 1) and the per-operation @connect writer (slice 2).
10
+ */
11
+ export declare function mapSchemeToAuthHeader(scheme: SecuritySchemeObject): AuthHeader | null;
12
+ /** Build a loud warning for a scheme that was not turned into a header. */
13
+ export declare function dropWarning(name: string, scheme: SecuritySchemeObject | undefined): string;
14
+ /**
15
+ * Resolve a security requirement to a single header, reusing the slice-1 rule: flatten the
16
+ * referenced scheme names and pick the first that produces a header; every other scheme is
17
+ * warned about. An absent (`undefined`) or empty (`[]`, i.e. public) requirement makes no
18
+ * header. The caller decides what a null header means in its context (see the mode switch in
19
+ * schemaWriter / operationWriter) and emits the returned warnings with its own prefix.
20
+ */
21
+ export declare function resolveAuthHeader(securityRequirements: SecurityRequirementObject[] | undefined, securitySchemes: SecuritySchemesObject): {
22
+ header: AuthHeader | null;
23
+ warnings: string[];
24
+ };
25
+ /** Read `components.securitySchemes` (OAS 3) falling back to `securityDefinitions` (Swagger 2.0). */
26
+ export declare function securitySchemes(api: Oas): SecuritySchemesObject;
27
+ /** The spec's global `security` requirement (the document-level default), or undefined. */
28
+ export declare function globalSecurity(api: Oas): SecurityRequirementObject[] | undefined;
29
+ /**
30
+ * True when ANY operation in the spec declares its own `security` (empty `[]` or non-empty).
31
+ * This is the per-source mode switch: when true, a shared `@source` auth header is unsafe
32
+ * (a public op would still leak it, and a different-named override would send both), so auth
33
+ * moves to each operation's `@connect` instead. See the ROADMAP R5 slice 2 notes.
34
+ */
35
+ export declare function anyOperationDeclaresSecurity(api: Oas): boolean;
@@ -0,0 +1,111 @@
1
+ import _ from 'lodash';
2
+ import { OpenAPIV3 } from 'openapi-types';
3
+ // The HTTP methods a path item can hold — reuse the canonical enum rather than a local literal.
4
+ const HTTP_METHODS = Object.values(OpenAPIV3.HttpMethods);
5
+ /**
6
+ * Map one OAS security scheme to its connector header, or null when it makes no header.
7
+ * Shared by the @source writer (slice 1) and the per-operation @connect writer (slice 2).
8
+ */
9
+ export function mapSchemeToAuthHeader(scheme) {
10
+ switch (scheme.type) {
11
+ case 'apiKey':
12
+ // apiKey carried in a header, e.g. `{ type: apiKey, in: header, name: X-API-Key }`
13
+ if (scheme.in === 'header' && scheme.name) {
14
+ return { name: scheme.name, value: '{$config.apiKey}' };
15
+ }
16
+ return null; // query / cookie are deferred and warned about, not emitted as headers
17
+ case 'http':
18
+ // e.g. `{ type: http, scheme: bearer }`
19
+ if (scheme.scheme === 'bearer') {
20
+ return { name: 'Authorization', value: 'Bearer {$config.token}' };
21
+ }
22
+ // e.g. `{ type: http, scheme: basic }`
23
+ if (scheme.scheme === 'basic') {
24
+ return { name: 'Authorization', value: 'Basic {$config.token}' };
25
+ }
26
+ return null;
27
+ case 'oauth2':
28
+ case 'openIdConnect':
29
+ // both flows ultimately send a bearer token
30
+ return { name: 'Authorization', value: 'Bearer {$config.token}' };
31
+ default:
32
+ return null;
33
+ }
34
+ }
35
+ /** Build a loud warning for a scheme that was not turned into a header. */
36
+ export function dropWarning(name, scheme) {
37
+ if (!scheme) {
38
+ return `[security] scheme "${name}" is referenced by global security but not defined; skipped.`;
39
+ }
40
+ if (scheme.type === 'apiKey' && scheme.in === 'query') {
41
+ return `[security] scheme "${name}" is apiKey in query — not emitted as a header (query-param auth deferred).`;
42
+ }
43
+ if (scheme.type === 'apiKey' && scheme.in === 'cookie') {
44
+ return `[security] scheme "${name}" is apiKey in cookie — not emitted as a header (cookie auth deferred).`;
45
+ }
46
+ return `[security] scheme "${name}" (${scheme.type}) not emitted — only the first header-producing scheme of the requirement is mapped.`;
47
+ }
48
+ /**
49
+ * Resolve a security requirement to a single header, reusing the slice-1 rule: flatten the
50
+ * referenced scheme names and pick the first that produces a header; every other scheme is
51
+ * warned about. An absent (`undefined`) or empty (`[]`, i.e. public) requirement makes no
52
+ * header. The caller decides what a null header means in its context (see the mode switch in
53
+ * schemaWriter / operationWriter) and emits the returned warnings with its own prefix.
54
+ */
55
+ export function resolveAuthHeader(
56
+ // e.g. `[{ bearerAuth: [] }]` (one requirement, one scheme) or `[{ a: [] }, { b: [] }]` (OR of schemes)
57
+ securityRequirements, securitySchemes) {
58
+ // absent or `security: []` (public) → no header, nothing to warn about
59
+ if (!securityRequirements?.length) {
60
+ return { header: null, warnings: [] };
61
+ }
62
+ // flatten to scheme names (across objects = OR, within each = AND), then resolve each to a header
63
+ // (null if it makes none, e.g. apiKey in query)
64
+ const candidates = securityRequirements
65
+ .flatMap((requirement) => Object.keys(requirement))
66
+ .map((name) => {
67
+ const scheme = securitySchemes[name];
68
+ return { name, scheme, header: scheme ? mapSchemeToAuthHeader(scheme) : null };
69
+ });
70
+ // the first scheme that produces a header wins; every other referenced scheme is warned about
71
+ const chosen = candidates.find((candidate) => candidate.header);
72
+ const warnings = candidates
73
+ .filter((candidate) => candidate !== chosen)
74
+ .map((candidate) => dropWarning(candidate.name, candidate.scheme));
75
+ return { header: chosen?.header ?? null, warnings };
76
+ }
77
+ /** Read `components.securitySchemes` (OAS 3) falling back to `securityDefinitions` (Swagger 2.0). */
78
+ export function securitySchemes(api) {
79
+ const def = api.getDefinition();
80
+ // oas-normalize usually up-converts Swagger 2.0 → components.securitySchemes; the
81
+ // securityDefinitions fallback (same shape, exposed via OASDocument's index signature) is a
82
+ // safety net. refs are already dereferenced by oas, so the shape is a plain SecuritySchemesObject.
83
+ return (def.components?.securitySchemes ?? def.securityDefinitions ?? {});
84
+ }
85
+ /** The spec's global `security` requirement (the document-level default), or undefined. */
86
+ export function globalSecurity(api) {
87
+ return api.getDefinition().security;
88
+ }
89
+ /**
90
+ * True when ANY operation in the spec declares its own `security` (empty `[]` or non-empty).
91
+ * This is the per-source mode switch: when true, a shared `@source` auth header is unsafe
92
+ * (a public op would still leak it, and a different-named override would send both), so auth
93
+ * moves to each operation's `@connect` instead. See the ROADMAP R5 slice 2 notes.
94
+ */
95
+ export function anyOperationDeclaresSecurity(api) {
96
+ const paths = api.getDefinition().paths ?? {};
97
+ for (const pathName of Object.keys(paths)) {
98
+ const path = paths[pathName];
99
+ if (!path)
100
+ continue;
101
+ for (const method of HTTP_METHODS) {
102
+ const op = path[method];
103
+ // own property (not inherited) — an explicit `security: []` is still a declaration
104
+ if (op && _.has(op, 'security')) {
105
+ return true;
106
+ }
107
+ }
108
+ }
109
+ return false;
110
+ }
111
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../../src/oas/io/security.ts"],"names":[],"mappings":"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","sourcesContent":["import _ from 'lodash';\nimport Oas from 'oas';\nimport { SecurityRequirementObject, SecuritySchemeObject, SecuritySchemesObject } from 'oas/types';\nimport { OpenAPIV3 } from 'openapi-types';\n\n// One auth header: the header name and its value template (e.g. Authorization / \"Bearer {$config.token}\").\nexport type AuthHeader = { name: string; value: string };\n\n// The HTTP methods a path item can hold — reuse the canonical enum rather than a local literal.\nconst HTTP_METHODS = Object.values(OpenAPIV3.HttpMethods);\n\n/**\n * Map one OAS security scheme to its connector header, or null when it makes no header.\n * Shared by the @source writer (slice 1) and the per-operation @connect writer (slice 2).\n */\nexport function mapSchemeToAuthHeader(scheme: SecuritySchemeObject): AuthHeader | null {\n switch (scheme.type) {\n case 'apiKey':\n // apiKey carried in a header, e.g. `{ type: apiKey, in: header, name: X-API-Key }`\n if (scheme.in === 'header' && scheme.name) {\n return { name: scheme.name, value: '{$config.apiKey}' };\n }\n return null; // query / cookie are deferred and warned about, not emitted as headers\n case 'http':\n // e.g. `{ type: http, scheme: bearer }`\n if (scheme.scheme === 'bearer') {\n return { name: 'Authorization', value: 'Bearer {$config.token}' };\n }\n // e.g. `{ type: http, scheme: basic }`\n if (scheme.scheme === 'basic') {\n return { name: 'Authorization', value: 'Basic {$config.token}' };\n }\n return null;\n case 'oauth2':\n case 'openIdConnect':\n // both flows ultimately send a bearer token\n return { name: 'Authorization', value: 'Bearer {$config.token}' };\n default:\n return null;\n }\n}\n\n/** Build a loud warning for a scheme that was not turned into a header. */\nexport function dropWarning(name: string, scheme: SecuritySchemeObject | undefined): string {\n if (!scheme) {\n return `[security] scheme \"${name}\" is referenced by global security but not defined; skipped.`;\n }\n if (scheme.type === 'apiKey' && scheme.in === 'query') {\n return `[security] scheme \"${name}\" is apiKey in query — not emitted as a header (query-param auth deferred).`;\n }\n if (scheme.type === 'apiKey' && scheme.in === 'cookie') {\n return `[security] scheme \"${name}\" is apiKey in cookie — not emitted as a header (cookie auth deferred).`;\n }\n return `[security] scheme \"${name}\" (${scheme.type}) not emitted — only the first header-producing scheme of the requirement is mapped.`;\n}\n\n/**\n * Resolve a security requirement to a single header, reusing the slice-1 rule: flatten the\n * referenced scheme names and pick the first that produces a header; every other scheme is\n * warned about. An absent (`undefined`) or empty (`[]`, i.e. public) requirement makes no\n * header. The caller decides what a null header means in its context (see the mode switch in\n * schemaWriter / operationWriter) and emits the returned warnings with its own prefix.\n */\nexport function resolveAuthHeader(\n // e.g. `[{ bearerAuth: [] }]` (one requirement, one scheme) or `[{ a: [] }, { b: [] }]` (OR of schemes)\n securityRequirements: SecurityRequirementObject[] | undefined,\n securitySchemes: SecuritySchemesObject,\n): { header: AuthHeader | null; warnings: string[] } {\n // absent or `security: []` (public) → no header, nothing to warn about\n if (!securityRequirements?.length) {\n return { header: null, warnings: [] };\n }\n\n // flatten to scheme names (across objects = OR, within each = AND), then resolve each to a header\n // (null if it makes none, e.g. apiKey in query)\n const candidates = securityRequirements\n .flatMap((requirement) => Object.keys(requirement))\n .map((name) => {\n const scheme = securitySchemes[name];\n return { name, scheme, header: scheme ? mapSchemeToAuthHeader(scheme) : null };\n });\n\n // the first scheme that produces a header wins; every other referenced scheme is warned about\n const chosen = candidates.find((candidate) => candidate.header);\n const warnings = candidates\n .filter((candidate) => candidate !== chosen)\n .map((candidate) => dropWarning(candidate.name, candidate.scheme));\n\n return { header: chosen?.header ?? null, warnings };\n}\n\n/** Read `components.securitySchemes` (OAS 3) falling back to `securityDefinitions` (Swagger 2.0). */\nexport function securitySchemes(api: Oas): SecuritySchemesObject {\n const def = api.getDefinition();\n // oas-normalize usually up-converts Swagger 2.0 → components.securitySchemes; the\n // securityDefinitions fallback (same shape, exposed via OASDocument's index signature) is a\n // safety net. refs are already dereferenced by oas, so the shape is a plain SecuritySchemesObject.\n return (def.components?.securitySchemes ?? def.securityDefinitions ?? {}) as SecuritySchemesObject;\n}\n\n/** The spec's global `security` requirement (the document-level default), or undefined. */\nexport function globalSecurity(api: Oas): SecurityRequirementObject[] | undefined {\n return api.getDefinition().security;\n}\n\n/**\n * True when ANY operation in the spec declares its own `security` (empty `[]` or non-empty).\n * This is the per-source mode switch: when true, a shared `@source` auth header is unsafe\n * (a public op would still leak it, and a different-named override would send both), so auth\n * moves to each operation's `@connect` instead. See the ROADMAP R5 slice 2 notes.\n */\nexport function anyOperationDeclaresSecurity(api: Oas): boolean {\n const paths = api.getDefinition().paths ?? {};\n\n for (const pathName of Object.keys(paths)) {\n const path = paths[pathName];\n if (!path) continue;\n\n for (const method of HTTP_METHODS) {\n const op = path[method];\n // own property (not inherited) — an explicit `security: []` is still a declaration\n if (op && _.has(op, 'security')) {\n return true;\n }\n }\n }\n return false;\n}\n"]}
@@ -1 +1 @@
1
- {"root":["../src/index.ts","../src/versions.ts","../src/cli/json.ts","../src/cli/oas.ts","../src/cli/oas-helpers/index.ts","../src/json/index.ts","../src/json/io/index.ts","../src/json/io/writer.ts","../src/json/walker/index.ts","../src/json/walker/jsoncontext.ts","../src/json/walker/jsongen.ts","../src/json/walker/naming.ts","../src/json/walker/log/trace.ts","../src/json/walker/types/index.ts","../src/json/walker/types/jsonarray.ts","../src/json/walker/types/jsonobj.ts","../src/json/walker/types/jsonscalar.ts","../src/json/walker/types/jsontype.ts","../src/oas/index.ts","../src/oas/oascontext.ts","../src/oas/oasgen.ts","../src/oas/generator/typescollector.ts","../src/oas/io/errorswriter.ts","../src/oas/io/operationwriter.ts","../src/oas/io/schemawriter.ts","../src/oas/io/writer.ts","../src/oas/log/trace.ts","../src/oas/mapper/index.ts","../src/oas/mapper/loader.ts","../src/oas/mapper/namemapper.ts","../src/oas/mapper/types.ts","../src/oas/nodes/arr.ts","../src/oas/nodes/body.ts","../src/oas/nodes/circularref.ts","../src/oas/nodes/comp.ts","../src/oas/nodes/delete.ts","../src/oas/nodes/en.ts","../src/oas/nodes/entity.ts","../src/oas/nodes/factory.ts","../src/oas/nodes/get.ts","../src/oas/nodes/itype.ts","../src/oas/nodes/interfacepromotion.ts","../src/oas/nodes/internal.ts","../src/oas/nodes/map.ts","../src/oas/nodes/obj.ts","../src/oas/nodes/op.ts","../src/oas/nodes/param.ts","../src/oas/nodes/patch.ts","../src/oas/nodes/post.ts","../src/oas/nodes/prop.ts","../src/oas/nodes/proparray.ts","../src/oas/nodes/propcircref.ts","../src/oas/nodes/propcomp.ts","../src/oas/nodes/propen.ts","../src/oas/nodes/propmap.ts","../src/oas/nodes/propobj.ts","../src/oas/nodes/propref.ts","../src/oas/nodes/propscalar.ts","../src/oas/nodes/put.ts","../src/oas/nodes/ref.ts","../src/oas/nodes/refcircref.ts","../src/oas/nodes/referenceobject.ts","../src/oas/nodes/res.ts","../src/oas/nodes/scalar.ts","../src/oas/nodes/type.ts","../src/oas/nodes/typeutils.ts","../src/oas/nodes/union.ts","../src/oas/prompts/prompt.ts","../src/oas/prompts/theme.ts","../src/oas/prompts/base/theme.ts","../src/oas/prompts/base/utils.ts","../src/oas/schemas/index.ts","../src/oas/utils/gql.ts","../src/oas/utils/naming.ts","../src/oas/visitor/generatorvisitor.ts","../src/oas/visitor/nodevisitor.ts","../src/oas/visitor/example.ts","../src/oas/visitor/index.ts","../src/tests/runners.ts"],"version":"5.8.2"}
1
+ {"root":["../src/index.ts","../src/versions.ts","../src/cli/json.ts","../src/cli/oas.ts","../src/cli/oas-helpers/index.ts","../src/json/index.ts","../src/json/io/index.ts","../src/json/io/writer.ts","../src/json/walker/index.ts","../src/json/walker/jsoncontext.ts","../src/json/walker/jsongen.ts","../src/json/walker/naming.ts","../src/json/walker/log/trace.ts","../src/json/walker/types/index.ts","../src/json/walker/types/jsonarray.ts","../src/json/walker/types/jsonobj.ts","../src/json/walker/types/jsonscalar.ts","../src/json/walker/types/jsontype.ts","../src/oas/index.ts","../src/oas/oascontext.ts","../src/oas/oasgen.ts","../src/oas/generator/typescollector.ts","../src/oas/io/errorswriter.ts","../src/oas/io/operationwriter.ts","../src/oas/io/schemawriter.ts","../src/oas/io/security.ts","../src/oas/io/writer.ts","../src/oas/log/trace.ts","../src/oas/mapper/index.ts","../src/oas/mapper/loader.ts","../src/oas/mapper/namemapper.ts","../src/oas/mapper/types.ts","../src/oas/nodes/arr.ts","../src/oas/nodes/body.ts","../src/oas/nodes/circularref.ts","../src/oas/nodes/comp.ts","../src/oas/nodes/delete.ts","../src/oas/nodes/en.ts","../src/oas/nodes/entity.ts","../src/oas/nodes/factory.ts","../src/oas/nodes/get.ts","../src/oas/nodes/itype.ts","../src/oas/nodes/interfacepromotion.ts","../src/oas/nodes/internal.ts","../src/oas/nodes/map.ts","../src/oas/nodes/obj.ts","../src/oas/nodes/op.ts","../src/oas/nodes/param.ts","../src/oas/nodes/patch.ts","../src/oas/nodes/post.ts","../src/oas/nodes/prop.ts","../src/oas/nodes/proparray.ts","../src/oas/nodes/propcircref.ts","../src/oas/nodes/propcomp.ts","../src/oas/nodes/propen.ts","../src/oas/nodes/propmap.ts","../src/oas/nodes/propobj.ts","../src/oas/nodes/propref.ts","../src/oas/nodes/propscalar.ts","../src/oas/nodes/put.ts","../src/oas/nodes/ref.ts","../src/oas/nodes/refcircref.ts","../src/oas/nodes/referenceobject.ts","../src/oas/nodes/res.ts","../src/oas/nodes/scalar.ts","../src/oas/nodes/type.ts","../src/oas/nodes/typeutils.ts","../src/oas/nodes/union.ts","../src/oas/prompts/prompt.ts","../src/oas/prompts/theme.ts","../src/oas/prompts/base/theme.ts","../src/oas/prompts/base/utils.ts","../src/oas/schemas/index.ts","../src/oas/utils/gql.ts","../src/oas/utils/naming.ts","../src/oas/visitor/generatorvisitor.ts","../src/oas/visitor/nodevisitor.ts","../src/oas/visitor/example.ts","../src/oas/visitor/index.ts","../src/tests/runners.ts"],"version":"5.8.2"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apollo-conn-gen",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Library to generate Apollo connector schemas from OpenAPI specs or JSON payloads. Also includes a CLI to generate connectors from the command line.",
5
5
  "author": "Fernando @ Apollo",
6
6
  "license": "MIT",