@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,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared shape-graph utilities for the SDK generators (dev-time only).
|
|
3
|
+
*
|
|
4
|
+
* Works over plain Smithy-JSON shape maps (`{ "ns#Name": { type, members,
|
|
5
|
+
* … } }`). Providers supply the shape map and (optionally) a custom
|
|
6
|
+
* dependency extractor; reachability, topological ordering, and cycle
|
|
7
|
+
* analysis are generic.
|
|
8
|
+
*/
|
|
9
|
+
import { isPrelude } from "./naming.ts";
|
|
10
|
+
|
|
11
|
+
export type ShapeMap = Record<string, any>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Direct dependencies of a shape: structure/union member targets, list
|
|
15
|
+
* member, map key/value. Operations/services are not traversed here —
|
|
16
|
+
* providers seed reachability from operation I/O explicitly.
|
|
17
|
+
*/
|
|
18
|
+
export const shapeDeps = (def: any): string[] => {
|
|
19
|
+
if (!def) return [];
|
|
20
|
+
if (def.type === "structure" || def.type === "union") {
|
|
21
|
+
return Object.values(def.members ?? {}).map((m: any) => m.target);
|
|
22
|
+
}
|
|
23
|
+
if (def.type === "list") return [def.member.target];
|
|
24
|
+
if (def.type === "map") {
|
|
25
|
+
return def.key ? [def.key.target, def.value.target] : [def.value.target];
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Every shape reachable from `roots` through `deps`, excluding prelude
|
|
32
|
+
* shapes and ids missing from the map.
|
|
33
|
+
*/
|
|
34
|
+
export const reachableFrom = (
|
|
35
|
+
shapes: ShapeMap,
|
|
36
|
+
roots: Iterable<string>,
|
|
37
|
+
deps: (def: any) => string[] = shapeDeps,
|
|
38
|
+
): Set<string> => {
|
|
39
|
+
const reachable = new Set<string>();
|
|
40
|
+
const visit = (id: string) => {
|
|
41
|
+
if (isPrelude(id) || reachable.has(id) || !shapes[id]) return;
|
|
42
|
+
reachable.add(id);
|
|
43
|
+
for (const dep of deps(shapes[id])) visit(dep);
|
|
44
|
+
};
|
|
45
|
+
for (const r of roots) visit(r);
|
|
46
|
+
return reachable;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Dependencies-first topological order over `ids`. Cycles are tolerated:
|
|
51
|
+
* a back-edge is simply skipped, so mutually-recursive shapes come out in
|
|
52
|
+
* discovery order and emitters break the cycle with `S.suspend` at
|
|
53
|
+
* forward references (see {@link forwardRef}).
|
|
54
|
+
*/
|
|
55
|
+
export const topoOrder = (
|
|
56
|
+
shapes: ShapeMap,
|
|
57
|
+
ids: Iterable<string>,
|
|
58
|
+
deps: (def: any) => string[] = shapeDeps,
|
|
59
|
+
): string[] => {
|
|
60
|
+
const order: string[] = [];
|
|
61
|
+
const done = new Set<string>();
|
|
62
|
+
const stack = new Set<string>();
|
|
63
|
+
const walk = (id: string) => {
|
|
64
|
+
if (done.has(id) || stack.has(id) || isPrelude(id) || !shapes[id]) return;
|
|
65
|
+
stack.add(id);
|
|
66
|
+
for (const dep of deps(shapes[id])) walk(dep);
|
|
67
|
+
stack.delete(id);
|
|
68
|
+
done.add(id);
|
|
69
|
+
order.push(id);
|
|
70
|
+
};
|
|
71
|
+
for (const id of ids) walk(id);
|
|
72
|
+
return order;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Position index for {@link forwardRef} decisions. */
|
|
76
|
+
export const orderIndex = (order: readonly string[]): Map<string, number> => {
|
|
77
|
+
const indexOf = new Map<string, number>();
|
|
78
|
+
order.forEach((id, i) => indexOf.set(id, i));
|
|
79
|
+
return indexOf;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Whether a reference from the shape at `selfIdx` to `target` points
|
|
84
|
+
* forward in emission order (including into a cycle) and therefore needs
|
|
85
|
+
* `S.suspend(() => …)` to defer evaluation.
|
|
86
|
+
*/
|
|
87
|
+
export const isForwardRef = (
|
|
88
|
+
indexOf: ReadonlyMap<string, number>,
|
|
89
|
+
target: string,
|
|
90
|
+
selfIdx: number,
|
|
91
|
+
): boolean => {
|
|
92
|
+
const ti = indexOf.get(target);
|
|
93
|
+
return ti !== undefined && ti > selfIdx;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* All shape ids that participate in a dependency cycle (Tarjan SCC —
|
|
98
|
+
* components of size > 1, plus direct self-loops). Used by generators
|
|
99
|
+
* that must know cyclicity independent of emission position (e.g. to add
|
|
100
|
+
* an explicit `S.Schema<T>` return type on suspended thunks and break
|
|
101
|
+
* circular type inference).
|
|
102
|
+
*/
|
|
103
|
+
export const cyclicShapeIds = (
|
|
104
|
+
shapes: ShapeMap,
|
|
105
|
+
ids: Iterable<string>,
|
|
106
|
+
deps: (def: any) => string[] = shapeDeps,
|
|
107
|
+
): Set<string> => {
|
|
108
|
+
const idSet = new Set(ids);
|
|
109
|
+
const index = new Map<string, number>();
|
|
110
|
+
const low = new Map<string, number>();
|
|
111
|
+
const onStack = new Set<string>();
|
|
112
|
+
const stack: string[] = [];
|
|
113
|
+
const cyclic = new Set<string>();
|
|
114
|
+
let counter = 0;
|
|
115
|
+
|
|
116
|
+
const strongConnect = (v: string) => {
|
|
117
|
+
index.set(v, counter);
|
|
118
|
+
low.set(v, counter);
|
|
119
|
+
counter++;
|
|
120
|
+
stack.push(v);
|
|
121
|
+
onStack.add(v);
|
|
122
|
+
|
|
123
|
+
for (const w of deps(shapes[v])) {
|
|
124
|
+
if (!idSet.has(w) || !shapes[w]) continue;
|
|
125
|
+
if (!index.has(w)) {
|
|
126
|
+
strongConnect(w);
|
|
127
|
+
low.set(v, Math.min(low.get(v)!, low.get(w)!));
|
|
128
|
+
} else if (onStack.has(w)) {
|
|
129
|
+
low.set(v, Math.min(low.get(v)!, index.get(w)!));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (low.get(v) === index.get(v)) {
|
|
134
|
+
const component: string[] = [];
|
|
135
|
+
let w: string;
|
|
136
|
+
do {
|
|
137
|
+
w = stack.pop()!;
|
|
138
|
+
onStack.delete(w);
|
|
139
|
+
component.push(w);
|
|
140
|
+
} while (w !== v);
|
|
141
|
+
if (component.length > 1 || deps(shapes[v]).some((d) => d === v)) {
|
|
142
|
+
for (const c of component) cyclic.add(c);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
for (const id of idSet) {
|
|
148
|
+
if (!index.has(id) && shapes[id]) strongConnect(id);
|
|
149
|
+
}
|
|
150
|
+
return cyclic;
|
|
151
|
+
};
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { buildSchema, introspectionFromSchema, parse, validate } from "graphql";
|
|
3
|
+
import { compile } from "../graphql.ts";
|
|
4
|
+
import { fixtureSDL } from "../graphql.fixture.ts";
|
|
5
|
+
import { applyOperation } from "../json-patch.ts";
|
|
6
|
+
import {
|
|
7
|
+
convertGraphQLClient,
|
|
8
|
+
generateGraphQLClient,
|
|
9
|
+
graphqlTypeString,
|
|
10
|
+
validateGraphQLModel,
|
|
11
|
+
} from "./graphql-client.ts";
|
|
12
|
+
|
|
13
|
+
const named = (name: string, kind = "OBJECT") => ({ kind, name });
|
|
14
|
+
const required = (ofType: object) => ({ kind: "NON_NULL", ofType });
|
|
15
|
+
const list = (ofType: object) => ({ kind: "LIST", ofType });
|
|
16
|
+
const field = (name: string, type: object, args: object[] = []) => ({
|
|
17
|
+
name,
|
|
18
|
+
type,
|
|
19
|
+
args,
|
|
20
|
+
});
|
|
21
|
+
const fixture = {
|
|
22
|
+
queryType: { name: "Query" },
|
|
23
|
+
mutationType: { name: "Mutation" },
|
|
24
|
+
types: [
|
|
25
|
+
{ kind: "SCALAR", name: "String" },
|
|
26
|
+
{ kind: "SCALAR", name: "Int" },
|
|
27
|
+
{ kind: "SCALAR", name: "JSON" },
|
|
28
|
+
{
|
|
29
|
+
kind: "ENUM",
|
|
30
|
+
name: "Status",
|
|
31
|
+
enumValues: [{ name: "ACTIVE" }, { name: "OLD", isDeprecated: true }],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
kind: "INPUT_OBJECT",
|
|
35
|
+
name: "Filter",
|
|
36
|
+
inputFields: [
|
|
37
|
+
{
|
|
38
|
+
name: "status",
|
|
39
|
+
type: named("Status", "ENUM"),
|
|
40
|
+
defaultValue: "ACTIVE",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
kind: "OBJECT",
|
|
46
|
+
name: "Query",
|
|
47
|
+
fields: [
|
|
48
|
+
field("project", required(named("Project")), [
|
|
49
|
+
{ name: "id", type: required(named("String", "SCALAR")) },
|
|
50
|
+
]),
|
|
51
|
+
field("search", list(named("Search", "UNION"))),
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
kind: "OBJECT",
|
|
56
|
+
name: "Mutation",
|
|
57
|
+
fields: [
|
|
58
|
+
field("projectCreate", named("Project"), [
|
|
59
|
+
{ name: "filter", type: named("Filter", "INPUT_OBJECT") },
|
|
60
|
+
]),
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
kind: "OBJECT",
|
|
65
|
+
name: "Project",
|
|
66
|
+
interfaces: [named("Node", "INTERFACE")],
|
|
67
|
+
fields: [
|
|
68
|
+
field("id", required(named("String", "SCALAR"))),
|
|
69
|
+
field("children", required(list(required(named("Project")))), [
|
|
70
|
+
{
|
|
71
|
+
name: "first",
|
|
72
|
+
type: required(named("Int", "SCALAR")),
|
|
73
|
+
defaultValue: "20",
|
|
74
|
+
},
|
|
75
|
+
{ name: "filter", type: named("Filter", "INPUT_OBJECT") },
|
|
76
|
+
]),
|
|
77
|
+
{
|
|
78
|
+
...field("legacy", named("String", "SCALAR")),
|
|
79
|
+
isDeprecated: true,
|
|
80
|
+
deprecationReason: "Use id",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
kind: "OBJECT",
|
|
86
|
+
name: "Service",
|
|
87
|
+
fields: [field("id", required(named("String", "SCALAR")))],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
kind: "INTERFACE",
|
|
91
|
+
name: "Node",
|
|
92
|
+
fields: [field("id", required(named("String", "SCALAR")))],
|
|
93
|
+
possibleTypes: [named("Project")],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
kind: "UNION",
|
|
97
|
+
name: "Search",
|
|
98
|
+
possibleTypes: [named("Project"), named("Service")],
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const options = {
|
|
104
|
+
transportImport: "./transport.ts",
|
|
105
|
+
requirementsImport: "./transport.ts",
|
|
106
|
+
requirementsType: "Requirements",
|
|
107
|
+
operationAliases: { createProject: "projectCreate" },
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
describe("GraphQL-native compiler", () => {
|
|
111
|
+
test("root helpers allocate safe unique names without hiding schema operations", () => {
|
|
112
|
+
const source = buildSchema(`
|
|
113
|
+
type Query {
|
|
114
|
+
query: String report: String schema: String transport: String G: String
|
|
115
|
+
catchTags: String delete: String Scalars: String Selection: String
|
|
116
|
+
same: String mutationSame: String
|
|
117
|
+
}
|
|
118
|
+
type Mutation { same: String }
|
|
119
|
+
`);
|
|
120
|
+
const model = convertGraphQLClient(introspectionFromSchema(source));
|
|
121
|
+
const output = generateGraphQLClient(model, {
|
|
122
|
+
...options,
|
|
123
|
+
operationAliases: {
|
|
124
|
+
default: "delete",
|
|
125
|
+
deleteField: "delete",
|
|
126
|
+
transport: "report",
|
|
127
|
+
readReport: "report",
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
expect(output).toContain(
|
|
131
|
+
'export const queryQuery = client.operation("query", "query");',
|
|
132
|
+
);
|
|
133
|
+
expect(output).toContain(
|
|
134
|
+
'export const queryReport = client.operation("query", "report");',
|
|
135
|
+
);
|
|
136
|
+
expect(output).toContain(
|
|
137
|
+
'export const queryTransport = client.operation("query", "transport");',
|
|
138
|
+
);
|
|
139
|
+
expect(output).toContain(
|
|
140
|
+
'export const queryDelete = client.operation("query", "delete");',
|
|
141
|
+
);
|
|
142
|
+
expect(output).toContain(
|
|
143
|
+
'export const queryScalars = client.operation("query", "Scalars");',
|
|
144
|
+
);
|
|
145
|
+
expect(output).toContain(
|
|
146
|
+
'export const mutationSame2 = client.operation("mutation", "same");',
|
|
147
|
+
);
|
|
148
|
+
expect(output).toContain("export const deleteField = queryDelete;");
|
|
149
|
+
expect(output).toContain("export const readReport = queryReport;");
|
|
150
|
+
expect(output).not.toContain("export const default =");
|
|
151
|
+
expect(
|
|
152
|
+
[...output.matchAll(/export const ([A-Za-z_$][\w$]*) =/g)].map(
|
|
153
|
+
(match) => match[1],
|
|
154
|
+
),
|
|
155
|
+
).toEqual(expect.arrayContaining(["query", "report", "queryCatchTags"]));
|
|
156
|
+
const declarations = [
|
|
157
|
+
...output.matchAll(/export (?:const|class|type) ([A-Za-z_$][\w$]*)/g),
|
|
158
|
+
].map((match) => match[1]);
|
|
159
|
+
expect(new Set(declarations).size).toBe(declarations.length);
|
|
160
|
+
expect(output.match(/client\.operation\(/g)).toHaveLength(12);
|
|
161
|
+
expect(() =>
|
|
162
|
+
new Bun.Transpiler({ loader: "ts" }).transformSync(output),
|
|
163
|
+
).not.toThrow();
|
|
164
|
+
expect(
|
|
165
|
+
generateGraphQLClient(model, {
|
|
166
|
+
...options,
|
|
167
|
+
operationAliases: {
|
|
168
|
+
default: "delete",
|
|
169
|
+
deleteField: "delete",
|
|
170
|
+
transport: "report",
|
|
171
|
+
readReport: "report",
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
).toBe(output);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("schema type names and patched error tags cannot shadow generated support symbols", () => {
|
|
178
|
+
const model = convertGraphQLClient(
|
|
179
|
+
introspectionFromSchema(
|
|
180
|
+
buildSchema(`
|
|
181
|
+
type Query { data: Schema }
|
|
182
|
+
type Schema { selection: Selection }
|
|
183
|
+
type Selection { id: ID! }
|
|
184
|
+
`),
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
for (const tag of ["Schema", "catchTags", "class"]) {
|
|
188
|
+
applyOperation(model, {
|
|
189
|
+
op: "add",
|
|
190
|
+
path: `/errors/${tag}`,
|
|
191
|
+
value: { matchers: [{ code: tag }] },
|
|
192
|
+
});
|
|
193
|
+
applyOperation(model, {
|
|
194
|
+
op: "add",
|
|
195
|
+
path: "/types/Query/fields/data/errors/-",
|
|
196
|
+
value: tag,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const output = generateGraphQLClient(model, options);
|
|
200
|
+
expect(output).toContain(
|
|
201
|
+
'export class SchemaError extends S.TaggedError<SchemaError>()("Schema", G.errorFields)',
|
|
202
|
+
);
|
|
203
|
+
expect(output).toContain(
|
|
204
|
+
'export class catchTagsError extends S.TaggedError<catchTagsError>()("catchTags", G.errorFields)',
|
|
205
|
+
);
|
|
206
|
+
expect(output).toContain(
|
|
207
|
+
'export class classError extends S.TaggedError<classError>()("class", G.errorFields)',
|
|
208
|
+
);
|
|
209
|
+
expect(output).toContain('"Schema": SchemaError;');
|
|
210
|
+
expect(output).toContain('"Schema": {');
|
|
211
|
+
expect(output).toContain('"Selection": {');
|
|
212
|
+
expect(() =>
|
|
213
|
+
new Bun.Transpiler({ loader: "ts" }).transformSync(output),
|
|
214
|
+
).not.toThrow();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("real introspection round-trips abstract fragments, recursive selections and defaulted required arguments", () => {
|
|
218
|
+
const schema = buildSchema(
|
|
219
|
+
fixtureSDL.replaceAll("first: Int = 20", "first: Int! = 20"),
|
|
220
|
+
);
|
|
221
|
+
const model = convertGraphQLClient(introspectionFromSchema(schema));
|
|
222
|
+
const compiled = compile(model, "query", {
|
|
223
|
+
node: {
|
|
224
|
+
where: { id: "node-id" },
|
|
225
|
+
select: {
|
|
226
|
+
id: true,
|
|
227
|
+
__typename: true,
|
|
228
|
+
__on: {
|
|
229
|
+
Project: {
|
|
230
|
+
services: { select: { edges: { node: { id: true } } } },
|
|
231
|
+
},
|
|
232
|
+
Service: { status: true, project: { parent: { name: true } } },
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
search: {
|
|
237
|
+
where: { text: "query" },
|
|
238
|
+
select: {
|
|
239
|
+
__typename: true,
|
|
240
|
+
__on: { Project: { name: true }, Service: { status: true } },
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
expect(validate(schema, parse(compiled.query))).toEqual([]);
|
|
245
|
+
expect(compiled.query).not.toContain("first:");
|
|
246
|
+
expect(model.types.Project!.fields!.services!.args.first).toEqual({
|
|
247
|
+
type: "Int!",
|
|
248
|
+
defaultValue: "20",
|
|
249
|
+
});
|
|
250
|
+
expect(model.types.ProjectFilter!.inputFields!.statuses).toEqual({
|
|
251
|
+
type: "[Status!]",
|
|
252
|
+
defaultValue: "[ACTIVE]",
|
|
253
|
+
});
|
|
254
|
+
const output = generateGraphQLClient(model, options);
|
|
255
|
+
expect(output).toContain('"first"?: Scalars["Int"]');
|
|
256
|
+
expect(output).toContain('"Node": "Project" | "Service"');
|
|
257
|
+
expect(output).toContain('"SearchResult": "Project" | "Service"');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("required input fields with defaults are omittable but other required fields stay required", () => {
|
|
261
|
+
const schema = buildSchema(`
|
|
262
|
+
input Paging { limit: Int! = 20, owner: ID!, statuses: [String!]! = [] }
|
|
263
|
+
type Query { names(paging: Paging!): [String!]! }
|
|
264
|
+
`);
|
|
265
|
+
const model = convertGraphQLClient(introspectionFromSchema(schema));
|
|
266
|
+
const output = generateGraphQLClient(model, options);
|
|
267
|
+
expect(output).toContain('"limit"?: Scalars["Int"]');
|
|
268
|
+
expect(output).toContain('"owner": Scalars["ID"]');
|
|
269
|
+
expect(output).toContain('"statuses"?: ReadonlyArray<Scalars["String"]>');
|
|
270
|
+
const compiled = compile(model, "query", {
|
|
271
|
+
names: { where: { paging: { owner: "account" } } },
|
|
272
|
+
});
|
|
273
|
+
expect(Object.values(compiled.variables)).toEqual([{ owner: "account" }]);
|
|
274
|
+
expect(validate(schema, parse(compiled.query))).toEqual([]);
|
|
275
|
+
expect(() =>
|
|
276
|
+
compile(model, "query", { names: { where: { paging: {} } } }),
|
|
277
|
+
).toThrow("owner is required");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("provider scalar overrides stay aligned in runtime metadata and generated scalar types", () => {
|
|
281
|
+
const schema = buildSchema(
|
|
282
|
+
`scalar DateTime scalar JSON type Query { date: DateTime! blob: JSON! }`,
|
|
283
|
+
);
|
|
284
|
+
const model = convertGraphQLClient(introspectionFromSchema(schema), {
|
|
285
|
+
scalars: { DateTime: "string" },
|
|
286
|
+
});
|
|
287
|
+
const output = generateGraphQLClient(model, options);
|
|
288
|
+
expect(model.types.DateTime!.scalar).toBe("string");
|
|
289
|
+
expect(model.types.JSON!.scalar).toBe("unknown");
|
|
290
|
+
expect(output).toContain('"DateTime": string;');
|
|
291
|
+
expect(output).toContain('"JSON": unknown;');
|
|
292
|
+
expect(
|
|
293
|
+
validate(
|
|
294
|
+
schema,
|
|
295
|
+
parse(compile(model, "query", { date: true, blob: true }).query),
|
|
296
|
+
),
|
|
297
|
+
).toEqual([]);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("preserves nested required arguments, defaults, recursion, union/interface members and deprecated fields", () => {
|
|
301
|
+
const model = convertGraphQLClient({ data: { __schema: fixture } });
|
|
302
|
+
expect(model.types.Project!.fields!.children).toEqual({
|
|
303
|
+
type: "[Project!]!",
|
|
304
|
+
args: {
|
|
305
|
+
first: { type: "Int!", defaultValue: "20" },
|
|
306
|
+
filter: { type: "Filter" },
|
|
307
|
+
},
|
|
308
|
+
errors: [],
|
|
309
|
+
});
|
|
310
|
+
expect(model.types.Project!.interfaces).toEqual(["Node"]);
|
|
311
|
+
expect(model.types.Node!.possibleTypes).toEqual(["Project"]);
|
|
312
|
+
expect(model.types.Search!.possibleTypes).toEqual(["Project", "Service"]);
|
|
313
|
+
expect(model.types.Project!.fields!.legacy!.deprecated).toBe("Use id");
|
|
314
|
+
expect(model.types.Status!.enumValues).toEqual(["ACTIVE", "OLD"]);
|
|
315
|
+
expect(model.types.Filter!.inputFields!.status!.defaultValue).toBe(
|
|
316
|
+
"ACTIVE",
|
|
317
|
+
);
|
|
318
|
+
expect(model.types.JSON!.scalar).toBe("unknown");
|
|
319
|
+
validateGraphQLModel(model);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("field errors patch the one model used by emitted type unions and runtime classification", () => {
|
|
323
|
+
const model = convertGraphQLClient(fixture);
|
|
324
|
+
applyOperation(model, {
|
|
325
|
+
op: "add",
|
|
326
|
+
path: "/errors/ChildrenUnavailable",
|
|
327
|
+
value: {
|
|
328
|
+
category: "server",
|
|
329
|
+
matchers: [{ code: "CHILDREN_UNAVAILABLE" }],
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
applyOperation(model, {
|
|
333
|
+
op: "add",
|
|
334
|
+
path: "/types/Project/fields/children/errors/-",
|
|
335
|
+
value: "ChildrenUnavailable",
|
|
336
|
+
});
|
|
337
|
+
const output = generateGraphQLClient(model, options);
|
|
338
|
+
expect(output).toContain(
|
|
339
|
+
'"children": G.Field<{ "first"?: Scalars["Int"]; "filter"?: Inputs["Filter"] | null }, "[Project!]!", "ChildrenUnavailable">;',
|
|
340
|
+
);
|
|
341
|
+
expect(output).toContain(
|
|
342
|
+
'export class ChildrenUnavailable extends S.TaggedError<ChildrenUnavailable>()("ChildrenUnavailable", G.errorFields).pipe(Category.withServerError)',
|
|
343
|
+
);
|
|
344
|
+
expect(output).toContain('"errors":["ChildrenUnavailable"]');
|
|
345
|
+
expect(output).toContain('"matchers":[{"code":"CHILDREN_UNAVAILABLE"}]');
|
|
346
|
+
expect(output).toContain(
|
|
347
|
+
'export const projectCreate = client.operation("mutation", "projectCreate");',
|
|
348
|
+
);
|
|
349
|
+
expect(output).toContain("export const createProject = projectCreate;");
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("generation has no fixed depth projection or artificial output optionality", () => {
|
|
353
|
+
const output = generateGraphQLClient(
|
|
354
|
+
convertGraphQLClient(fixture),
|
|
355
|
+
options,
|
|
356
|
+
);
|
|
357
|
+
expect(output).toContain('"Project": {');
|
|
358
|
+
expect(output).toContain('"Search": "Project" | "Service";');
|
|
359
|
+
expect(output).toContain('"Node": "Project";');
|
|
360
|
+
expect(output).toContain('"id": G.Field<{ }, "String!", never>;');
|
|
361
|
+
expect(output).not.toContain('"id"?: G.Field');
|
|
362
|
+
expect(output).not.toContain("maxDepth");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("unknown coordinates, incomplete references and stale patches fail immediately", () => {
|
|
366
|
+
const model = convertGraphQLClient(fixture);
|
|
367
|
+
applyOperation(model, {
|
|
368
|
+
op: "add",
|
|
369
|
+
path: "/types/Project/fields/children/errors/-",
|
|
370
|
+
value: "MissingError",
|
|
371
|
+
});
|
|
372
|
+
expect(() => validateGraphQLModel(model)).toThrow(
|
|
373
|
+
"Project.children: unknown GraphQL error MissingError",
|
|
374
|
+
);
|
|
375
|
+
expect(() => graphqlTypeString({ kind: "NON_NULL" })).toThrow(
|
|
376
|
+
"Incomplete GraphQL NON_NULL",
|
|
377
|
+
);
|
|
378
|
+
expect(() =>
|
|
379
|
+
applyOperation(model, {
|
|
380
|
+
op: "replace",
|
|
381
|
+
path: "/types/Project/fields/removed/errors",
|
|
382
|
+
value: [],
|
|
383
|
+
}),
|
|
384
|
+
).toThrow();
|
|
385
|
+
});
|
|
386
|
+
});
|