@gusnips/sdkgen 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/methods.d.ts +119 -0
- package/dist/methods.d.ts.map +1 -0
- package/dist/methods.js +248 -0
- package/dist/methods.js.map +1 -0
- package/dist/transport.d.ts +88 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +198 -0
- package/dist/transport.js.map +1 -0
- package/package.json +4 -1
- package/src/index.ts +11 -0
- package/src/methods.test.ts +538 -0
- package/src/methods.ts +393 -0
- package/src/readme.test.ts +27 -1
- package/src/transport.test.ts +426 -0
- package/src/transport.ts +320 -0
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @gusnips/sdkgen
|
|
2
2
|
|
|
3
|
-
The parts of a script that writes a TypeScript SDK for your API. You keep the script
|
|
4
|
-
|
|
3
|
+
The parts of a script that writes a TypeScript SDK for your API. You keep the script: it picks the
|
|
4
|
+
names, the doc text and anything written by hand. This package writes the rest, including the
|
|
5
|
+
parts that are easy to get wrong.
|
|
5
6
|
|
|
6
7
|
```bash
|
|
7
8
|
bun add -d @gusnips/sdkgen prettier
|
|
@@ -39,6 +40,9 @@ fieldsOf(inputJsonSchema(z.object({ to: z.string().describe("Who gets it.") })))
|
|
|
39
40
|
| `camelCase`, `pascalCase` | `send_message` gives `sendMessage` and `SendMessage`. |
|
|
40
41
|
| `inputJsonSchema` | Turns a zod schema into JSON Schema. JSON Schema passes through as it is. |
|
|
41
42
|
| `writeGenerated` | Formats and writes the files, or lists the ones that are out of date. |
|
|
43
|
+
| `sdkMethods` | Writes one SDK method per operation, and the params type each one takes. |
|
|
44
|
+
| `transportSource` | Returns the code every method runs on: the request, the retries, the error. |
|
|
45
|
+
| `retrySource` | Returns the rule for when a failed call is worth another try. |
|
|
42
46
|
|
|
43
47
|
## Copy your API's types
|
|
44
48
|
|
|
@@ -76,6 +80,141 @@ output drops the comments. It finds `export interface`, `export type`, `export c
|
|
|
76
80
|
If a real type has a name that short, the SDK will not compile, so you find out.
|
|
77
81
|
- A quote inside a regex literal, like `/"/`, is read as the start of a string.
|
|
78
82
|
|
|
83
|
+
## Write the methods
|
|
84
|
+
|
|
85
|
+
Give each operation an `sdk` field that says what its method is called and what it returns. An
|
|
86
|
+
operation without one gets no method.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { sdkMethods } from "@gusnips/sdkgen";
|
|
90
|
+
|
|
91
|
+
const { members } = sdkMethods([
|
|
92
|
+
{
|
|
93
|
+
name: "health",
|
|
94
|
+
method: "get",
|
|
95
|
+
path: "/health",
|
|
96
|
+
summary: "Check the API is up.",
|
|
97
|
+
sdk: { method: "health", returns: "HealthDto" },
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`members` is the method as text, ready to go inside a class:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
/**
|
|
106
|
+
* Check the API is up.
|
|
107
|
+
*/
|
|
108
|
+
health(opts?: Omit<RequestOptions, "idempotencyKey">): Promise<HealthDto> {
|
|
109
|
+
return this.request({ method: "GET", path: "/health" }, undefined, opts);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The operations are the same list you hand `buildOpenApi` from `@gusnips/server`, so you write them
|
|
114
|
+
once. `sdk.method` is a name like `sendMessage`, or `numbers.pair` to put the method in a `numbers`
|
|
115
|
+
group. `sdk.returns` is the type `data` holds: `MessageDto`, `NumberDto[]`, or `void` for a 204.
|
|
116
|
+
From `@gusnips/server` 0.8.24, `buildOpenApi` checks the `sdk` field and writes it into the
|
|
117
|
+
reference as `x-sdk`.
|
|
118
|
+
|
|
119
|
+
Each method calls `this.request(spec, params, opts)`. Your class declares it and sends the call
|
|
120
|
+
through the transport, below. `sdkMethods` also returns:
|
|
121
|
+
|
|
122
|
+
- `params`: an `export interface …Params` for each method that takes arguments, written from the
|
|
123
|
+
operation's `input` schema. A path slot is filled from the field of its name, or the one
|
|
124
|
+
`params` maps to it.
|
|
125
|
+
- `paramTypes` and `returnTypes`: the names your file has to import.
|
|
126
|
+
- `routes`: where each generated method sends its call, keyed by the method. Here that is
|
|
127
|
+
`{ health: { method: "GET", path: "/health", pathParams: [] } }`. A slot in `path` carries the
|
|
128
|
+
argument's name, and `pathParams` lists those arguments in path order. Use it for a docs page
|
|
129
|
+
that shows a REST call beside the SDK call that makes it.
|
|
130
|
+
|
|
131
|
+
Four options:
|
|
132
|
+
|
|
133
|
+
- `namespaces`: the groups, in the order the client lists them. A group missing from the list is an
|
|
134
|
+
error, so a typo cannot start a new one.
|
|
135
|
+
- `doc(op)`: the method's doc comment, one string per paragraph. Default: the summary, then the
|
|
136
|
+
description.
|
|
137
|
+
- `specExtra(op)`: more fields on the spec, for your own `request` to read.
|
|
138
|
+
- `inject`: methods you write by hand, such as one that polls a job. They go beside the generated
|
|
139
|
+
ones and must not take one of their names.
|
|
140
|
+
|
|
141
|
+
It stops with an error that names the operation when a method name has more than one dot, two
|
|
142
|
+
methods want one name, a status with no body (204, 205, 304) returns something, or a path slot is
|
|
143
|
+
filled by a field the caller may leave out.
|
|
144
|
+
|
|
145
|
+
## Send the calls
|
|
146
|
+
|
|
147
|
+
Two more files go into the SDK. `transportSource()` is the code that sends each call: it fills the
|
|
148
|
+
path, tries again when that is safe, and turns a failure into your SDK's own error.
|
|
149
|
+
`retrySource()` is the rule it asks, from `@gusnips/http`. Both are plain TypeScript that import
|
|
150
|
+
nothing else, so the SDK installs nothing.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { retrySource, transportSource } from "@gusnips/sdkgen";
|
|
154
|
+
|
|
155
|
+
const files = {
|
|
156
|
+
"packages/sdk/src/generated/retry.ts": retrySource(),
|
|
157
|
+
"packages/sdk/src/generated/transport.ts": transportSource(),
|
|
158
|
+
};
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Your client hands its settings to `send`, which returns `{ data, meta }`:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { GeneratedOperations } from "./generated/operations.ts";
|
|
165
|
+
import {
|
|
166
|
+
send,
|
|
167
|
+
type RequestOptions,
|
|
168
|
+
type RequestSpec,
|
|
169
|
+
type Transport,
|
|
170
|
+
} from "./generated/transport.ts";
|
|
171
|
+
|
|
172
|
+
export class Example extends GeneratedOperations {
|
|
173
|
+
private readonly transport: Transport;
|
|
174
|
+
|
|
175
|
+
constructor(apiKey: string) {
|
|
176
|
+
super();
|
|
177
|
+
this.transport = {
|
|
178
|
+
baseUrl: "https://api.example.com/v1",
|
|
179
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
180
|
+
error: (failure) => new ExampleError(failure),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
protected async request<T>(spec: RequestSpec, params?: object, opts?: RequestOptions) {
|
|
185
|
+
return (await send<T>(this.transport, spec, params, opts)).data;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
| Setting | Default | What it does |
|
|
191
|
+
| ------------------------- | --------- | --------------------------------------------------------------------------- |
|
|
192
|
+
| `baseUrl` | required | Where the API lives, with its base path. |
|
|
193
|
+
| `error(failure)` | required | Builds your SDK's error. The transport throws what it returns. |
|
|
194
|
+
| `headers` | none | Sent on every call. |
|
|
195
|
+
| `fetch` | global | The fetch to call, such as a fake one in tests. |
|
|
196
|
+
| `timeoutMs(spec, params)` | 30,000 ms | How long one try waits for an answer. A call's `opts.timeoutMs` wins. |
|
|
197
|
+
| `maxRetries` | 2 | Extra tries after a failure worth repeating. |
|
|
198
|
+
| `durableCodes` | none | Error codes that waiting does not fix, such as a spent monthly quota. |
|
|
199
|
+
| `mintKeys` | false | Makes up an idempotency key for a call that takes one, so it can try again. |
|
|
200
|
+
|
|
201
|
+
`failure` has the status (0 when no answer came back), the API's `error`, the `Retry-After` wait,
|
|
202
|
+
the request id, and the idempotency key the call went out with. A call that may have run can be
|
|
203
|
+
sent again with that key, and the API answers from the first run.
|
|
204
|
+
|
|
205
|
+
A failed call is tried again:
|
|
206
|
+
|
|
207
|
+
- **After a 408, 425 or 429**, whatever it is. Those say the API did not run it.
|
|
208
|
+
- **After no answer, a 5xx, or a 409 that says when to come back**, only if running it twice is
|
|
209
|
+
safe. A GET is. A write is when its operation reads an `Idempotency-Key` and the call has one, or
|
|
210
|
+
when its `sdk.repeatable` is true. A write without a key is not sent twice, because it may
|
|
211
|
+
already have run.
|
|
212
|
+
- **Never** when the answer says waiting will not help: a code in `durableCodes`,
|
|
213
|
+
`details.retryAfterSecs: null`, or a wait longer than 10 seconds.
|
|
214
|
+
|
|
215
|
+
It waits what the `Retry-After` header says, in seconds or as a date, then what
|
|
216
|
+
`details.retryAfterSecs` says. With neither, it waits about 1 second, then 2.
|
|
217
|
+
|
|
79
218
|
## Keep the SDK current
|
|
80
219
|
|
|
81
220
|
```ts
|
|
@@ -108,6 +247,11 @@ error and not "out of date".
|
|
|
108
247
|
- **A `*/` in a description is broken up**, so it cannot end the doc comment early.
|
|
109
248
|
- **A zod schema needs zod 4.4 or later.** Older versions cannot describe themselves as JSON
|
|
110
249
|
Schema, and the error says so.
|
|
250
|
+
- **A call with a missing path value throws a TypeError** before anything is sent, rather than
|
|
251
|
+
calling `/numbers//pair`.
|
|
252
|
+
- **An idempotency key on a call that takes none throws.** The API would ignore it, so it could not
|
|
253
|
+
stop the call running twice. The method's type refuses it first: only a call that takes a key
|
|
254
|
+
accepts `idempotencyKey` in its options.
|
|
111
255
|
|
|
112
256
|
## Why it exists
|
|
113
257
|
|
|
@@ -121,7 +265,7 @@ had fixed something the others had not, and every one still had these bugs:
|
|
|
121
265
|
- `export interface Empty {}` on one line swallowed the declaration after it.
|
|
122
266
|
- A comment written `/*/` ended on the character that opened it.
|
|
123
267
|
|
|
124
|
-
This package is the merge, with each fix pinned by a test.
|
|
125
|
-
on it and wrote every file byte for byte as before,
|
|
268
|
+
This package is the merge, with each fix pinned by a test. All five generators were then rewritten
|
|
269
|
+
on it and wrote every file byte for byte as before, each 267 to 281 lines shorter.
|
|
126
270
|
|
|
127
271
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { liftContract, type LiftOptions } from "./contract.ts";
|
|
2
|
+
export { retrySource, sdkMethods, transportSource, type InjectedMember, type SdkMethods, type SdkMethodsOptions, type SdkOperation, type SdkPlacement, type SdkRoute, } from "./methods.ts";
|
|
2
3
|
export { camelCase, docComment, fieldsOf, inputJsonSchema, paramsInterface, pascalCase, typeNames, typeOf, wrapLines, type JsonObject, type SchemaSource, type StandardJsonSchema, } from "./types.ts";
|
|
3
4
|
export { writeGenerated, type GeneratedFiles, type WriteOptions, type WriteResult, } from "./write.ts";
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EACL,SAAS,EACT,UAAU,EACV,QAAQ,EACR,eAAe,EACf,eAAe,EACf,UAAU,EACV,SAAS,EACT,MAAM,EACN,SAAS,EACT,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EACL,WAAW,EACX,UAAU,EACV,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,QAAQ,GACd,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,SAAS,EACT,UAAU,EACV,QAAQ,EACR,eAAe,EACf,eAAe,EACf,UAAU,EACV,SAAS,EACT,MAAM,EACN,SAAS,EACT,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { liftContract } from "./contract.js";
|
|
2
|
+
export { retrySource, sdkMethods, transportSource, } from "./methods.js";
|
|
2
3
|
export { camelCase, docComment, fieldsOf, inputJsonSchema, paramsInterface, pascalCase, typeNames, typeOf, wrapLines, } from "./types.js";
|
|
3
4
|
export { writeGenerated, } from "./write.js";
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAoB,MAAM,eAAe,CAAC;AAC/D,OAAO,EACL,SAAS,EACT,UAAU,EACV,QAAQ,EACR,eAAe,EACf,eAAe,EACf,UAAU,EACV,SAAS,EACT,MAAM,EACN,SAAS,GAIV,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,GAIf,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAoB,MAAM,eAAe,CAAC;AAC/D,OAAO,EACL,WAAW,EACX,UAAU,EACV,eAAe,GAOhB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,SAAS,EACT,UAAU,EACV,QAAQ,EACR,eAAe,EACf,eAAe,EACf,UAAU,EACV,SAAS,EACT,MAAM,EACN,SAAS,GAIV,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,GAIf,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { type SchemaSource } from "./types.ts";
|
|
2
|
+
/** Where the generated SDK puts an operation. */
|
|
3
|
+
export interface SdkPlacement {
|
|
4
|
+
/** `sendMessage`, or `numbers.pair` in a namespace. One dot at most; unique. */
|
|
5
|
+
method: string;
|
|
6
|
+
/** What `data` holds, as the SDK names it: `MessageDto`, `NumberDto[]`; `void` for a 204. */
|
|
7
|
+
returns: string;
|
|
8
|
+
/**
|
|
9
|
+
* Safe to run twice with no key: a read sent as a POST, or a write the server dedupes on its
|
|
10
|
+
* own. Default: a GET only.
|
|
11
|
+
*/
|
|
12
|
+
repeatable?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The part of an operation the generator reads. It is a structural subset of `@gusnips/server`'s
|
|
16
|
+
* `OpenApiOperation`, so the list an API hands `buildOpenApi` fits here unchanged.
|
|
17
|
+
*/
|
|
18
|
+
export interface SdkOperation {
|
|
19
|
+
name: string;
|
|
20
|
+
method: "get" | "post" | "put" | "patch" | "delete";
|
|
21
|
+
/** The route as the router spells it: `/numbers/:id`. */
|
|
22
|
+
path: string;
|
|
23
|
+
summary: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
/** Everything the operation reads. It becomes the method's params interface. */
|
|
26
|
+
input?: SchemaSource;
|
|
27
|
+
/** Input field → path slot, where the two names differ: `{ numberId: "id" }`. */
|
|
28
|
+
params?: Readonly<Record<string, string>>;
|
|
29
|
+
/** Fields the route fills in itself. The SDK leaves them out. */
|
|
30
|
+
fixed?: readonly string[];
|
|
31
|
+
/** Request headers the operation reads. An `Idempotency-Key` here makes the call keyed. */
|
|
32
|
+
headers?: readonly {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
}[];
|
|
35
|
+
/** The success status. A 204, 205 or 304 has no body, so it returns `void`. */
|
|
36
|
+
status?: number;
|
|
37
|
+
extensions?: Readonly<Record<`x-${string}`, unknown>>;
|
|
38
|
+
/** Where the generated SDK puts this operation. Absent: no method. */
|
|
39
|
+
sdk?: SdkPlacement;
|
|
40
|
+
}
|
|
41
|
+
/** A member written by hand, placed beside the generated ones and held to the same names. */
|
|
42
|
+
export interface InjectedMember {
|
|
43
|
+
/** `jobs.wait`, or `wait` at the top level. */
|
|
44
|
+
method: string;
|
|
45
|
+
/** Its parameters and return type: `(jobId: string, opts?: WaitOptions): Promise<JobDto>`. */
|
|
46
|
+
signature: string;
|
|
47
|
+
/** What it returns: `this.waitForJob(jobId, opts)`. */
|
|
48
|
+
call: string;
|
|
49
|
+
/** Its doc comment, one string per paragraph. */
|
|
50
|
+
doc?: readonly string[];
|
|
51
|
+
}
|
|
52
|
+
export interface SdkMethodsOptions {
|
|
53
|
+
/**
|
|
54
|
+
* The namespaces, in the order the client lists them. Given, a namespace not in it throws, so a
|
|
55
|
+
* typo cannot start a new one. Default: the order they first appear.
|
|
56
|
+
*/
|
|
57
|
+
namespaces?: readonly string[];
|
|
58
|
+
/**
|
|
59
|
+
* A method's doc comment, one string per paragraph; `undefined` ones are skipped. Default: the
|
|
60
|
+
* summary, then the description.
|
|
61
|
+
*/
|
|
62
|
+
doc?: (op: SdkOperation) => readonly (string | undefined)[];
|
|
63
|
+
/** Fields to add to the spec an operation hands `request`, such as its proxy block's fields. */
|
|
64
|
+
specExtra?: (op: SdkOperation) => Readonly<Record<string, unknown>> | undefined;
|
|
65
|
+
/** Members written by hand, each after the generated ones in its place. */
|
|
66
|
+
inject?: readonly InjectedMember[];
|
|
67
|
+
}
|
|
68
|
+
export interface SdkMethods {
|
|
69
|
+
/** An `export interface …Params` for each method that takes arguments: the body of `params.ts`. */
|
|
70
|
+
params: string;
|
|
71
|
+
/**
|
|
72
|
+
* The class members: top-level methods first, then one object per namespace. Each calls
|
|
73
|
+
* `this.request(spec, params, opts)`, which the class declares and the SDK implements.
|
|
74
|
+
*/
|
|
75
|
+
members: string;
|
|
76
|
+
/** The params interfaces the members name, sorted, for their import line. */
|
|
77
|
+
paramTypes: string[];
|
|
78
|
+
/** The declared types the `returns` name, sorted, for their import from the contract. */
|
|
79
|
+
returnTypes: string[];
|
|
80
|
+
/**
|
|
81
|
+
* Each generated method's route as the SDK spells it, keyed by the method: `numbers.get` →
|
|
82
|
+
* `{ method: "GET", path: "/numbers/:numberId", pathParams: ["numberId"] }`. For a page that
|
|
83
|
+
* shows a REST call beside the SDK call that makes it. Hand-written members have none.
|
|
84
|
+
*/
|
|
85
|
+
routes: Record<string, SdkRoute>;
|
|
86
|
+
}
|
|
87
|
+
/** Where a generated method sends its call. */
|
|
88
|
+
export interface SdkRoute {
|
|
89
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
90
|
+
/** The path with each slot named for the argument that fills it: `/numbers/:numberId`. */
|
|
91
|
+
path: string;
|
|
92
|
+
/** The arguments that fill the path, in the order the path has them. */
|
|
93
|
+
pathParams: string[];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The SDK's methods for every operation that names an `sdk` place, with the params interfaces they
|
|
97
|
+
* take.
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* const { params, members, paramTypes, returnTypes } = sdkMethods(operations);
|
|
101
|
+
* const file = `export abstract class GeneratedOperations {
|
|
102
|
+
* protected abstract request<T>(spec: RequestSpec, params?: object, opts?: RequestOptions): Promise<T>;
|
|
103
|
+
* ${members}}\n`;
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export declare function sdkMethods(operations: readonly SdkOperation[], options?: SdkMethodsOptions): SdkMethods;
|
|
107
|
+
/**
|
|
108
|
+
* `@gusnips/http`'s retry rule, its `src/retry.ts` whole, for an SDK to carry as
|
|
109
|
+
* `generated/retry.ts`. It is read from this package's own dependency, never the adopter's, so
|
|
110
|
+
* every SDK built with one sdkgen carries one rule. When an sdkgen release moves the rule, each
|
|
111
|
+
* SDK's `--check` fails, and that is the signal to release the SDK.
|
|
112
|
+
*/
|
|
113
|
+
export declare function retrySource(): string;
|
|
114
|
+
/**
|
|
115
|
+
* The transport every generated method runs on, for an SDK to carry as `generated/transport.ts`
|
|
116
|
+
* beside `retry.ts`: this package's own `src/transport.ts`, importing the rule from `./retry.ts`.
|
|
117
|
+
*/
|
|
118
|
+
export declare function transportSource(): string;
|
|
119
|
+
//# sourceMappingURL=methods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"methods.d.ts","sourceRoot":"","sources":["../src/methods.ts"],"names":[],"mappings":"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"}
|
package/dist/methods.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The methods an SDK exposes, written from the operation list the API already mounts, and the two
|
|
3
|
+
* files they run on.
|
|
4
|
+
*
|
|
5
|
+
* Five generators each wrote this loop, and what they shared is here: one method per operation,
|
|
6
|
+
* grouped by namespace, taking the params interface its input schema gives and returning the type
|
|
7
|
+
* the operation says `data` holds; a path whose slots carry the argument's own name, so the
|
|
8
|
+
* transport can fill them; and the checks that stop a wrong SDK at generation rather than in the
|
|
9
|
+
* hands of whoever installs it. What differs by product comes in through options: the doc
|
|
10
|
+
* comment's paragraphs, what the spec carries beyond the route, and members written by hand.
|
|
11
|
+
*
|
|
12
|
+
* It reads the operation list in-process, never the published OpenAPI document. The document has
|
|
13
|
+
* lost the type names, the argument renames and the typed query params, and a type JSON cannot
|
|
14
|
+
* carry reaches it as `{}`, where the schema writer here would have stopped.
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { createRequire } from "node:module";
|
|
18
|
+
import { dirname, join } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { inputJsonSchema, paramsInterface, pascalCase, typeNames, wrapLines, } from "./types.js";
|
|
21
|
+
/** Success statuses with no body (RFC 9110), the same three the server's reference documents so. */
|
|
22
|
+
const NO_BODY = new Set([204, 205, 304]);
|
|
23
|
+
/** Class members a method must not be named: the seam every method calls, and the constructor. */
|
|
24
|
+
const RESERVED = new Set(["request", "constructor"]);
|
|
25
|
+
/** The spec fields the generator writes, which `specExtra` must not overwrite. */
|
|
26
|
+
const SPEC_FIELDS = new Set(["method", "path", "keyed", "repeatable"]);
|
|
27
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
28
|
+
const VERB = { get: "GET", post: "POST", put: "PUT", patch: "PATCH", delete: "DELETE" };
|
|
29
|
+
/**
|
|
30
|
+
* The SDK's methods for every operation that names an `sdk` place, with the params interfaces they
|
|
31
|
+
* take.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* const { params, members, paramTypes, returnTypes } = sdkMethods(operations);
|
|
35
|
+
* const file = `export abstract class GeneratedOperations {
|
|
36
|
+
* protected abstract request<T>(spec: RequestSpec, params?: object, opts?: RequestOptions): Promise<T>;
|
|
37
|
+
* ${members}}\n`;
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function sdkMethods(operations, options = {}) {
|
|
41
|
+
const { doc = (op) => [op.summary, op.description], specExtra, inject = [] } = options;
|
|
42
|
+
const members = [];
|
|
43
|
+
const params = [];
|
|
44
|
+
const paramTypes = [];
|
|
45
|
+
const returnTypes = new Set();
|
|
46
|
+
const routes = {};
|
|
47
|
+
for (const op of operations) {
|
|
48
|
+
const sdk = op.sdk;
|
|
49
|
+
if (sdk === undefined)
|
|
50
|
+
continue;
|
|
51
|
+
const where = `${op.method.toUpperCase()} ${op.path} (${op.name})`;
|
|
52
|
+
if (op.status !== undefined && NO_BODY.has(op.status) && sdk.returns !== "void") {
|
|
53
|
+
throw new Error(`${where} answers ${op.status}, which has no body, so \`sdk.returns\` must be "void".`);
|
|
54
|
+
}
|
|
55
|
+
const schema = op.input === undefined ? undefined : schemaOf(op.input, where);
|
|
56
|
+
const fixed = op.fixed ?? [];
|
|
57
|
+
const typeName = `${pascalCase(op.name)}Params`;
|
|
58
|
+
const iface = schema === undefined ? null : paramsInterface(typeName, schema, fixed);
|
|
59
|
+
if (iface !== null) {
|
|
60
|
+
params.push(`/** Arguments for \`${sdk.method}()\`. */\n${iface.source}`);
|
|
61
|
+
paramTypes.push(typeName);
|
|
62
|
+
}
|
|
63
|
+
for (const name of typeNames(sdk.returns))
|
|
64
|
+
returnTypes.add(name);
|
|
65
|
+
const listed = schema?.["required"];
|
|
66
|
+
const required = new Set(Array.isArray(listed)
|
|
67
|
+
? listed.filter((f) => typeof f === "string" && !fixed.includes(f))
|
|
68
|
+
: []);
|
|
69
|
+
const method = VERB[op.method];
|
|
70
|
+
const path = sdkPath(op, required, where);
|
|
71
|
+
routes[sdk.method] = {
|
|
72
|
+
method,
|
|
73
|
+
path,
|
|
74
|
+
pathParams: [...path.matchAll(/:(\w+)/g)].flatMap((match) => match[1] ?? []),
|
|
75
|
+
};
|
|
76
|
+
const fields = [`method: "${method}"`, `path: ${JSON.stringify(path)}`];
|
|
77
|
+
const keyed = op.headers?.some((h) => h.name.toLowerCase() === "idempotency-key") === true;
|
|
78
|
+
if (keyed)
|
|
79
|
+
fields.push("keyed: true");
|
|
80
|
+
if (sdk.repeatable !== undefined)
|
|
81
|
+
fields.push(`repeatable: ${sdk.repeatable}`);
|
|
82
|
+
for (const [key, value] of Object.entries(specExtra?.(op) ?? {})) {
|
|
83
|
+
if (SPEC_FIELDS.has(key)) {
|
|
84
|
+
throw new Error(`${where}: \`specExtra\` sets "${key}", which the generator writes itself.`);
|
|
85
|
+
}
|
|
86
|
+
if (value !== undefined)
|
|
87
|
+
fields.push(`${propertyKey(key)}: ${JSON.stringify(value)}`);
|
|
88
|
+
}
|
|
89
|
+
const argument = iface === null ? "" : `params${iface.optional ? "?" : ""}: ${typeName}, `;
|
|
90
|
+
// A key on a call that takes none is refused by the transport at runtime; the type refuses it
|
|
91
|
+
// first, where the caller's editor shows it.
|
|
92
|
+
const options = keyed ? "RequestOptions" : `Omit<RequestOptions, "idempotencyKey">`;
|
|
93
|
+
members.push({
|
|
94
|
+
where,
|
|
95
|
+
...place(sdk.method, where),
|
|
96
|
+
doc: doc(op),
|
|
97
|
+
signature: `(${argument}opts?: ${options}): Promise<${sdk.returns}>`,
|
|
98
|
+
call: `this.request({ ${fields.join(", ")} }, ${iface === null ? "undefined" : "params"}, opts)`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
for (const member of inject) {
|
|
102
|
+
const where = `The hand-written ${member.method}`;
|
|
103
|
+
members.push({
|
|
104
|
+
where,
|
|
105
|
+
...place(member.method, where),
|
|
106
|
+
doc: member.doc ?? [],
|
|
107
|
+
signature: member.signature,
|
|
108
|
+
call: member.call,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
params: params.join("\n"),
|
|
113
|
+
members: writeMembers(members, options.namespaces),
|
|
114
|
+
paramTypes: paramTypes.sort(),
|
|
115
|
+
returnTypes: [...returnTypes].sort(),
|
|
116
|
+
routes,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** `numbers.pair` → its namespace and name, each checked to be a name a class member can have. */
|
|
120
|
+
function place(method, where) {
|
|
121
|
+
const parts = method.split(".");
|
|
122
|
+
if (parts.length > 2 || !parts.every((part) => IDENTIFIER.test(part))) {
|
|
123
|
+
throw new Error(`${where}: \`${method}\` is not a method name. Write \`name\`, or \`namespace.name\` with one dot.`);
|
|
124
|
+
}
|
|
125
|
+
const [first = "", second] = parts;
|
|
126
|
+
return second === undefined
|
|
127
|
+
? { namespace: undefined, name: first }
|
|
128
|
+
: { namespace: first, name: second };
|
|
129
|
+
}
|
|
130
|
+
/** Top-level methods first, then one `readonly namespace = { … }` per namespace. */
|
|
131
|
+
function writeMembers(members, order) {
|
|
132
|
+
const seen = new Map();
|
|
133
|
+
const topLevel = new Set();
|
|
134
|
+
const namespaces = [];
|
|
135
|
+
for (const { where, namespace, name } of members) {
|
|
136
|
+
const full = namespace === undefined ? name : `${namespace}.${name}`;
|
|
137
|
+
const first = seen.get(full);
|
|
138
|
+
if (first !== undefined) {
|
|
139
|
+
throw new Error(`Two methods want to be \`${full}\`: ${first}, and ${where}. Rename one.`);
|
|
140
|
+
}
|
|
141
|
+
seen.set(full, where);
|
|
142
|
+
if (namespace === undefined)
|
|
143
|
+
topLevel.add(name);
|
|
144
|
+
else if (!namespaces.includes(namespace))
|
|
145
|
+
namespaces.push(namespace);
|
|
146
|
+
}
|
|
147
|
+
for (const name of [...topLevel, ...namespaces]) {
|
|
148
|
+
if (RESERVED.has(name)) {
|
|
149
|
+
throw new Error(`\`${name}\` is a class member every SDK already has. Pick another name.`);
|
|
150
|
+
}
|
|
151
|
+
if (topLevel.has(name) && namespaces.includes(name)) {
|
|
152
|
+
throw new Error(`\`${name}\` is both a method and a namespace. Rename one.`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (order !== undefined) {
|
|
156
|
+
const unknown = namespaces.find((namespace) => !order.includes(namespace));
|
|
157
|
+
if (unknown !== undefined) {
|
|
158
|
+
throw new Error(`The namespace \`${unknown}\` is not in \`namespaces\`. Add it there, or use one of: ${order.join(", ")}.`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
let out = "";
|
|
162
|
+
for (const member of members.filter((m) => m.namespace === undefined)) {
|
|
163
|
+
out += `\n${jsdoc(member.doc, " ")} ${member.name}${member.signature} {\n`;
|
|
164
|
+
out += ` return ${member.call};\n }\n`;
|
|
165
|
+
}
|
|
166
|
+
for (const namespace of (order ?? namespaces).filter((ns) => namespaces.includes(ns))) {
|
|
167
|
+
out += `\n readonly ${namespace} = {\n`;
|
|
168
|
+
for (const member of members.filter((m) => m.namespace === namespace)) {
|
|
169
|
+
out += `${jsdoc(member.doc, " ")} ${member.name}: ${member.signature} =>\n`;
|
|
170
|
+
out += ` ${member.call},\n`;
|
|
171
|
+
}
|
|
172
|
+
out += ` };\n`;
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The route with each slot named for the argument that fills it: `/numbers/:id` with
|
|
178
|
+
* `{ numberId: "id" }` → `/numbers/:numberId`. A slot's argument must be required, or the SDK
|
|
179
|
+
* would let a caller leave out part of the address.
|
|
180
|
+
*/
|
|
181
|
+
function sdkPath(op, required, where) {
|
|
182
|
+
// A constraint such as `:id{[0-9]+}` is the router's business, and the SDK drops it.
|
|
183
|
+
const slot = /:(\w+)(\{[^}]*\})?(\?)?/g;
|
|
184
|
+
const slots = new Set([...op.path.matchAll(slot)].map((match) => match[1]));
|
|
185
|
+
const fieldIn = new Map();
|
|
186
|
+
for (const [field, name] of Object.entries(op.params ?? {})) {
|
|
187
|
+
if (!slots.has(name)) {
|
|
188
|
+
throw new Error(`${where}: \`params\` sends ${field} in ":${name}", which the path does not have.`);
|
|
189
|
+
}
|
|
190
|
+
fieldIn.set(name, field);
|
|
191
|
+
}
|
|
192
|
+
return op.path.replace(slot, (_match, name, _re, optional) => {
|
|
193
|
+
if (optional) {
|
|
194
|
+
throw new Error(`${where} has an optional slot ":${name}?". List it as two operations, with and without it.`);
|
|
195
|
+
}
|
|
196
|
+
const field = fieldIn.get(name) ?? name;
|
|
197
|
+
if (!/^\w+$/.test(field)) {
|
|
198
|
+
throw new Error(`${where}: the path's ":${name}" is filled from \`${field}\`, which a path cannot name. Rename the field.`);
|
|
199
|
+
}
|
|
200
|
+
if (!required.has(field)) {
|
|
201
|
+
throw new Error(`${where}: the path's ":${name}" is filled from \`${field}\`, which the input does not require. ` +
|
|
202
|
+
"Make it required, or map the slot to another field with `params`.");
|
|
203
|
+
}
|
|
204
|
+
return `:${field}`;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** The input as JSON Schema, with the operation named when it cannot be written. */
|
|
208
|
+
function schemaOf(input, where) {
|
|
209
|
+
try {
|
|
210
|
+
return inputJsonSchema(input);
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
throw new Error(`${where}: ${err instanceof Error ? err.message : String(err)}`, {
|
|
214
|
+
cause: err,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** `/**` over paragraphs, a blank ` *` line between them, each wrapped for its indent. */
|
|
219
|
+
function jsdoc(paragraphs, indent) {
|
|
220
|
+
const blocks = paragraphs
|
|
221
|
+
.map((paragraph) => (paragraph === undefined ? [] : wrapLines(paragraph, indent)))
|
|
222
|
+
.filter((lines) => lines.length > 0);
|
|
223
|
+
if (blocks.length === 0)
|
|
224
|
+
return "";
|
|
225
|
+
const line = `\n${indent} * `;
|
|
226
|
+
return `${indent}/**${line}${blocks.map((lines) => lines.join(line)).join(`\n${indent} *${line}`)}\n${indent} */\n`;
|
|
227
|
+
}
|
|
228
|
+
/** A key that is not an identifier has to be quoted. */
|
|
229
|
+
const propertyKey = (key) => (IDENTIFIER.test(key) ? key : JSON.stringify(key));
|
|
230
|
+
/**
|
|
231
|
+
* `@gusnips/http`'s retry rule, its `src/retry.ts` whole, for an SDK to carry as
|
|
232
|
+
* `generated/retry.ts`. It is read from this package's own dependency, never the adopter's, so
|
|
233
|
+
* every SDK built with one sdkgen carries one rule. When an sdkgen release moves the rule, each
|
|
234
|
+
* SDK's `--check` fails, and that is the signal to release the SDK.
|
|
235
|
+
*/
|
|
236
|
+
export function retrySource() {
|
|
237
|
+
const entry = createRequire(import.meta.url).resolve("@gusnips/http/retry");
|
|
238
|
+
return readFileSync(join(dirname(entry), "../src/retry.ts"), "utf8");
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The transport every generated method runs on, for an SDK to carry as `generated/transport.ts`
|
|
242
|
+
* beside `retry.ts`: this package's own `src/transport.ts`, importing the rule from `./retry.ts`.
|
|
243
|
+
*/
|
|
244
|
+
export function transportSource() {
|
|
245
|
+
const path = fileURLToPath(new URL("../src/transport.ts", import.meta.url));
|
|
246
|
+
return readFileSync(path, "utf8").replace(`from "@gusnips/http/retry";`, `from "./retry.ts";`);
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=methods.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"methods.js","sourceRoot":"","sources":["../src/methods.ts"],"names":[],"mappings":"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"}
|