@faable/deploy-sdk 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FaableDeployApi.d.ts +132 -122
- package/dist/FaableDeployApi.d.ts.map +1 -1
- package/dist/FaableDeployApi.js +72 -45
- package/dist/api/api-types.d.ts +46 -68
- package/dist/api/api-types.d.ts.map +1 -1
- package/dist/api/api-types.js +4 -0
- package/dist/api/custom-types.d.ts +66 -0
- package/dist/api/custom-types.d.ts.map +1 -0
- package/dist/api/custom-types.js +5 -0
- package/dist/api/generated-client.d.ts +895 -0
- package/dist/api/generated-client.d.ts.map +1 -0
- package/dist/api/generated-client.js +315 -0
- package/dist/api/types.d.ts +2 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/helpers.d.ts +2 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +11 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +9 -0
- package/package.json +6 -4
- package/scripts/fetch-spec.mjs +24 -0
- package/scripts/gen-client.mjs +241 -0
- package/spec/openapi.json +6603 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Generates `src/api/generated-client.ts` + `src/api/api-types.ts` from the
|
|
2
|
+
// OpenAPI spec. Ported from auth-sdk's generator (kept in sync by hand).
|
|
3
|
+
//
|
|
4
|
+
// Why: the SDK's typed methods (appGet, deploymentList, …) used to be
|
|
5
|
+
// hand-written and always lagged the API. The TYPES are already generated by
|
|
6
|
+
// openapi-typescript; this does the same for the METHODS, so the SDK covers the
|
|
7
|
+
// whole deploy API for free and stays in sync on every build.
|
|
8
|
+
//
|
|
9
|
+
// Scope: every secured operation (all of them carry the same
|
|
10
|
+
// apikey/faable_cli/faable_machine/faable_user scheme), MINUS internal
|
|
11
|
+
// resources in EXCLUDE_RESOURCES and the unsecured github/webhook/oidc
|
|
12
|
+
// endpoints. Reads the spec from a LOCAL file (downloaded by fetch-spec.mjs) so
|
|
13
|
+
// types.ts and this client are generated from the exact same snapshot.
|
|
14
|
+
//
|
|
15
|
+
// Run via `npm run gentypes`. Output is committed and reviewed in diff like
|
|
16
|
+
// `types.ts`. Do not edit the output by hand.
|
|
17
|
+
|
|
18
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { dirname, resolve } from "node:path";
|
|
21
|
+
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const ROOT = resolve(__dirname, "..");
|
|
24
|
+
|
|
25
|
+
const SPEC = process.env.DEPLOY_OPENAPI || resolve(ROOT, "spec/openapi.json");
|
|
26
|
+
const OUT = resolve(ROOT, "src/api/generated-client.ts");
|
|
27
|
+
const OUT_TYPES = resolve(ROOT, "src/api/api-types.ts");
|
|
28
|
+
|
|
29
|
+
const spec = JSON.parse(readFileSync(SPEC, "utf8"));
|
|
30
|
+
|
|
31
|
+
// operationId (`resource/action`, possibly snake/kebab in the action) →
|
|
32
|
+
// camelCase. `app/list`→appList, `app/get_deploy_workflow`→appGetDeployWorkflow,
|
|
33
|
+
// `secrets/list_app`→secretsListApp.
|
|
34
|
+
const toMethodName = (operationId) =>
|
|
35
|
+
operationId
|
|
36
|
+
.split(/[/_-]/)
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.map((seg, i) =>
|
|
39
|
+
i === 0
|
|
40
|
+
? seg[0].toLowerCase() + seg.slice(1)
|
|
41
|
+
: seg[0].toUpperCase() + seg.slice(1),
|
|
42
|
+
)
|
|
43
|
+
.join("");
|
|
44
|
+
|
|
45
|
+
// `/app/{id}/traffic` → ["id"], in order of appearance.
|
|
46
|
+
const pathParamNames = (path) =>
|
|
47
|
+
[...path.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]);
|
|
48
|
+
|
|
49
|
+
// `/app/{id}` → "/app/${id}" (template-literal body).
|
|
50
|
+
const toUrlTemplate = (path) =>
|
|
51
|
+
path.replace(/\{([^}]+)\}/g, (_, name) => "${" + name + "}");
|
|
52
|
+
|
|
53
|
+
// Include every operation that requires auth (all deploy ops share one scheme)
|
|
54
|
+
// and isn't marked internal. Two exclusion layers:
|
|
55
|
+
// 1. no `security` → infra (github setup/webhook, oidc token exchange)
|
|
56
|
+
// 2. `x-internal: true` → explicitly marked private in the API
|
|
57
|
+
const isIncluded = (op) => {
|
|
58
|
+
if (!Array.isArray(op.security) || op.security.length === 0) return false;
|
|
59
|
+
return op["x-internal"] !== true;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const successSchema = (op) => {
|
|
63
|
+
const responses = op.responses || {};
|
|
64
|
+
const res = responses["200"] || responses["201"];
|
|
65
|
+
return res?.content?.["application/json"]?.schema;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// A paginated list response is `{ next, results }` (see buildPaginator in
|
|
69
|
+
// sdk-base). Detected structurally so it tracks the server, not the op name.
|
|
70
|
+
const isPaginated = (op) => {
|
|
71
|
+
const schema = successSchema(op);
|
|
72
|
+
const req = schema?.required;
|
|
73
|
+
return Array.isArray(req) && req.includes("next") && req.includes("results");
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const escape = (s) => (s || "").replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
|
|
77
|
+
|
|
78
|
+
// Collect + sort operations by operationId for a stable, reviewable diff.
|
|
79
|
+
const operations = [];
|
|
80
|
+
for (const [path, methods] of Object.entries(spec.paths || {})) {
|
|
81
|
+
for (const [httpMethod, op] of Object.entries(methods)) {
|
|
82
|
+
if (!op || typeof op !== "object" || !op.operationId) continue;
|
|
83
|
+
if (!["get", "post", "delete"].includes(httpMethod)) continue;
|
|
84
|
+
if (!isIncluded(op)) continue;
|
|
85
|
+
operations.push({ path, httpMethod, op });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
operations.sort((a, b) => a.op.operationId.localeCompare(b.op.operationId));
|
|
89
|
+
|
|
90
|
+
const emitMethod = ({ path, httpMethod, op }) => {
|
|
91
|
+
const id = op.operationId;
|
|
92
|
+
const name = toMethodName(id);
|
|
93
|
+
const pathParams = pathParamNames(path);
|
|
94
|
+
const queryParams = (op.parameters || []).filter((p) => p.in === "query");
|
|
95
|
+
const hasBody = !!op.requestBody;
|
|
96
|
+
const paginated = isPaginated(op);
|
|
97
|
+
const url = "`" + toUrlTemplate(path) + "`";
|
|
98
|
+
|
|
99
|
+
const hasQuery = queryParams.length > 0;
|
|
100
|
+
const args = pathParams.map((p) => `${p}: string`);
|
|
101
|
+
if (hasBody) args.push(`data: OpBody<"${id}">`);
|
|
102
|
+
|
|
103
|
+
// fetcher.post takes a strict FetcherConfig (string-only params), so a POST
|
|
104
|
+
// carrying query params needs extra handling. None exist today; fail loud if
|
|
105
|
+
// that changes rather than silently dropping the query.
|
|
106
|
+
if (httpMethod === "post" && hasQuery) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`gen-client: POST with query params not supported yet (${id}). ` +
|
|
109
|
+
"Extend emitMethod to thread query params through fetcher.post's config.",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let body;
|
|
114
|
+
if (paginated) {
|
|
115
|
+
args.push(`params?: Omit<OpQuery<"${id}">, "cursor" | "next">`);
|
|
116
|
+
// The paginator's request params are untyped (`any`), so array/number/enum
|
|
117
|
+
// query values pass straight through.
|
|
118
|
+
body = `return this.paginator<OpItem<"${id}">>({ url: ${url}, params });`;
|
|
119
|
+
} else if (httpMethod === "post") {
|
|
120
|
+
// fetcher.post rejects a falsy body ("empty body"), so bodyless POSTs send
|
|
121
|
+
// an empty object.
|
|
122
|
+
const data = hasBody ? "data" : "{}";
|
|
123
|
+
body = `return this.fetcher.post<OpResult<"${id}">>(${url}, ${data});`;
|
|
124
|
+
} else if (hasQuery) {
|
|
125
|
+
// GET/DELETE with query params route through fetcher.request: its `params`
|
|
126
|
+
// is untyped, sidestepping FetcherConfig's string-only constraint, and the
|
|
127
|
+
// GET path still goes through the ETag cache.
|
|
128
|
+
args.push(`params?: OpQuery<"${id}">`);
|
|
129
|
+
const method = httpMethod.toUpperCase();
|
|
130
|
+
body = `return this.fetcher.request<OpResult<"${id}">>({ method: "${method}", url: ${url}, params });`;
|
|
131
|
+
} else if (httpMethod === "delete") {
|
|
132
|
+
body = `return this.fetcher.delete<OpResult<"${id}">>(${url});`;
|
|
133
|
+
} else {
|
|
134
|
+
body = `return this.fetcher.get<OpResult<"${id}">>(${url});`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const guards = pathParams.map((p) => ` requireId("${p}", ${p});`);
|
|
138
|
+
const doc = op.summary || op.description;
|
|
139
|
+
const jsdoc = [
|
|
140
|
+
" /**",
|
|
141
|
+
` * \`${httpMethod.toUpperCase()} ${path}\` — operationId: \`${id}\``,
|
|
142
|
+
...(doc ? [` *`, ` * ${escape(doc)}`] : []),
|
|
143
|
+
" */",
|
|
144
|
+
].join("\n");
|
|
145
|
+
|
|
146
|
+
return [
|
|
147
|
+
jsdoc,
|
|
148
|
+
` ${name}(${args.join(", ")}) {`,
|
|
149
|
+
...guards,
|
|
150
|
+
` ${body}`,
|
|
151
|
+
" }",
|
|
152
|
+
].join("\n");
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const header = `// AUTO-GENERATED by scripts/gen-client.mjs — do not edit by hand.
|
|
156
|
+
// Regenerated from the OpenAPI spec on every build (npm run gentypes).
|
|
157
|
+
// Source of truth: the deploy API's operationIds + security.
|
|
158
|
+
|
|
159
|
+
import type { operations } from "./types.js";
|
|
160
|
+
import { FaableApi } from "@faable/sdk-base";
|
|
161
|
+
import { requireId } from "../helpers.js";
|
|
162
|
+
|
|
163
|
+
// JSON body of an operation's request (typed from the generated \`operations\`).
|
|
164
|
+
type OpBody<K extends keyof operations> = operations[K] extends {
|
|
165
|
+
requestBody: { content: { "application/json": infer B } };
|
|
166
|
+
}
|
|
167
|
+
? B
|
|
168
|
+
: never;
|
|
169
|
+
|
|
170
|
+
// JSON body of an operation's 2xx response.
|
|
171
|
+
type Content2xx<R> = R extends {
|
|
172
|
+
200: { content: { "application/json": infer T } };
|
|
173
|
+
}
|
|
174
|
+
? T
|
|
175
|
+
: R extends { 201: { content: { "application/json": infer T } } }
|
|
176
|
+
? T
|
|
177
|
+
: void;
|
|
178
|
+
|
|
179
|
+
type OpResult<K extends keyof operations> = operations[K] extends {
|
|
180
|
+
responses: infer R;
|
|
181
|
+
}
|
|
182
|
+
? Content2xx<R>
|
|
183
|
+
: void;
|
|
184
|
+
|
|
185
|
+
// Item type of a paginated (\`{ next, results }\`) list response.
|
|
186
|
+
type OpItem<K extends keyof operations> =
|
|
187
|
+
OpResult<K> extends { results: (infer I)[] } ? I : never;
|
|
188
|
+
|
|
189
|
+
// Query parameters of an operation.
|
|
190
|
+
type OpQuery<K extends keyof operations> = operations[K] extends {
|
|
191
|
+
parameters: { query?: infer Q };
|
|
192
|
+
}
|
|
193
|
+
? NonNullable<Q>
|
|
194
|
+
: Record<string, never>;
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
const classBody = operations.map(emitMethod).join("\n\n");
|
|
198
|
+
|
|
199
|
+
const out = `${header}
|
|
200
|
+
/**
|
|
201
|
+
* Auto-generated deploy-API methods, one per secured operation in the OpenAPI
|
|
202
|
+
* spec. \`DeployApi\` extends this and adds the constructor, custom-logic
|
|
203
|
+
* helpers, and deprecated aliases.
|
|
204
|
+
*/
|
|
205
|
+
export abstract class GeneratedFaableDeployApi extends FaableApi {
|
|
206
|
+
${classBody}
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
209
|
+
|
|
210
|
+
writeFileSync(OUT, out);
|
|
211
|
+
console.warn(
|
|
212
|
+
`gen-client: wrote ${operations.length} methods → ${OUT.replace(ROOT + "/", "")}`,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Simple-named aliases for every component schema (App, Deployment, Secret, …),
|
|
216
|
+
// so consumers import `App` instead of `components["schemas"]["App"]`. One alias
|
|
217
|
+
// per schema, sorted for a stable diff. Hand-maintained extras (friendlier event
|
|
218
|
+
// names, the AppTraffic interface) live in custom-types.ts, which re-exports
|
|
219
|
+
// this file.
|
|
220
|
+
const schemaNames = Object.keys(spec.components?.schemas || {})
|
|
221
|
+
.filter((name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name))
|
|
222
|
+
.sort();
|
|
223
|
+
|
|
224
|
+
const typeAliases = schemaNames
|
|
225
|
+
.map((name) => `export type ${name} = components["schemas"]["${name}"];`)
|
|
226
|
+
.join("\n");
|
|
227
|
+
|
|
228
|
+
const typesOut = `// AUTO-GENERATED by scripts/gen-client.mjs — do not edit by hand.
|
|
229
|
+
// Regenerated from the OpenAPI spec on every build (npm run gentypes).
|
|
230
|
+
// One simple-named alias per schema in \`components["schemas"]\`.
|
|
231
|
+
// Hand-maintained extras live in custom-types.ts.
|
|
232
|
+
|
|
233
|
+
import type { components } from "./types.js";
|
|
234
|
+
|
|
235
|
+
${typeAliases}
|
|
236
|
+
`;
|
|
237
|
+
|
|
238
|
+
writeFileSync(OUT_TYPES, typesOut);
|
|
239
|
+
console.warn(
|
|
240
|
+
`gen-client: wrote ${schemaNames.length} type aliases → ${OUT_TYPES.replace(ROOT + "/", "")}`,
|
|
241
|
+
);
|