@distilled.cloud/core 0.30.3 → 1.0.0-rc.10
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/LICENSE +201 -0
- package/lib/api.d.ts +165 -0
- package/lib/api.d.ts.map +1 -0
- package/lib/api.js +190 -0
- package/lib/api.js.map +1 -0
- package/lib/category.d.ts +4 -4
- package/lib/category.js +4 -4
- package/lib/codegen/boolean-string-enums.d.ts +36 -0
- package/lib/codegen/boolean-string-enums.d.ts.map +1 -0
- package/lib/codegen/boolean-string-enums.js +94 -0
- package/lib/codegen/boolean-string-enums.js.map +1 -0
- package/lib/codegen/boolean-string-enums.test.d.ts +2 -0
- package/lib/codegen/boolean-string-enums.test.d.ts.map +1 -0
- package/lib/codegen/boolean-string-enums.test.js +147 -0
- package/lib/codegen/boolean-string-enums.test.js.map +1 -0
- package/lib/codegen/cli.d.ts +79 -0
- package/lib/codegen/cli.d.ts.map +1 -0
- package/lib/codegen/cli.js +149 -0
- package/lib/codegen/cli.js.map +1 -0
- package/lib/codegen/emit.d.ts +125 -0
- package/lib/codegen/emit.d.ts.map +1 -0
- package/lib/codegen/emit.js +101 -0
- package/lib/codegen/emit.js.map +1 -0
- package/lib/codegen/format.d.ts +23 -0
- package/lib/codegen/format.d.ts.map +1 -0
- package/lib/codegen/format.js +28 -0
- package/lib/codegen/format.js.map +1 -0
- package/lib/codegen/generator.d.ts +334 -0
- package/lib/codegen/generator.d.ts.map +1 -0
- package/lib/codegen/generator.js +813 -0
- package/lib/codegen/generator.js.map +1 -0
- package/lib/codegen/graph.d.ts +36 -0
- package/lib/codegen/graph.d.ts.map +1 -0
- package/lib/codegen/graph.js +136 -0
- package/lib/codegen/graph.js.map +1 -0
- package/lib/codegen/graphql-client.d.ts +62 -0
- package/lib/codegen/graphql-client.d.ts.map +1 -0
- package/lib/codegen/graphql-client.js +294 -0
- package/lib/codegen/graphql-client.js.map +1 -0
- package/lib/codegen/graphql-client.test.d.ts +2 -0
- package/lib/codegen/graphql-client.test.d.ts.map +1 -0
- package/lib/codegen/graphql-client.test.js +311 -0
- package/lib/codegen/graphql-client.test.js.map +1 -0
- package/lib/codegen/graphql.d.ts +207 -0
- package/lib/codegen/graphql.d.ts.map +1 -0
- package/lib/codegen/graphql.js +799 -0
- package/lib/codegen/graphql.js.map +1 -0
- package/lib/codegen/members.d.ts +25 -0
- package/lib/codegen/members.d.ts.map +1 -0
- package/lib/codegen/members.js +55 -0
- package/lib/codegen/members.js.map +1 -0
- package/lib/codegen/naming.d.ts +29 -0
- package/lib/codegen/naming.d.ts.map +1 -0
- package/lib/codegen/naming.js +74 -0
- package/lib/codegen/naming.js.map +1 -0
- package/lib/codegen/openapi-cli.d.ts +52 -0
- package/lib/codegen/openapi-cli.d.ts.map +1 -0
- package/lib/codegen/openapi-cli.js +109 -0
- package/lib/codegen/openapi-cli.js.map +1 -0
- package/lib/codegen/openapi.d.ts +178 -0
- package/lib/codegen/openapi.d.ts.map +1 -0
- package/lib/codegen/openapi.js +1377 -0
- package/lib/codegen/openapi.js.map +1 -0
- package/lib/codegen/operations.d.ts +24 -0
- package/lib/codegen/operations.d.ts.map +1 -0
- package/lib/codegen/operations.js +56 -0
- package/lib/codegen/operations.js.map +1 -0
- package/lib/codegen/pagination.d.ts +39 -0
- package/lib/codegen/pagination.d.ts.map +1 -0
- package/lib/codegen/pagination.js +33 -0
- package/lib/codegen/pagination.js.map +1 -0
- package/lib/codegen/patches.d.ts +65 -0
- package/lib/codegen/patches.d.ts.map +1 -0
- package/lib/codegen/patches.js +236 -0
- package/lib/codegen/patches.js.map +1 -0
- package/lib/codegen/patches.test.d.ts +2 -0
- package/lib/codegen/patches.test.d.ts.map +1 -0
- package/lib/codegen/patches.test.js +105 -0
- package/lib/codegen/patches.test.js.map +1 -0
- package/lib/codegen/prelude.d.ts +15 -0
- package/lib/codegen/prelude.d.ts.map +1 -0
- package/lib/codegen/prelude.js +60 -0
- package/lib/codegen/prelude.js.map +1 -0
- package/lib/codegen/proto.d.ts +121 -0
- package/lib/codegen/proto.d.ts.map +1 -0
- package/lib/codegen/proto.js +962 -0
- package/lib/codegen/proto.js.map +1 -0
- package/lib/codegen/rewrite-operation-ids.d.ts +131 -0
- package/lib/codegen/rewrite-operation-ids.d.ts.map +1 -0
- package/lib/codegen/rewrite-operation-ids.js +1079 -0
- package/lib/codegen/rewrite-operation-ids.js.map +1 -0
- package/lib/codegen/rewrite-operation-ids.test.d.ts +2 -0
- package/lib/codegen/rewrite-operation-ids.test.d.ts.map +1 -0
- package/lib/codegen/rewrite-operation-ids.test.js +533 -0
- package/lib/codegen/rewrite-operation-ids.test.js.map +1 -0
- package/lib/codegen/spec-path.d.ts +16 -0
- package/lib/codegen/spec-path.d.ts.map +1 -0
- package/lib/codegen/spec-path.js +101 -0
- package/lib/codegen/spec-path.js.map +1 -0
- package/lib/error-category.d.ts +28 -0
- package/lib/error-category.d.ts.map +1 -0
- package/lib/error-category.js +46 -0
- package/lib/error-category.js.map +1 -0
- package/lib/errors.d.ts +1 -0
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +18 -13
- package/lib/errors.js.map +1 -1
- package/lib/graphql.d.ts +284 -0
- package/lib/graphql.d.ts.map +1 -0
- package/lib/graphql.fixture.d.ts +249 -0
- package/lib/graphql.fixture.d.ts.map +1 -0
- package/lib/graphql.fixture.js +240 -0
- package/lib/graphql.fixture.js.map +1 -0
- package/lib/graphql.js +718 -0
- package/lib/graphql.js.map +1 -0
- package/lib/graphql.test.d.ts +2 -0
- package/lib/graphql.test.d.ts.map +1 -0
- package/lib/graphql.test.js +780 -0
- package/lib/graphql.test.js.map +1 -0
- package/lib/graphql.types.d.ts +2 -0
- package/lib/graphql.types.d.ts.map +1 -0
- package/lib/graphql.types.js +45 -0
- package/lib/graphql.types.js.map +1 -0
- package/lib/json-patch.d.ts +30 -30
- package/lib/json-patch.d.ts.map +1 -1
- package/lib/json-patch.js +73 -107
- package/lib/json-patch.js.map +1 -1
- package/lib/pagination.d.ts +77 -51
- package/lib/pagination.d.ts.map +1 -1
- package/lib/pagination.js +162 -94
- package/lib/pagination.js.map +1 -1
- package/lib/protocol-http.d.ts +74 -0
- package/lib/protocol-http.d.ts.map +1 -0
- package/lib/protocol-http.js +590 -0
- package/lib/protocol-http.js.map +1 -0
- package/lib/protocol-http.test.d.ts +2 -0
- package/lib/protocol-http.test.d.ts.map +1 -0
- package/lib/protocol-http.test.js +88 -0
- package/lib/protocol-http.test.js.map +1 -0
- package/lib/protocol-rest.d.ts +134 -0
- package/lib/protocol-rest.d.ts.map +1 -0
- package/lib/protocol-rest.js +256 -0
- package/lib/protocol-rest.js.map +1 -0
- package/lib/retry.d.ts +8 -2
- package/lib/retry.d.ts.map +1 -1
- package/lib/retry.js +22 -16
- package/lib/retry.js.map +1 -1
- package/lib/schema.d.ts +8 -9
- package/lib/schema.d.ts.map +1 -1
- package/lib/schema.js +8 -9
- package/lib/schema.js.map +1 -1
- package/lib/trait.d.ts +174 -0
- package/lib/trait.d.ts.map +1 -0
- package/lib/trait.js +123 -0
- package/lib/trait.js.map +1 -0
- package/package.json +24 -78
- package/src/api.ts +460 -0
- package/src/category.ts +4 -4
- package/src/codegen/boolean-string-enums.test.ts +168 -0
- package/src/codegen/boolean-string-enums.ts +106 -0
- package/src/codegen/cli.ts +285 -0
- package/src/codegen/emit.ts +203 -0
- package/src/codegen/format.ts +47 -0
- package/src/codegen/generator.ts +1283 -0
- package/src/codegen/graph.ts +151 -0
- package/src/codegen/graphql-client.test.ts +386 -0
- package/src/codegen/graphql-client.ts +419 -0
- package/src/codegen/graphql.ts +1217 -0
- package/src/codegen/members.ts +71 -0
- package/src/codegen/naming.ts +86 -0
- package/src/codegen/openapi-cli.ts +182 -0
- package/src/codegen/openapi.ts +1689 -0
- package/src/codegen/operations.ts +76 -0
- package/src/codegen/pagination.ts +71 -0
- package/src/codegen/patches.test.ts +130 -0
- package/src/codegen/patches.ts +291 -0
- package/src/codegen/prelude.ts +70 -0
- package/src/codegen/proto.ts +1128 -0
- package/src/codegen/rewrite-operation-ids.test.ts +563 -0
- package/src/codegen/rewrite-operation-ids.ts +1206 -0
- package/src/codegen/spec-path.ts +115 -0
- package/src/error-category.ts +84 -0
- package/src/errors.ts +22 -25
- package/src/graphql.fixture.ts +371 -0
- package/src/graphql.test.ts +974 -0
- package/src/graphql.ts +1321 -0
- package/src/graphql.types.ts +185 -0
- package/src/json-patch.ts +95 -122
- package/src/pagination.ts +217 -146
- package/src/protocol-http.test.ts +107 -0
- package/src/protocol-http.ts +735 -0
- package/src/protocol-rest.ts +391 -0
- package/src/retry.ts +21 -22
- package/src/schema.ts +9 -10
- package/src/trait.ts +274 -0
- package/README.md +0 -30
- package/lib/client.d.ts +0 -167
- package/lib/client.d.ts.map +0 -1
- package/lib/client.js +0 -659
- package/lib/client.js.map +0 -1
- package/lib/schemas.d.ts +0 -60
- package/lib/schemas.d.ts.map +0 -1
- package/lib/schemas.js +0 -79
- package/lib/schemas.js.map +0 -1
- package/lib/sensitive.d.ts +0 -71
- package/lib/sensitive.d.ts.map +0 -1
- package/lib/sensitive.js +0 -96
- package/lib/sensitive.js.map +0 -1
- package/lib/traits.d.ts +0 -421
- package/lib/traits.d.ts.map +0 -1
- package/lib/traits.js +0 -737
- package/lib/traits.js.map +0 -1
- package/src/client.ts +0 -1177
- package/src/schemas.ts +0 -128
- package/src/sensitive.ts +0 -119
- package/src/traits.ts +0 -996
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generic smithy→SDK service generator (dev-time only).
|
|
3
|
+
*
|
|
4
|
+
* One driver compiles a Smithy JSON model into an Effect SDK service module.
|
|
5
|
+
* Everything provider-specific arrives through {@link SdkSpec}: import
|
|
6
|
+
* header, trait vocabulary (which trait ids mean payload/file/nullable/…),
|
|
7
|
+
* the `T.*` pipe expressions to emit for each binding, how operations are
|
|
8
|
+
* declared (protocol/retry/error names), and naming policies. The driver
|
|
9
|
+
* owns the pipeline: operation discovery, reachability, topological order,
|
|
10
|
+
* schema/interface emission, error classes, pagination validation, and
|
|
11
|
+
* operation consts.
|
|
12
|
+
*
|
|
13
|
+
* A provider's `scripts/generate.ts` reduces to: load models (its own
|
|
14
|
+
* pipeline — docs-derived specs, patches, …), define its {@link SdkSpec},
|
|
15
|
+
* call {@link generateService} per model, write files.
|
|
16
|
+
*/
|
|
17
|
+
import { camel as _camel, local, lowerFirst, oneLine, q, tsKey, upperFirst, } from "./naming.js";
|
|
18
|
+
import { orderIndex, reachableFrom, shapeDeps, topoOrder, } from "./graph.js";
|
|
19
|
+
import { enumDecl, errorClass, errorUnionAlias, interfaceDecl, interfaceField, operationConst, PURE, suspendConst, } from "./emit.js";
|
|
20
|
+
import { JSON_PRELUDE, makeSchemaRef, makeTsRef, TS_JSON_PRELUDE, } from "./prelude.js";
|
|
21
|
+
import { collectOperations, collectOpErrorIds, ensureNamedIo, modelNamespace, } from "./operations.js";
|
|
22
|
+
import { memberBases, smithyWireName } from "./members.js";
|
|
23
|
+
import { validatePaginated } from "./pagination.js";
|
|
24
|
+
import { booleanStringEnums, STRING_ENCODED_TRAIT, } from "./boolean-string-enums.js";
|
|
25
|
+
const PAGINATED_TRAIT = "smithy.api#paginated";
|
|
26
|
+
/**
|
|
27
|
+
* Error categories, derived from the STANDARD Smithy error traits.
|
|
28
|
+
*
|
|
29
|
+
* `smithy.api#httpError` and `smithy.api#retryable` are how every Smithy
|
|
30
|
+
* model — AWS's included — says what kind of failure an error is. The
|
|
31
|
+
* categories the runtime acts on (`core/category.ts`, and `isTransientError`
|
|
32
|
+
* in `core/retry.ts`) are a reading of those two traits, so the reading
|
|
33
|
+
* belongs here, once, for every provider.
|
|
34
|
+
*
|
|
35
|
+
* A model that wants its errors classified states the status the API
|
|
36
|
+
* actually returns:
|
|
37
|
+
*
|
|
38
|
+
* "traits": { "smithy.api#error": "client", "smithy.api#httpError": 404 }
|
|
39
|
+
*
|
|
40
|
+
* and the error class is emitted with `.pipe(C.withNotFoundError)`. A model
|
|
41
|
+
* with no `httpError` gets no categories — the same as today.
|
|
42
|
+
*/
|
|
43
|
+
export const errorCategories = (traits,
|
|
44
|
+
/** Categories a provider knows that the traits don't say (AWS's spec file). */
|
|
45
|
+
extra = []) => {
|
|
46
|
+
const categories = [];
|
|
47
|
+
const add = (name) => {
|
|
48
|
+
if (!categories.includes(name))
|
|
49
|
+
categories.push(name);
|
|
50
|
+
};
|
|
51
|
+
const status = traits?.["smithy.api#httpError"];
|
|
52
|
+
if (typeof status === "number") {
|
|
53
|
+
if (status === 401 || status === 403)
|
|
54
|
+
add("AuthError");
|
|
55
|
+
else if (status === 402)
|
|
56
|
+
add("QuotaError");
|
|
57
|
+
else if (status === 400 ||
|
|
58
|
+
status === 404 ||
|
|
59
|
+
status === 405 ||
|
|
60
|
+
status === 406 ||
|
|
61
|
+
status === 410 ||
|
|
62
|
+
status === 413 ||
|
|
63
|
+
status === 415 ||
|
|
64
|
+
status === 422) {
|
|
65
|
+
add("BadRequestError");
|
|
66
|
+
}
|
|
67
|
+
else if (status === 408 || status === 504)
|
|
68
|
+
add("TimeoutError");
|
|
69
|
+
else if (status === 409)
|
|
70
|
+
add("ConflictError");
|
|
71
|
+
else if (status === 429)
|
|
72
|
+
add("ThrottlingError");
|
|
73
|
+
else if (status >= 500 && status < 600)
|
|
74
|
+
add("ServerError");
|
|
75
|
+
}
|
|
76
|
+
const retryable = traits?.["smithy.api#retryable"];
|
|
77
|
+
if (retryable !== undefined) {
|
|
78
|
+
add("RetryableError");
|
|
79
|
+
if (retryable?.throttling)
|
|
80
|
+
add("ThrottlingError");
|
|
81
|
+
}
|
|
82
|
+
for (const name of extra)
|
|
83
|
+
add(name);
|
|
84
|
+
return categories;
|
|
85
|
+
};
|
|
86
|
+
/** `["NotFoundError"]` → `C.withNotFoundError`. */
|
|
87
|
+
const categoryPipes = (traits) => errorCategories(traits).map((name) => `C.with${name}`);
|
|
88
|
+
/** Compile one Smithy model into a service module. */
|
|
89
|
+
export const generateService = (model, spec) => {
|
|
90
|
+
// `"true" | "false"` request members become real booleans that travel as
|
|
91
|
+
// their string spelling (see boolean-string-enums.ts).
|
|
92
|
+
booleanStringEnums(model);
|
|
93
|
+
const shapes = model.shapes;
|
|
94
|
+
const pure = spec.pure ?? PURE;
|
|
95
|
+
const prelude = spec.prelude ?? JSON_PRELUDE;
|
|
96
|
+
const tsPrelude = spec.tsPrelude ?? TS_JSON_PRELUDE;
|
|
97
|
+
const memberName = spec.memberName ?? ((n) => n);
|
|
98
|
+
const opExportName = spec.opExportName ?? lowerFirst;
|
|
99
|
+
// 1. Operations — synthesize named Request/Response for Unit I/O so every
|
|
100
|
+
// operation has an input shape that can carry operation-level traits.
|
|
101
|
+
const operations = collectOperations(shapes);
|
|
102
|
+
const httpFor = {}; // input shape id → http trait
|
|
103
|
+
const ns = modelNamespace(operations, shapes, spec.namespaceFallback ?? "smithy.unknown");
|
|
104
|
+
const selected = [];
|
|
105
|
+
for (const op of operations) {
|
|
106
|
+
selected.push(op);
|
|
107
|
+
const { input, output } = ensureNamedIo(shapes, op, ns);
|
|
108
|
+
op.def.__input = input;
|
|
109
|
+
op.def.__output = output;
|
|
110
|
+
const http = op.def.traits?.["smithy.api#http"];
|
|
111
|
+
if (http)
|
|
112
|
+
httpFor[input] = http;
|
|
113
|
+
}
|
|
114
|
+
if (selected.length === 0)
|
|
115
|
+
return { code: "", operations: 0 };
|
|
116
|
+
// 2. Reachability + dependencies-first order (cycles suspend at refs).
|
|
117
|
+
const roots = [
|
|
118
|
+
...selected.flatMap((op) => [op.def.__input, op.def.__output]),
|
|
119
|
+
...(spec.extraRoots?.(selected, shapes) ?? []),
|
|
120
|
+
];
|
|
121
|
+
const reachable = reachableFrom(shapes, roots, shapeDeps);
|
|
122
|
+
const order = topoOrder(shapes, reachable, shapeDeps);
|
|
123
|
+
const indexOf = orderIndex(order);
|
|
124
|
+
/**
|
|
125
|
+
* Shape id → the id whose emitted body it shares.
|
|
126
|
+
*
|
|
127
|
+
* Structural aliasing (see the struct emission below) has to cascade: a
|
|
128
|
+
* parent only matches another parent if their MEMBERS render identically,
|
|
129
|
+
* and a member renders as its target's name. So every reference resolves
|
|
130
|
+
* through this map first — once `…ItemGroupRuleGroup` is aliased onto a
|
|
131
|
+
* canonical shape, the parents referencing it start rendering the same
|
|
132
|
+
* text and collapse in turn.
|
|
133
|
+
*
|
|
134
|
+
* Filled in topological order, so a canonical target is always a shape
|
|
135
|
+
* that was already emitted — a back-reference, never a forward one.
|
|
136
|
+
*/
|
|
137
|
+
const canonicalId = new Map();
|
|
138
|
+
const canon = (target) => canonicalId.get(target) ?? target;
|
|
139
|
+
const rawRef = makeSchemaRef(prelude, indexOf);
|
|
140
|
+
const rawTsRef = makeTsRef(tsPrelude);
|
|
141
|
+
const ref = (target, selfIdx) => rawRef(canon(target), selfIdx);
|
|
142
|
+
const tsRef = (target) => rawTsRef(canon(target));
|
|
143
|
+
// Direction classification for enum openness. Enum ALIASES are emitted
|
|
144
|
+
// CLOSED (exhaustively matchable on reads); request-reachable shapes
|
|
145
|
+
// re-open enum references inline (`X | (string & {})`) so consumers can
|
|
146
|
+
// send tomorrow's values without an SDK update.
|
|
147
|
+
const requestReachable = reachableFrom(shapes, selected.map((op) => op.def.__input), shapeDeps);
|
|
148
|
+
// Discriminant enums: single-value enums required by a union-arm
|
|
149
|
+
// structure (e.g. the worker binding `type: "ai"` literals). These stay
|
|
150
|
+
// CLOSED even on the request side — consumers and the union's TS
|
|
151
|
+
// narrowing rely on the exact literal (v0 parity).
|
|
152
|
+
const discriminantEnums = new Set();
|
|
153
|
+
for (const d of Object.values(shapes)) {
|
|
154
|
+
if (d?.type !== "union")
|
|
155
|
+
continue;
|
|
156
|
+
for (const um of Object.values(d.members ?? {})) {
|
|
157
|
+
const arm = shapes[um.target];
|
|
158
|
+
if (arm?.type !== "structure")
|
|
159
|
+
continue;
|
|
160
|
+
for (const am of Object.values(arm.members ?? {})) {
|
|
161
|
+
const t = shapes[am.target];
|
|
162
|
+
if ((t?.type === "enum" || t?.type === "intEnum") &&
|
|
163
|
+
Object.keys(t.members ?? {}).length === 1 &&
|
|
164
|
+
am.traits?.["smithy.api#required"] !== undefined) {
|
|
165
|
+
discriminantEnums.add(am.target);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* TS reference with direction-aware enum openness. Any reference inside a
|
|
172
|
+
* request-reachable shape re-opens the enum inline — including shapes
|
|
173
|
+
* shared with responses (a value you can SEND must accept undocumented
|
|
174
|
+
* members, and response runtime is open regardless). References inside
|
|
175
|
+
* pure response/error shapes stay the plain closed alias, and so do
|
|
176
|
+
* union-arm discriminant literals.
|
|
177
|
+
*/
|
|
178
|
+
const tsRefAt = (rawTarget, ownerId) => {
|
|
179
|
+
const target = canon(rawTarget);
|
|
180
|
+
const base = tsRef(target);
|
|
181
|
+
if (!requestReachable.has(ownerId))
|
|
182
|
+
return base;
|
|
183
|
+
if (discriminantEnums.has(target))
|
|
184
|
+
return base;
|
|
185
|
+
const d = shapes[target];
|
|
186
|
+
if (d?.type === "enum")
|
|
187
|
+
return `${base} | (string & {})`;
|
|
188
|
+
if (d?.type === "intEnum")
|
|
189
|
+
return `${base} | (number & {})`;
|
|
190
|
+
return base;
|
|
191
|
+
};
|
|
192
|
+
const wireKind = spec.wireKind ??
|
|
193
|
+
((b) => b === "label"
|
|
194
|
+
? "label"
|
|
195
|
+
: b === "query"
|
|
196
|
+
? "query"
|
|
197
|
+
: b === "header"
|
|
198
|
+
? "header"
|
|
199
|
+
: "other");
|
|
200
|
+
// The generic binding cascade; provider bindings slot in after headers.
|
|
201
|
+
const extraBindingOf = (traits) => {
|
|
202
|
+
for (const b of spec.extraBindings ?? []) {
|
|
203
|
+
if (b.trait in traits)
|
|
204
|
+
return b.binding;
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
};
|
|
208
|
+
const bindingOf = (traits) => "smithy.api#httpLabel" in traits
|
|
209
|
+
? "label"
|
|
210
|
+
: "smithy.api#httpQuery" in traits
|
|
211
|
+
? "query"
|
|
212
|
+
: "smithy.api#httpHeader" in traits
|
|
213
|
+
? "header"
|
|
214
|
+
: (extraBindingOf(traits) ??
|
|
215
|
+
("smithy.api#httpPayload" in traits ? "rawBody" : "body"));
|
|
216
|
+
// Shapes reachable from any response/error root — used to scope the
|
|
217
|
+
// blanket-nullable-optionals rule to reads (the wire returns explicit
|
|
218
|
+
// nulls; requests only accept null where a nullable trait says so).
|
|
219
|
+
const responseReachable = spec.optionalsNullable === true
|
|
220
|
+
? reachableFrom(shapes, [
|
|
221
|
+
...selected.map((op) => op.def.__output),
|
|
222
|
+
...collectOpErrorIds(selected, shapes),
|
|
223
|
+
], shapeDeps)
|
|
224
|
+
: undefined;
|
|
225
|
+
const memberInfos = (d, ownerId) => memberBases(d, memberName).map((base) => {
|
|
226
|
+
const traits = base.traits;
|
|
227
|
+
const binding = bindingOf(traits);
|
|
228
|
+
return {
|
|
229
|
+
...base,
|
|
230
|
+
binding,
|
|
231
|
+
wire: smithyWireName(traits, base.name, wireKind(binding)),
|
|
232
|
+
nullable: (spec.nullableTrait ? spec.nullableTrait in traits : false) ||
|
|
233
|
+
// Blanket-nullable optionals (v0 surface): every optional BODY
|
|
234
|
+
// member of a response-reachable shape accepts/announces null
|
|
235
|
+
// in addition to being omittable.
|
|
236
|
+
(responseReachable !== undefined &&
|
|
237
|
+
ownerId !== undefined &&
|
|
238
|
+
responseReachable.has(ownerId) &&
|
|
239
|
+
!base.required &&
|
|
240
|
+
binding === "body"),
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
/** The single value a one-value enum shape fixes, if `target` is one. */
|
|
244
|
+
const soleEnumValue = (target) => {
|
|
245
|
+
const d = shapes[target];
|
|
246
|
+
if (d?.type !== "enum")
|
|
247
|
+
return undefined;
|
|
248
|
+
const values = Object.values(d.members ?? {}).map((m) => m.traits?.["smithy.api#enumValue"]);
|
|
249
|
+
return values.length === 1 && typeof values[0] === "string"
|
|
250
|
+
? values[0]
|
|
251
|
+
: undefined;
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* The member every case of a union pins to a different literal — the tag
|
|
255
|
+
* the API itself discriminates by (`type: "zone"`, `id: "browser_check"`).
|
|
256
|
+
* Undefined unless every case is a structure carrying that member and no
|
|
257
|
+
* two cases claim the same value.
|
|
258
|
+
*/
|
|
259
|
+
const unionDiscriminator = (caseTargets) => {
|
|
260
|
+
if (caseTargets.length < 2)
|
|
261
|
+
return undefined;
|
|
262
|
+
const perCase = caseTargets.map((t) => {
|
|
263
|
+
const cd = shapes[t];
|
|
264
|
+
if (cd?.type !== "structure")
|
|
265
|
+
return undefined;
|
|
266
|
+
const tags = new Map();
|
|
267
|
+
for (const mi of memberInfos(cd)) {
|
|
268
|
+
const value = soleEnumValue(mi.target);
|
|
269
|
+
if (value !== undefined)
|
|
270
|
+
tags.set(mi.tsName, value);
|
|
271
|
+
}
|
|
272
|
+
return tags;
|
|
273
|
+
});
|
|
274
|
+
if (perCase.some((tags) => tags === undefined || tags.size === 0)) {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
for (const key of perCase[0].keys()) {
|
|
278
|
+
const values = perCase.map((tags) => tags.get(key));
|
|
279
|
+
if (values.some((v) => v === undefined))
|
|
280
|
+
continue;
|
|
281
|
+
if (new Set(values).size !== values.length)
|
|
282
|
+
continue;
|
|
283
|
+
return { key, values: values };
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
286
|
+
};
|
|
287
|
+
// Generic pipes for the smithy bindings (the SDK's traits module exports
|
|
288
|
+
// core's Label/Query/Header/HttpBody/Body builders under these names);
|
|
289
|
+
// provider bindings and member traits append theirs via memberExtraPipes.
|
|
290
|
+
const genericPipes = (info) => {
|
|
291
|
+
switch (info.binding) {
|
|
292
|
+
case "label":
|
|
293
|
+
return [
|
|
294
|
+
info.wire === info.tsName ? "T.Label()" : `T.Label(${q(info.wire)})`,
|
|
295
|
+
];
|
|
296
|
+
case "query":
|
|
297
|
+
return [
|
|
298
|
+
info.wire === info.tsName ? "T.Query()" : `T.Query(${q(info.wire)})`,
|
|
299
|
+
];
|
|
300
|
+
case "header":
|
|
301
|
+
return [
|
|
302
|
+
info.wire === info.tsName
|
|
303
|
+
? "T.Header()"
|
|
304
|
+
: `T.Header(${q(info.wire)})`,
|
|
305
|
+
];
|
|
306
|
+
case "rawBody":
|
|
307
|
+
return ["T.HttpBody()"];
|
|
308
|
+
case "body":
|
|
309
|
+
return info.wire !== info.tsName ? [`T.Body(${q(info.wire)})`] : [];
|
|
310
|
+
default:
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
const memberPipes = spec.memberPipes ??
|
|
315
|
+
((info) => [
|
|
316
|
+
...genericPipes(info),
|
|
317
|
+
// The binding table's pipe (envelope payloads, file uploads, …).
|
|
318
|
+
...(spec.extraBindings ?? [])
|
|
319
|
+
.filter((b) => b.binding === info.binding && b.trait in info.traits)
|
|
320
|
+
.map((b) => b.pipe),
|
|
321
|
+
// Trait-table pipes: trait value JSON-inlined as the argument.
|
|
322
|
+
...Object.entries(spec.memberTraitPipes ?? {})
|
|
323
|
+
.filter(([trait]) => info.traits[trait] !== undefined)
|
|
324
|
+
.map(([trait, builder]) => `${builder}(${JSON.stringify(info.traits[trait])})`),
|
|
325
|
+
...(STRING_ENCODED_TRAIT in info.traits ? ["T.StringEncoded()"] : []),
|
|
326
|
+
...(spec.memberExtraPipes?.(info) ?? []),
|
|
327
|
+
]);
|
|
328
|
+
const memberTsTypeOf = (info, tsRefFn) => spec.memberTsType?.(info, tsRefFn) ??
|
|
329
|
+
(spec.extraBindings ?? []).find((b) => b.binding === info.binding && b.tsType !== undefined)?.tsType;
|
|
330
|
+
const emitMember = (info, selfIdx) => {
|
|
331
|
+
let expr = ref(info.target, selfIdx);
|
|
332
|
+
if (info.nullable)
|
|
333
|
+
expr = `S.NullOr(${expr})`;
|
|
334
|
+
const pipes = memberPipes(info);
|
|
335
|
+
if (pipes.length)
|
|
336
|
+
expr = `${expr}.pipe(${pipes.join(", ")})`;
|
|
337
|
+
if (!info.required)
|
|
338
|
+
expr = `S.optional(${expr})`;
|
|
339
|
+
return ` ${q(info.tsName)}: ${expr},`;
|
|
340
|
+
};
|
|
341
|
+
const opIoShapes = new Set();
|
|
342
|
+
for (const op of selected) {
|
|
343
|
+
opIoShapes.add(op.def.__input);
|
|
344
|
+
opIoShapes.add(op.def.__output);
|
|
345
|
+
}
|
|
346
|
+
// 3. Validate pagination traits: a paginated op must actually carry its
|
|
347
|
+
// token on the input and its items member on the output, else it
|
|
348
|
+
// degrades to a plain operation.
|
|
349
|
+
const paginatedOutputs = new Set();
|
|
350
|
+
const paginatedItemsRoot = new Map();
|
|
351
|
+
/** Op id → its full (possibly dotted) items path, `""` when it has none. */
|
|
352
|
+
const paginatedItemsPath = new Map();
|
|
353
|
+
/** Output shape id → the pagination profile that decodes it. */
|
|
354
|
+
const outputProfile = new Map();
|
|
355
|
+
/** Op id → its pagination profile (drives protocol/strategy emission). */
|
|
356
|
+
const opProfile = new Map();
|
|
357
|
+
const usedProfiles = new Set();
|
|
358
|
+
const profileNames = Object.keys(spec.paginationProfiles ?? {});
|
|
359
|
+
if (profileNames.length > 0) {
|
|
360
|
+
for (const op of selected) {
|
|
361
|
+
const pg = op.def.traits?.[PAGINATED_TRAIT];
|
|
362
|
+
if (!pg)
|
|
363
|
+
continue;
|
|
364
|
+
// Which profile decodes this op's pages: the selector's answer, or
|
|
365
|
+
// the sole declared profile.
|
|
366
|
+
const profileName = spec.paginationProfileFor?.(pg, op) ??
|
|
367
|
+
(profileNames.length === 1 ? profileNames[0] : undefined);
|
|
368
|
+
const profile = profileName
|
|
369
|
+
? spec.paginationProfiles[profileName]
|
|
370
|
+
: undefined;
|
|
371
|
+
if (!profile)
|
|
372
|
+
continue;
|
|
373
|
+
const inNames = new Set(memberInfos(shapes[op.def.__input] ?? {}).map((m) => m.tsName));
|
|
374
|
+
const outNames = new Set(memberInfos(shapes[op.def.__output] ?? {}).map((m) => m.tsName));
|
|
375
|
+
const { ok, itemsRoot } = validatePaginated({
|
|
376
|
+
trait: pg,
|
|
377
|
+
inputNames: inNames,
|
|
378
|
+
outputNames: outNames,
|
|
379
|
+
itemsFallback: profile.itemsFallback,
|
|
380
|
+
syntheticOutputs: new Set(profile.syntheticOutputs ?? []),
|
|
381
|
+
});
|
|
382
|
+
if (ok) {
|
|
383
|
+
op.def.__pagination = pg;
|
|
384
|
+
paginatedOutputs.add(op.def.__output);
|
|
385
|
+
paginatedItemsRoot.set(op.def.__output, itemsRoot);
|
|
386
|
+
paginatedItemsPath.set(op.id, String(pg.items ?? profile.itemsFallback ?? ""));
|
|
387
|
+
outputProfile.set(op.def.__output, profile);
|
|
388
|
+
opProfile.set(op.id, profile);
|
|
389
|
+
usedProfiles.add(profile);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// 4. Error classes from the operations' errors lists.
|
|
394
|
+
const out = [];
|
|
395
|
+
// Emitted struct body → the name that owns it, for structural aliasing.
|
|
396
|
+
// Keyed on the emitted TEXT, so two shapes collapse only when what they
|
|
397
|
+
// would emit is byte-identical — including member names, targets, docs and
|
|
398
|
+
// pipes. References are already resolved to names at this point, so a
|
|
399
|
+
// difference anywhere in the tree shows up as different text.
|
|
400
|
+
const structBodies = new Map();
|
|
401
|
+
let aliased = 0;
|
|
402
|
+
// Set when any error carries CATEGORY_TRAIT, so the header only imports
|
|
403
|
+
// the category module when something actually uses it.
|
|
404
|
+
let usesCategories = false;
|
|
405
|
+
const errorIds = collectOpErrorIds(selected, shapes);
|
|
406
|
+
const errorIdSet = new Set(errorIds);
|
|
407
|
+
const errorNames = new Set(errorIds.map(local));
|
|
408
|
+
const shapeDocs = spec.shapeDocs ?? true;
|
|
409
|
+
for (const id of errorIds) {
|
|
410
|
+
const d = shapes[id];
|
|
411
|
+
const name = local(id);
|
|
412
|
+
const doc = oneLine(d.traits?.["smithy.api#documentation"]);
|
|
413
|
+
if (doc && shapeDocs)
|
|
414
|
+
out.push(`/** ${doc} */`);
|
|
415
|
+
const overridden = spec.errors?.override?.({ id, def: d, name });
|
|
416
|
+
if (overridden) {
|
|
417
|
+
out.push(...overridden);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const errorField = spec.errors?.field ??
|
|
421
|
+
((mn, target) => ` ${tsKey(mn)}: ${prelude[local(target)] ?? "S.Unknown"},`);
|
|
422
|
+
const fields = d.members && Object.keys(d.members).length
|
|
423
|
+
? Object.entries(d.members).map(([mn, m]) => errorField(mn, m.target))
|
|
424
|
+
: (spec.errors?.defaultFields?.(prelude) ?? [
|
|
425
|
+
errorField("code", "smithy.api#Integer"),
|
|
426
|
+
errorField("message", "smithy.api#String"),
|
|
427
|
+
]);
|
|
428
|
+
const matchers = spec.errorMatchersTrait
|
|
429
|
+
? d.traits?.[spec.errorMatchersTrait]
|
|
430
|
+
: undefined;
|
|
431
|
+
const categories = categoryPipes(d.traits);
|
|
432
|
+
if (categories.length)
|
|
433
|
+
usesCategories = true;
|
|
434
|
+
out.push(errorClass({
|
|
435
|
+
name,
|
|
436
|
+
fields,
|
|
437
|
+
pipes: categories.length
|
|
438
|
+
? `.pipe(${categories.join(", ")})`
|
|
439
|
+
: undefined,
|
|
440
|
+
wrap: spec.errors?.wrap?.(d.traits ?? {}) ??
|
|
441
|
+
(matchers
|
|
442
|
+
? (cls) => `T.applyErrorMatchers(\n${cls},\n${JSON.stringify(matchers)},\n)`
|
|
443
|
+
: undefined),
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
// 5. Every reachable shape in dependency order: explicit TS type next to
|
|
447
|
+
// a schema const cast to `S.Schema<T>` (the compile-perf pattern).
|
|
448
|
+
order.forEach((id, i) => {
|
|
449
|
+
if (errorIdSet.has(id))
|
|
450
|
+
return; // emitted as an error class above
|
|
451
|
+
const d = shapes[id];
|
|
452
|
+
const name = local(id);
|
|
453
|
+
const doc = oneLine(d.traits?.["smithy.api#documentation"]);
|
|
454
|
+
if (doc && shapeDocs)
|
|
455
|
+
out.push(`/** ${doc} */`);
|
|
456
|
+
const override = spec.shapeOverride?.({
|
|
457
|
+
id,
|
|
458
|
+
def: d,
|
|
459
|
+
name,
|
|
460
|
+
selfIdx: i,
|
|
461
|
+
ref,
|
|
462
|
+
tsRef,
|
|
463
|
+
members: memberInfos,
|
|
464
|
+
});
|
|
465
|
+
if (override) {
|
|
466
|
+
out.push(...override);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (d.type === "structure") {
|
|
470
|
+
// Bare-payload response: single trait-marked member — the response IS
|
|
471
|
+
// that member's value; emit its type + a root marker for the protocol.
|
|
472
|
+
const memberEntriesAll = Object.entries(d.members ?? {});
|
|
473
|
+
const soleMemberRoot = !paginatedOutputs.has(id) && memberEntriesAll.length === 1
|
|
474
|
+
? (spec.extraBindings ?? []).find((b) => b.rootPipe !== undefined &&
|
|
475
|
+
b.trait in (memberEntriesAll[0][1].traits ?? {}))
|
|
476
|
+
: undefined;
|
|
477
|
+
if (soleMemberRoot) {
|
|
478
|
+
const [, m] = memberEntriesAll[0];
|
|
479
|
+
// Alias responses are op roots too — they must carry the service
|
|
480
|
+
// key dictionary so opaque content (union cases, freeform maps)
|
|
481
|
+
// nested in the payload decodes with the same wire mapping as
|
|
482
|
+
// struct-form roots.
|
|
483
|
+
const rootPipes = [
|
|
484
|
+
soleMemberRoot.rootPipe,
|
|
485
|
+
...(spec.rootKeyDictionary && opIoShapes.has(id)
|
|
486
|
+
? [`T.KeyDictionary(KEY_DICTIONARY)`]
|
|
487
|
+
: []),
|
|
488
|
+
];
|
|
489
|
+
out.push(`export type ${name} = ${tsRef(m.target)};`);
|
|
490
|
+
out.push(suspendConst({
|
|
491
|
+
name,
|
|
492
|
+
pure,
|
|
493
|
+
multiline: true,
|
|
494
|
+
annotateIdentifier: true,
|
|
495
|
+
expr: `${ref(m.target, i)}.pipe(${rootPipes.join(", ")})`,
|
|
496
|
+
}));
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
// Paginated outputs always deliver their items member — required.
|
|
500
|
+
const itemsRoot = paginatedItemsRoot.get(id);
|
|
501
|
+
const infos = memberInfos(d, id).map((info) => info.tsName === itemsRoot ? { ...info, required: true } : info);
|
|
502
|
+
const fields = infos.flatMap((info) => interfaceField({
|
|
503
|
+
name: info.tsName,
|
|
504
|
+
optional: !info.required,
|
|
505
|
+
doc: info.doc,
|
|
506
|
+
type: memberTsTypeOf(info, tsRef) ??
|
|
507
|
+
`${tsRefAt(info.target, id)}${info.nullable ? " | null" : ""}`,
|
|
508
|
+
}));
|
|
509
|
+
const members = infos.map((info) => emitMember(info, i));
|
|
510
|
+
const inject = outputProfile.get(id)?.injectOutputMember;
|
|
511
|
+
if (inject && !infos.some((m) => m.tsName === inject.tsName)) {
|
|
512
|
+
fields.push(...inject.interfaceLines);
|
|
513
|
+
members.push(inject.structLine);
|
|
514
|
+
}
|
|
515
|
+
const struct = members.length
|
|
516
|
+
? `S.Struct({\n${members.join("\n")}\n})`
|
|
517
|
+
: `S.Struct({})`;
|
|
518
|
+
const structCtx = {
|
|
519
|
+
id,
|
|
520
|
+
isOpIo: opIoShapes.has(id),
|
|
521
|
+
httpTrait: httpFor[id],
|
|
522
|
+
};
|
|
523
|
+
const pipes = spec.structPipes?.(structCtx) ?? [
|
|
524
|
+
...(structCtx.httpTrait
|
|
525
|
+
? [`T.Http(${JSON.stringify(structCtx.httpTrait)})`]
|
|
526
|
+
: []),
|
|
527
|
+
...(spec.rootKeyDictionary && structCtx.isOpIo
|
|
528
|
+
? [`T.KeyDictionary(KEY_DICTIONARY)`]
|
|
529
|
+
: []),
|
|
530
|
+
];
|
|
531
|
+
const tail = pipes.map((p) => `.pipe(${p})`).join("");
|
|
532
|
+
// Shapes whose emitted body is byte-identical are one shape wearing
|
|
533
|
+
// many names. The docs pipelines generate a fresh copy of every nested
|
|
534
|
+
// shape per operation, so cloudflare's zero_trust carries 374 copies of
|
|
535
|
+
// `{ group?: unknown }` — one per operation x application type x
|
|
536
|
+
// include/exclude/require — and 72% of its 16,800 structures are
|
|
537
|
+
// redundant that way.
|
|
538
|
+
//
|
|
539
|
+
// Emit the body once and alias the rest to it. The public name is kept,
|
|
540
|
+
// so nothing about the surface changes; only the duplicate bodies go.
|
|
541
|
+
// Operation I/O is excluded: those carry the operation's Http trait and
|
|
542
|
+
// must never be merged onto one another.
|
|
543
|
+
const bodyKey = `${JSON.stringify(fields)}|${struct}${tail}`;
|
|
544
|
+
const canonical = structCtx.isOpIo
|
|
545
|
+
? undefined
|
|
546
|
+
: structBodies.get(bodyKey);
|
|
547
|
+
if (canonical !== undefined) {
|
|
548
|
+
out.push(`export type ${name} = ${canonical.name};`);
|
|
549
|
+
out.push(`export const ${name} = ${canonical.name};\n`);
|
|
550
|
+
// Later shapes referencing this one now render the canonical name,
|
|
551
|
+
// which is what lets their bodies collapse too.
|
|
552
|
+
canonicalId.set(id, canonical.id);
|
|
553
|
+
aliased++;
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
if (!structCtx.isOpIo) {
|
|
557
|
+
structBodies.set(bodyKey, { id, name });
|
|
558
|
+
}
|
|
559
|
+
out.push(interfaceDecl(name, fields));
|
|
560
|
+
out.push(suspendConst({
|
|
561
|
+
name,
|
|
562
|
+
pure,
|
|
563
|
+
multiline: true,
|
|
564
|
+
annotateIdentifier: true,
|
|
565
|
+
expr: `${struct}${tail}`,
|
|
566
|
+
}));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else if (d.type === "list") {
|
|
570
|
+
const nullable = spec.nullableTrait !== undefined &&
|
|
571
|
+
spec.nullableTrait in (d.member.traits ?? {});
|
|
572
|
+
const item = ref(d.member.target, i);
|
|
573
|
+
out.push(`export type ${name} = Array<${tsRefAt(d.member.target, id)}${nullable ? " | null" : ""}>;`);
|
|
574
|
+
out.push(`export const ${name} = ${pure}S.Array(${nullable ? `S.NullOr(${item})` : item}) as any as S.Schema<${name}>;\n`);
|
|
575
|
+
}
|
|
576
|
+
else if (d.type === "map") {
|
|
577
|
+
const nullable = spec.nullableTrait !== undefined &&
|
|
578
|
+
spec.nullableTrait in (d.value.traits ?? {});
|
|
579
|
+
const value = ref(d.value.target, i);
|
|
580
|
+
out.push(`export type ${name} = { [key: string]: ${tsRefAt(d.value.target, id)}${nullable ? " | null" : ""} | undefined };`);
|
|
581
|
+
out.push(`export const ${name} = ${pure}S.Record(S.String, ${nullable ? `S.NullOr(${value})` : value}) as any as S.Schema<${name}>;\n`);
|
|
582
|
+
}
|
|
583
|
+
else if (d.type === "union") {
|
|
584
|
+
// A union arm targeting the union itself carries no information
|
|
585
|
+
// (`type X = X | A | B` ≡ `A | B`) and is an illegal circular type
|
|
586
|
+
// alias. Specs produce them: atlas's OnlineArchiveSchedule is a
|
|
587
|
+
// discriminated oneOf whose DEFAULT arm is `allOf: [$ref <the union
|
|
588
|
+
// itself>]`, so the arm resolves back to its own parent.
|
|
589
|
+
const caseTargets = Object.values(d.members ?? {})
|
|
590
|
+
.map((m) => m.target)
|
|
591
|
+
.filter((t) => t !== id);
|
|
592
|
+
const caseKeys = caseTargets.map((t) => {
|
|
593
|
+
const cd = shapes[t];
|
|
594
|
+
return cd?.type === "structure"
|
|
595
|
+
? memberInfos(cd).map((mi) => mi.tsName)
|
|
596
|
+
: [];
|
|
597
|
+
});
|
|
598
|
+
if (spec.union) {
|
|
599
|
+
out.push(...spec.union({ name, caseTargets, caseKeys, tsRef }));
|
|
600
|
+
}
|
|
601
|
+
else if (spec.unionStyle === "opaque-cases") {
|
|
602
|
+
const disc = unionDiscriminator(caseTargets);
|
|
603
|
+
out.push(`export type ${name} = ${caseTargets.map((t) => tsRefAt(t, id)).join(" | ") || "unknown"};`, `export const ${name} = ${pure}S.Unknown.pipe(T.UnionCases(${JSON.stringify(caseKeys)}${disc ? `, ${JSON.stringify(disc)}` : ""}));\n`);
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
throw new Error(`no union emission configured for shape ${id} — set unionStyle or union`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
else if (d.type === "enum") {
|
|
610
|
+
const values = Object.values(d.members ?? {})
|
|
611
|
+
.map((m) => m.traits?.["smithy.api#enumValue"])
|
|
612
|
+
.filter((v) => typeof v === "string");
|
|
613
|
+
out.push(...enumDecl({ name, values, pure }));
|
|
614
|
+
}
|
|
615
|
+
else if (d.type === "intEnum") {
|
|
616
|
+
// CLOSED numeric literal union alias (`type Y = 1 | 30`); INPUT
|
|
617
|
+
// references re-open it inline (`Y | (number & {})`). Schema stays
|
|
618
|
+
// S.Number (protocols never validate enum membership).
|
|
619
|
+
const values = Object.values(d.members ?? {})
|
|
620
|
+
.map((m) => m.traits?.["smithy.api#enumValue"])
|
|
621
|
+
.filter((v) => typeof v === "number");
|
|
622
|
+
const union = values.length ? values.join(" | ") : "number";
|
|
623
|
+
out.push(`export type ${name} = ${union};`, `export const ${name} = S.Number;\n`);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
// 6. Operations — declarative emission from the names in operationDecl,
|
|
627
|
+
// unless the provider overrides the whole shape.
|
|
628
|
+
/**
|
|
629
|
+
* The element type `.items()` yields, resolved from the pagination
|
|
630
|
+
* trait's items PATH against the response shape.
|
|
631
|
+
*
|
|
632
|
+
* `makePaginated` can't infer this — the path is a runtime string — and
|
|
633
|
+
* the structural `API.PaginatedItem` fallback only recognizes bare arrays
|
|
634
|
+
* and `{ result: […] }` envelopes, so every other envelope degrades to
|
|
635
|
+
* `unknown` (distilled #302). Resolving it here and passing it as an
|
|
636
|
+
* explicit type argument is what makes `.items()` typed.
|
|
637
|
+
*
|
|
638
|
+
* Returns undefined when the path doesn't lead to a list, which leaves
|
|
639
|
+
* the annotation on the structural fallback rather than asserting a type
|
|
640
|
+
* the shape doesn't support.
|
|
641
|
+
*/
|
|
642
|
+
const paginatedItemTsType = (outputId, itemsPath) => {
|
|
643
|
+
// No items path: `.items()` is a page passthrough at runtime, so an
|
|
644
|
+
// item IS a whole response.
|
|
645
|
+
if (!itemsPath)
|
|
646
|
+
return tsRef(outputId);
|
|
647
|
+
let id = outputId;
|
|
648
|
+
// Whether the path crossed a list on the way down (`"edges.node"` on a
|
|
649
|
+
// Relay connection). When it did, the path itself is what fans out, so
|
|
650
|
+
// the value it lands on IS one item — it needn't be a list again.
|
|
651
|
+
let fannedOut = false;
|
|
652
|
+
for (const segment of itemsPath.split(".")) {
|
|
653
|
+
let def = shapes[id];
|
|
654
|
+
if (def?.type === "list") {
|
|
655
|
+
id = def.member.target;
|
|
656
|
+
def = shapes[id];
|
|
657
|
+
fannedOut = true;
|
|
658
|
+
}
|
|
659
|
+
if (def?.type !== "structure")
|
|
660
|
+
return undefined;
|
|
661
|
+
const info = memberInfos(def).find((m) => m.tsName === segment);
|
|
662
|
+
if (!info)
|
|
663
|
+
return undefined;
|
|
664
|
+
id = info.target;
|
|
665
|
+
}
|
|
666
|
+
const last = shapes[id];
|
|
667
|
+
if (last?.type === "list")
|
|
668
|
+
return tsRef(last.member.target);
|
|
669
|
+
return fannedOut ? tsRef(id) : undefined;
|
|
670
|
+
};
|
|
671
|
+
const emitOperation = spec.operation ??
|
|
672
|
+
((ctx) => {
|
|
673
|
+
const decl = spec.operationDecl;
|
|
674
|
+
if (!decl) {
|
|
675
|
+
throw new Error("SdkSpec needs either operationDecl or operation");
|
|
676
|
+
}
|
|
677
|
+
const errList = [...ctx.errorNames, ...decl.commonErrorClasses];
|
|
678
|
+
const paginated = ctx.pagination !== undefined;
|
|
679
|
+
const itemTsType = paginated
|
|
680
|
+
? paginatedItemTsType(ctx.op.def.__output, paginatedItemsPath.get(ctx.op.id) ?? "")
|
|
681
|
+
: undefined;
|
|
682
|
+
const typeAnnotation = `API.${paginated ? "PaginatedOperationMethod" : "OperationMethod"}<\n` +
|
|
683
|
+
` ${ctx.inputName},\n` +
|
|
684
|
+
` ${ctx.outputTsType},\n` +
|
|
685
|
+
` ${ctx.opName}Error,\n` +
|
|
686
|
+
` ${decl.contextType}` +
|
|
687
|
+
(itemTsType ? `,\n ${itemTsType}\n` : `\n`) +
|
|
688
|
+
`>`;
|
|
689
|
+
const config = `{\n` +
|
|
690
|
+
` input: ${ctx.inputName},\n` +
|
|
691
|
+
` output: ${ctx.outputSchema},\n` +
|
|
692
|
+
` errors: [${errList.join(", ")}],\n` +
|
|
693
|
+
` protocol: ${(paginated && opProfile.get(ctx.op.id)?.protocol) || decl.protocol},\n` +
|
|
694
|
+
` retry: ${decl.retry},\n` +
|
|
695
|
+
(decl.extraConfig?.(ctx) ?? []).map((l) => ` ${l},\n`).join("") +
|
|
696
|
+
(paginated
|
|
697
|
+
? ` pagination: ${JSON.stringify(ctx.pagination)} as const,\n`
|
|
698
|
+
: "") +
|
|
699
|
+
`}`;
|
|
700
|
+
return [
|
|
701
|
+
errorUnionAlias(ctx.opName, ctx.errorNames, decl.commonErrorType),
|
|
702
|
+
...(ctx.doc ? [`/** ${ctx.doc} */`] : []),
|
|
703
|
+
operationConst({
|
|
704
|
+
exportName: ctx.exportName,
|
|
705
|
+
typeAnnotation,
|
|
706
|
+
factory: paginated ? "API.makePaginated" : "API.make",
|
|
707
|
+
pure,
|
|
708
|
+
extraArg: paginated ? opProfile.get(ctx.op.id)?.strategy : undefined,
|
|
709
|
+
config,
|
|
710
|
+
// `makePaginated` infers the items element from the structural
|
|
711
|
+
// fallback; an explicit item type needs the same `as any as`
|
|
712
|
+
// idiom the schema consts use.
|
|
713
|
+
castToAnnotation: itemTsType !== undefined,
|
|
714
|
+
}),
|
|
715
|
+
].join("\n");
|
|
716
|
+
});
|
|
717
|
+
for (const op of selected) {
|
|
718
|
+
const opName = local(op.id);
|
|
719
|
+
const errNames = (op.def.errors ?? [])
|
|
720
|
+
.map((e) => local(e.target))
|
|
721
|
+
.filter((n) => errorNames.has(n));
|
|
722
|
+
out.push(emitOperation({
|
|
723
|
+
op,
|
|
724
|
+
opName,
|
|
725
|
+
exportName: opExportName(opName),
|
|
726
|
+
inputName: local(op.def.__input),
|
|
727
|
+
outputName: local(op.def.__output),
|
|
728
|
+
outputTsType: tsRef(op.def.__output),
|
|
729
|
+
outputSchema: ref(op.def.__output, indexOf.size),
|
|
730
|
+
errorNames: errNames,
|
|
731
|
+
doc: oneLine(op.def.traits?.["smithy.api#documentation"]),
|
|
732
|
+
pagination: op.def.__pagination,
|
|
733
|
+
}));
|
|
734
|
+
}
|
|
735
|
+
// 7. Alias re-exports, then provider trailing sections.
|
|
736
|
+
const emittedOps = new Set(selected.map((op) => opExportName(local(op.id))));
|
|
737
|
+
for (const { alias, target } of spec.opAliases ?? []) {
|
|
738
|
+
if (!emittedOps.has(target) || emittedOps.has(alias))
|
|
739
|
+
continue;
|
|
740
|
+
emittedOps.add(alias);
|
|
741
|
+
const A = upperFirst(alias);
|
|
742
|
+
const T2 = upperFirst(target);
|
|
743
|
+
out.push(`// Alias of ${target} (same route, alternate export name upstream).\n` +
|
|
744
|
+
`export const ${alias} = ${target};\n` +
|
|
745
|
+
`export type ${A}Request = ${T2}Request;\n` +
|
|
746
|
+
`export type ${A}Response = ${T2}Response;\n` +
|
|
747
|
+
`export type ${A}Error = ${T2}Error;\n`);
|
|
748
|
+
}
|
|
749
|
+
if (spec.footer) {
|
|
750
|
+
out.push(...spec.footer({ emittedOps }));
|
|
751
|
+
}
|
|
752
|
+
// Default header: banner + imports derived from operationDecl names and
|
|
753
|
+
// the conventional SDK module layout, op error/context type re-exports,
|
|
754
|
+
// and the KEY_DICTIONARY const when configured.
|
|
755
|
+
const defaultHeader = (ctx) => {
|
|
756
|
+
const decl = spec.operationDecl;
|
|
757
|
+
if (!decl) {
|
|
758
|
+
throw new Error("the default header needs operationDecl — or pass header");
|
|
759
|
+
}
|
|
760
|
+
const retryNs = decl.retry.split(".")[0];
|
|
761
|
+
// Imports the used pagination profiles pull in: their protocol consts
|
|
762
|
+
// (from the protocol module) and strategy/injected-member names (from
|
|
763
|
+
// the pagination module).
|
|
764
|
+
const profileProtocols = [
|
|
765
|
+
...new Set([...usedProfiles]
|
|
766
|
+
.map((p) => p.protocol)
|
|
767
|
+
.filter((p) => p !== undefined && p !== decl.protocol)),
|
|
768
|
+
];
|
|
769
|
+
const pagImports = [
|
|
770
|
+
...new Set([...usedProfiles].flatMap((p) => [
|
|
771
|
+
...(p.strategy ? [p.strategy] : []),
|
|
772
|
+
...(p.injectOutputMember?.imports ?? []),
|
|
773
|
+
])),
|
|
774
|
+
];
|
|
775
|
+
return (`// AUTO-GENERATED by scripts/generate.ts${spec.sourceNote ? ` from ${spec.sourceNote}` : ""}. Do not edit.\n` +
|
|
776
|
+
`import * as S from "@distilled.cloud/core/schema";\n` +
|
|
777
|
+
`import * as API from "@distilled.cloud/core/api";\n` +
|
|
778
|
+
(usesCategories
|
|
779
|
+
? `import * as C from "@distilled.cloud/core/category";\n`
|
|
780
|
+
: "") +
|
|
781
|
+
`import * as T from "../traits.ts";\n` +
|
|
782
|
+
`import {\n` +
|
|
783
|
+
` ${decl.protocol},\n` +
|
|
784
|
+
profileProtocols.map((p) => ` ${p},\n`).join("") +
|
|
785
|
+
` type ${decl.commonErrorType},\n` +
|
|
786
|
+
` type ${decl.contextType},\n` +
|
|
787
|
+
`} from "../protocol.ts";\n` +
|
|
788
|
+
(ctx.hasPaginated && pagImports.length
|
|
789
|
+
? `import { ${pagImports.join(", ")} } from "../pagination.ts";\n`
|
|
790
|
+
: "") +
|
|
791
|
+
`import { ${[...decl.commonErrorClasses].sort().join(", ")} } from "../errors.ts";\n` +
|
|
792
|
+
`import * as ${retryNs} from "../retry.ts";\n\n` +
|
|
793
|
+
// Re-exported so inferred provider types downstream can always name them.
|
|
794
|
+
`export type { ${decl.commonErrorType}, ${decl.contextType} };\n\n` +
|
|
795
|
+
(spec.rootKeyDictionary
|
|
796
|
+
? `/** ${spec.rootKeyDictionary.doc} */\n` +
|
|
797
|
+
`const KEY_DICTIONARY: Record<string, string | ReadonlyArray<string>> = ${JSON.stringify(spec.rootKeyDictionary.dict)};\n\n`
|
|
798
|
+
: ""));
|
|
799
|
+
};
|
|
800
|
+
const header = (spec.header ?? defaultHeader)({
|
|
801
|
+
hasPaginated: paginatedOutputs.size > 0,
|
|
802
|
+
model,
|
|
803
|
+
});
|
|
804
|
+
const code = header + out.join("\n") + "\n";
|
|
805
|
+
return {
|
|
806
|
+
code: spec.postProcess?.(code) ?? code,
|
|
807
|
+
operations: selected.length,
|
|
808
|
+
};
|
|
809
|
+
};
|
|
810
|
+
// Re-exported so provider specs can be written against the same helpers the
|
|
811
|
+
// driver uses, without importing every codegen module individually.
|
|
812
|
+
export { errorUnionAlias, operationConst, upperFirst, tsKey, q, local };
|
|
813
|
+
//# sourceMappingURL=generator.js.map
|