@nexus_js/graphql 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/complexity.d.ts +75 -0
- package/dist/complexity.d.ts.map +1 -0
- package/dist/complexity.js +182 -0
- package/dist/complexity.js.map +1 -0
- package/dist/dataloader.d.ts +96 -0
- package/dist/dataloader.d.ts.map +1 -0
- package/dist/dataloader.js +177 -0
- package/dist/dataloader.js.map +1 -0
- package/dist/handler.d.ts +142 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +420 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt.d.ts +109 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +197 -0
- package/dist/jwt.js.map +1 -0
- package/dist/mask.d.ts +70 -0
- package/dist/mask.d.ts.map +1 -0
- package/dist/mask.js +98 -0
- package/dist/mask.js.map +1 -0
- package/dist/remote-executor.d.ts +126 -0
- package/dist/remote-executor.d.ts.map +1 -0
- package/dist/remote-executor.js +270 -0
- package/dist/remote-executor.js.map +1 -0
- package/dist/stitching.d.ts +145 -0
- package/dist/stitching.d.ts.map +1 -0
- package/dist/stitching.js +111 -0
- package/dist/stitching.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexus_js/graphql - Remote GraphQL Executor
|
|
3
|
+
*
|
|
4
|
+
* Proxy to external GraphQL APIs with Nexus Shield integration.
|
|
5
|
+
* Use this to migrate legacy backends gradually by fronting them with Nexus security.
|
|
6
|
+
*/
|
|
7
|
+
import type { GraphQLSchema } from 'graphql';
|
|
8
|
+
export interface RemoteExecutorOptions {
|
|
9
|
+
/**
|
|
10
|
+
* URL of the remote GraphQL endpoint.
|
|
11
|
+
* @example 'https://legacy-api.example.com/graphql'
|
|
12
|
+
*/
|
|
13
|
+
url: string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional headers to include in every request.
|
|
16
|
+
* Use for API keys or service-to-service auth.
|
|
17
|
+
* @example { 'x-api-key': process.env.LEGACY_API_KEY }
|
|
18
|
+
*/
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
/**
|
|
21
|
+
* Timeout for remote requests in milliseconds.
|
|
22
|
+
* @default 10000 (10 seconds)
|
|
23
|
+
*/
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Whether to forward Authorization header from incoming request.
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
forwardAuth?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Transform outgoing variables before sending to remote.
|
|
32
|
+
* Use to rename fields or inject context.
|
|
33
|
+
*/
|
|
34
|
+
transformVariables?: (variables: Record<string, unknown>) => Record<string, unknown>;
|
|
35
|
+
/**
|
|
36
|
+
* Transform incoming result after receiving from remote.
|
|
37
|
+
* Use to adapt legacy response format to Nexus conventions.
|
|
38
|
+
*/
|
|
39
|
+
transformResult?: (result: unknown) => unknown;
|
|
40
|
+
/**
|
|
41
|
+
* Enable batching multiple operations into a single HTTP request.
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
batch?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Retry failed requests (network errors, 5xx).
|
|
47
|
+
* @default { attempts: 2, delayMs: 500 }
|
|
48
|
+
*/
|
|
49
|
+
retry?: {
|
|
50
|
+
attempts: number;
|
|
51
|
+
delayMs: number;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export interface RemoteExecutionContext {
|
|
55
|
+
/**
|
|
56
|
+
* Nexus context from the incoming request.
|
|
57
|
+
*/
|
|
58
|
+
nexusContext?: Record<string, unknown>;
|
|
59
|
+
/**
|
|
60
|
+
* Custom headers to merge with executor defaults.
|
|
61
|
+
*/
|
|
62
|
+
headers?: Record<string, string>;
|
|
63
|
+
/**
|
|
64
|
+
* Override timeout for this specific request.
|
|
65
|
+
*/
|
|
66
|
+
timeoutMs?: number;
|
|
67
|
+
}
|
|
68
|
+
export interface RemoteExecutionResult<T = unknown> {
|
|
69
|
+
data?: T;
|
|
70
|
+
errors?: Array<{
|
|
71
|
+
message: string;
|
|
72
|
+
locations?: Array<{
|
|
73
|
+
line: number;
|
|
74
|
+
column: number;
|
|
75
|
+
}>;
|
|
76
|
+
path?: Array<string | number>;
|
|
77
|
+
extensions?: Record<string, unknown>;
|
|
78
|
+
}>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a remote GraphQL executor that acts as a proxy to a legacy backend.
|
|
82
|
+
*
|
|
83
|
+
* Nexus Shield and rate limiting are applied BEFORE forwarding the request.
|
|
84
|
+
* This lets you add security to an insecure legacy API without modifying it.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { createRemoteExecutor } from '@nexus_js/graphql';
|
|
89
|
+
*
|
|
90
|
+
* const legacyApi = createRemoteExecutor({
|
|
91
|
+
* url: 'https://old-api.company.com/graphql',
|
|
92
|
+
* headers: { 'x-service-token': vault.get('LEGACY_TOKEN') },
|
|
93
|
+
* timeoutMs: 5000,
|
|
94
|
+
* forwardAuth: true,
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* // In resolver:
|
|
98
|
+
* const result = await legacyApi(
|
|
99
|
+
* 'query GetUser($id: ID!) { user(id: $id) { name email } }',
|
|
100
|
+
* { id: '123' },
|
|
101
|
+
* { nexusContext: ctx }
|
|
102
|
+
* );
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare function createRemoteExecutor(opts: RemoteExecutorOptions): <T = unknown>(query: string, variables?: Record<string, unknown>, context?: RemoteExecutionContext) => Promise<RemoteExecutionResult<T>>;
|
|
106
|
+
/**
|
|
107
|
+
* Create a remote executor with introspection to fetch the remote schema.
|
|
108
|
+
* Useful for GraphQL schema stitching (federation).
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* const { executor, schema } = await createRemoteExecutorWithSchema({
|
|
113
|
+
* url: 'https://legacy.example.com/graphql',
|
|
114
|
+
* });
|
|
115
|
+
*
|
|
116
|
+
* // Use `schema` for stitching or merging with Nexus schema
|
|
117
|
+
* const stitchedSchema = stitchSchemas({
|
|
118
|
+
* subschemas: [{ schema, executor }],
|
|
119
|
+
* });
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare function createRemoteExecutorWithSchema(opts: RemoteExecutorOptions): Promise<{
|
|
123
|
+
executor: ReturnType<typeof createRemoteExecutor>;
|
|
124
|
+
schema: GraphQLSchema | null;
|
|
125
|
+
}>;
|
|
126
|
+
//# sourceMappingURL=remote-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-executor.d.ts","sourceRoot":"","sources":["../src/remote-executor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAErF;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;IAE/C;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,KAAK,CAAC,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEvC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEjC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC,GAAG,OAAO;IAChD,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,MAAM,CAAC,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACpD,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;QAC9B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACtC,CAAC,CAAC;CACJ;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,IA+IhC,CAAC,GAAG,OAAO,EACvC,OAAO,MAAM,EACb,YAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACvC,UAAU,sBAAsB,KAC/B,OAAO,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAMrC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC;IACzF,QAAQ,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;IAClD,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;CAC9B,CAAC,CAiHD"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexus_js/graphql - Remote GraphQL Executor
|
|
3
|
+
*
|
|
4
|
+
* Proxy to external GraphQL APIs with Nexus Shield integration.
|
|
5
|
+
* Use this to migrate legacy backends gradually by fronting them with Nexus security.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Create a remote GraphQL executor that acts as a proxy to a legacy backend.
|
|
9
|
+
*
|
|
10
|
+
* Nexus Shield and rate limiting are applied BEFORE forwarding the request.
|
|
11
|
+
* This lets you add security to an insecure legacy API without modifying it.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { createRemoteExecutor } from '@nexus_js/graphql';
|
|
16
|
+
*
|
|
17
|
+
* const legacyApi = createRemoteExecutor({
|
|
18
|
+
* url: 'https://old-api.company.com/graphql',
|
|
19
|
+
* headers: { 'x-service-token': vault.get('LEGACY_TOKEN') },
|
|
20
|
+
* timeoutMs: 5000,
|
|
21
|
+
* forwardAuth: true,
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // In resolver:
|
|
25
|
+
* const result = await legacyApi(
|
|
26
|
+
* 'query GetUser($id: ID!) { user(id: $id) { name email } }',
|
|
27
|
+
* { id: '123' },
|
|
28
|
+
* { nexusContext: ctx }
|
|
29
|
+
* );
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function createRemoteExecutor(opts) {
|
|
33
|
+
const { url, headers: defaultHeaders = {}, timeoutMs: defaultTimeout = 10_000, forwardAuth = false, transformVariables, transformResult, batch = false, retry = { attempts: 2, delayMs: 500 }, } = opts;
|
|
34
|
+
// Batch queue for combining operations
|
|
35
|
+
let batchQueue = [];
|
|
36
|
+
let batchTimer = null;
|
|
37
|
+
async function executeSingle(query, variables, context) {
|
|
38
|
+
const finalVariables = transformVariables ? transformVariables(variables) : variables;
|
|
39
|
+
const timeout = context?.timeoutMs ?? defaultTimeout;
|
|
40
|
+
const reqHeaders = {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
...defaultHeaders,
|
|
43
|
+
...(context?.headers ?? {}),
|
|
44
|
+
};
|
|
45
|
+
// Forward Authorization if configured
|
|
46
|
+
if (forwardAuth && context?.nexusContext) {
|
|
47
|
+
const authHeader = context.nexusContext?.request
|
|
48
|
+
?.headers;
|
|
49
|
+
if (authHeader) {
|
|
50
|
+
const auth = authHeader.get('authorization');
|
|
51
|
+
if (auth)
|
|
52
|
+
reqHeaders['authorization'] = auth;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const body = JSON.stringify({ query, variables: finalVariables });
|
|
56
|
+
let lastError = null;
|
|
57
|
+
for (let attempt = 0; attempt < retry.attempts; attempt++) {
|
|
58
|
+
try {
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
61
|
+
const response = await fetch(url, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: reqHeaders,
|
|
64
|
+
body,
|
|
65
|
+
signal: controller.signal,
|
|
66
|
+
});
|
|
67
|
+
clearTimeout(timeoutId);
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
// Retry on 5xx
|
|
70
|
+
if (response.status >= 500 && attempt < retry.attempts - 1) {
|
|
71
|
+
await new Promise((r) => setTimeout(r, retry.delayMs));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`Remote GraphQL returned ${response.status}: ${await response.text()}`);
|
|
75
|
+
}
|
|
76
|
+
const json = (await response.json());
|
|
77
|
+
if (transformResult && json.data) {
|
|
78
|
+
json.data = transformResult(json.data);
|
|
79
|
+
}
|
|
80
|
+
return json;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
84
|
+
// Retry on network errors
|
|
85
|
+
if (attempt < retry.attempts - 1) {
|
|
86
|
+
await new Promise((r) => setTimeout(r, retry.delayMs));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
throw lastError ?? new Error('Remote execution failed');
|
|
91
|
+
}
|
|
92
|
+
async function flushBatch() {
|
|
93
|
+
if (batchQueue.length === 0)
|
|
94
|
+
return;
|
|
95
|
+
const ops = [...batchQueue];
|
|
96
|
+
batchQueue = [];
|
|
97
|
+
try {
|
|
98
|
+
// Build batch request (standard GraphQL batch format: array of { query, variables })
|
|
99
|
+
const batchBody = ops.map((op) => ({
|
|
100
|
+
query: op.query,
|
|
101
|
+
variables: transformVariables ? transformVariables(op.variables) : op.variables,
|
|
102
|
+
}));
|
|
103
|
+
const response = await fetch(url, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: { 'Content-Type': 'application/json', ...defaultHeaders },
|
|
106
|
+
body: JSON.stringify(batchBody),
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`Batch request failed: ${response.status}`);
|
|
110
|
+
}
|
|
111
|
+
const results = (await response.json());
|
|
112
|
+
ops.forEach((op, i) => {
|
|
113
|
+
const result = results[i];
|
|
114
|
+
if (transformResult && result?.data) {
|
|
115
|
+
result.data = transformResult(result.data);
|
|
116
|
+
}
|
|
117
|
+
op.resolve(result ?? { errors: [{ message: 'No result from batch' }] });
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
ops.forEach((op) => op.reject(err instanceof Error ? err : new Error(String(err))));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function executeBatched(query, variables) {
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
batchQueue.push({ query, variables, resolve, reject });
|
|
127
|
+
if (!batchTimer) {
|
|
128
|
+
batchTimer = setTimeout(() => {
|
|
129
|
+
batchTimer = null;
|
|
130
|
+
void flushBatch();
|
|
131
|
+
}, 10); // 10ms batch window
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return async function execute(query, variables = {}, context) {
|
|
136
|
+
if (batch && !context) {
|
|
137
|
+
return executeBatched(query, variables);
|
|
138
|
+
}
|
|
139
|
+
return executeSingle(query, variables, context);
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Create a remote executor with introspection to fetch the remote schema.
|
|
144
|
+
* Useful for GraphQL schema stitching (federation).
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* const { executor, schema } = await createRemoteExecutorWithSchema({
|
|
149
|
+
* url: 'https://legacy.example.com/graphql',
|
|
150
|
+
* });
|
|
151
|
+
*
|
|
152
|
+
* // Use `schema` for stitching or merging with Nexus schema
|
|
153
|
+
* const stitchedSchema = stitchSchemas({
|
|
154
|
+
* subschemas: [{ schema, executor }],
|
|
155
|
+
* });
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export async function createRemoteExecutorWithSchema(opts) {
|
|
159
|
+
const executor = createRemoteExecutor(opts);
|
|
160
|
+
// Fetch introspection query
|
|
161
|
+
const introspectionQuery = `
|
|
162
|
+
query IntrospectionQuery {
|
|
163
|
+
__schema {
|
|
164
|
+
queryType { name }
|
|
165
|
+
mutationType { name }
|
|
166
|
+
subscriptionType { name }
|
|
167
|
+
types {
|
|
168
|
+
...FullType
|
|
169
|
+
}
|
|
170
|
+
directives {
|
|
171
|
+
name
|
|
172
|
+
description
|
|
173
|
+
locations
|
|
174
|
+
args {
|
|
175
|
+
...InputValue
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fragment FullType on __Type {
|
|
182
|
+
kind
|
|
183
|
+
name
|
|
184
|
+
description
|
|
185
|
+
fields(includeDeprecated: true) {
|
|
186
|
+
name
|
|
187
|
+
description
|
|
188
|
+
args {
|
|
189
|
+
...InputValue
|
|
190
|
+
}
|
|
191
|
+
type {
|
|
192
|
+
...TypeRef
|
|
193
|
+
}
|
|
194
|
+
isDeprecated
|
|
195
|
+
deprecationReason
|
|
196
|
+
}
|
|
197
|
+
inputFields {
|
|
198
|
+
...InputValue
|
|
199
|
+
}
|
|
200
|
+
interfaces {
|
|
201
|
+
...TypeRef
|
|
202
|
+
}
|
|
203
|
+
enumValues(includeDeprecated: true) {
|
|
204
|
+
name
|
|
205
|
+
description
|
|
206
|
+
isDeprecated
|
|
207
|
+
deprecationReason
|
|
208
|
+
}
|
|
209
|
+
possibleTypes {
|
|
210
|
+
...TypeRef
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
fragment InputValue on __InputValue {
|
|
215
|
+
name
|
|
216
|
+
description
|
|
217
|
+
type { ...TypeRef }
|
|
218
|
+
defaultValue
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fragment TypeRef on __Type {
|
|
222
|
+
kind
|
|
223
|
+
name
|
|
224
|
+
ofType {
|
|
225
|
+
kind
|
|
226
|
+
name
|
|
227
|
+
ofType {
|
|
228
|
+
kind
|
|
229
|
+
name
|
|
230
|
+
ofType {
|
|
231
|
+
kind
|
|
232
|
+
name
|
|
233
|
+
ofType {
|
|
234
|
+
kind
|
|
235
|
+
name
|
|
236
|
+
ofType {
|
|
237
|
+
kind
|
|
238
|
+
name
|
|
239
|
+
ofType {
|
|
240
|
+
kind
|
|
241
|
+
name
|
|
242
|
+
ofType {
|
|
243
|
+
kind
|
|
244
|
+
name
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
try {
|
|
255
|
+
const result = await executor(introspectionQuery);
|
|
256
|
+
if (result.errors) {
|
|
257
|
+
console.warn('Remote introspection failed:', result.errors);
|
|
258
|
+
return { executor, schema: null };
|
|
259
|
+
}
|
|
260
|
+
// Build schema from introspection result (requires graphql package)
|
|
261
|
+
// This is a placeholder — full implementation would use buildClientSchema
|
|
262
|
+
// from 'graphql/utilities' with result.data as IntrospectionQuery
|
|
263
|
+
return { executor, schema: null }; // TODO: implement buildClientSchema
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
console.warn('Failed to introspect remote schema:', err);
|
|
267
|
+
return { executor, schema: null };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=remote-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-executor.js","sourceRoot":"","sources":["../src/remote-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexus_js/graphql - Schema Stitching & Federation
|
|
3
|
+
*
|
|
4
|
+
* Merge remote GraphQL schemas (legacy backends) with Nexus-native resolvers.
|
|
5
|
+
* This lets you gradually migrate by adding new fields to old types.
|
|
6
|
+
*/
|
|
7
|
+
import type { GraphQLSchema, GraphQLFieldResolver } from 'graphql';
|
|
8
|
+
export interface SubschemaConfig {
|
|
9
|
+
/**
|
|
10
|
+
* GraphQL schema from remote or local source.
|
|
11
|
+
*/
|
|
12
|
+
schema: GraphQLSchema;
|
|
13
|
+
/**
|
|
14
|
+
* Executor function for resolving fields from this subschema.
|
|
15
|
+
* For remote schemas, use createRemoteExecutor().
|
|
16
|
+
*/
|
|
17
|
+
executor?: (opts: {
|
|
18
|
+
document: string;
|
|
19
|
+
variables?: Record<string, unknown>;
|
|
20
|
+
context?: unknown;
|
|
21
|
+
}) => Promise<{
|
|
22
|
+
data?: unknown;
|
|
23
|
+
errors?: unknown[];
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Transforms to apply to this subschema before merging.
|
|
27
|
+
*/
|
|
28
|
+
transforms?: Array<{
|
|
29
|
+
transformSchema?: (schema: GraphQLSchema) => GraphQLSchema;
|
|
30
|
+
transformRequest?: (request: unknown) => unknown;
|
|
31
|
+
transformResult?: (result: unknown) => unknown;
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Batch configuration for this subschema.
|
|
35
|
+
*/
|
|
36
|
+
batch?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface StitchSchemasOptions {
|
|
39
|
+
/**
|
|
40
|
+
* Array of subschemas to merge.
|
|
41
|
+
* Each can be a local Nexus schema or a remote legacy schema.
|
|
42
|
+
*/
|
|
43
|
+
subschemas: SubschemaConfig[];
|
|
44
|
+
/**
|
|
45
|
+
* Type merging configuration.
|
|
46
|
+
* Allows multiple subschemas to contribute fields to the same type.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* typeMerging: {
|
|
51
|
+
* User: {
|
|
52
|
+
* // If User exists in both schemas, merge their fields
|
|
53
|
+
* selectionSet: '{ id }',
|
|
54
|
+
* fieldName: 'user',
|
|
55
|
+
* args: (obj) => ({ id: obj.id }),
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
typeMerging?: Record<string, {
|
|
61
|
+
selectionSet: string;
|
|
62
|
+
fieldName: string;
|
|
63
|
+
args?: (obj: unknown) => Record<string, unknown>;
|
|
64
|
+
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Resolvers to add or override in the stitched schema.
|
|
67
|
+
* Use this to add Nexus-specific business logic on top of legacy fields.
|
|
68
|
+
*/
|
|
69
|
+
resolvers?: Record<string, Record<string, GraphQLFieldResolver<unknown, unknown>>>;
|
|
70
|
+
/**
|
|
71
|
+
* Schema directives to add to the stitched schema.
|
|
72
|
+
*/
|
|
73
|
+
schemaDirectives?: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Stitch multiple GraphQL schemas into one unified schema.
|
|
77
|
+
*
|
|
78
|
+
* This is the core of Nexus's "Legacy Bridge" feature.
|
|
79
|
+
* You can combine:
|
|
80
|
+
* - Remote schemas from old backends (via createRemoteExecutor)
|
|
81
|
+
* - Local Nexus schemas with Shield/Vault integration
|
|
82
|
+
* - New resolvers that add security to legacy fields
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import { stitchSchemas } from '@nexus_js/graphql';
|
|
87
|
+
* import { createRemoteExecutor } from '@nexus_js/graphql';
|
|
88
|
+
*
|
|
89
|
+
* const legacyExecutor = createRemoteExecutor({
|
|
90
|
+
* url: 'https://old-api.example.com/graphql',
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* const { executor: legacyExec, schema: legacySchema } =
|
|
94
|
+
* await createRemoteExecutorWithSchema({ url: '...' });
|
|
95
|
+
*
|
|
96
|
+
* const stitched = stitchSchemas({
|
|
97
|
+
* subschemas: [
|
|
98
|
+
* { schema: legacySchema, executor: legacyExec },
|
|
99
|
+
* { schema: nexusSchema },
|
|
100
|
+
* ],
|
|
101
|
+
* typeMerging: {
|
|
102
|
+
* User: {
|
|
103
|
+
* selectionSet: '{ id }',
|
|
104
|
+
* fieldName: 'user',
|
|
105
|
+
* args: (obj) => ({ id: obj.id }),
|
|
106
|
+
* }
|
|
107
|
+
* },
|
|
108
|
+
* resolvers: {
|
|
109
|
+
* User: {
|
|
110
|
+
* // Add Shield protection to legacy User.apiKey field
|
|
111
|
+
* apiKey: async (parent, args, context) => {
|
|
112
|
+
* if (context.user?.role !== 'admin') return null;
|
|
113
|
+
* return parent.apiKey; // Delegate to legacy
|
|
114
|
+
* }
|
|
115
|
+
* }
|
|
116
|
+
* }
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export declare function stitchSchemas(opts: StitchSchemasOptions): GraphQLSchema;
|
|
121
|
+
/**
|
|
122
|
+
* Helper to create a "gateway" resolver that delegates to multiple backends.
|
|
123
|
+
*
|
|
124
|
+
* Use this when you want Nexus to act as a unified API gateway
|
|
125
|
+
* that routes requests to different legacy services.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* const gateway = createGatewayResolver({
|
|
130
|
+
* services: {
|
|
131
|
+
* auth: createRemoteExecutor({ url: 'http://auth.internal/graphql' }),
|
|
132
|
+
* payments: createRemoteExecutor({ url: 'http://payments.internal/graphql' }),
|
|
133
|
+
* },
|
|
134
|
+
* routing: {
|
|
135
|
+
* 'Query.user': 'auth',
|
|
136
|
+
* 'Query.payment': 'payments',
|
|
137
|
+
* }
|
|
138
|
+
* });
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export declare function createGatewayResolver(opts: {
|
|
142
|
+
services: Record<string, ReturnType<typeof import('./remote-executor').createRemoteExecutor>>;
|
|
143
|
+
routing: Record<string, string>;
|
|
144
|
+
}): GraphQLFieldResolver<unknown, unknown>;
|
|
145
|
+
//# sourceMappingURL=stitching.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stitching.d.ts","sourceRoot":"","sources":["../src/stitching.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEnE,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;IAEtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,KAAK,OAAO,CAAC;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC,CAAC;IAEtD;;OAEG;IACH,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,aAAa,CAAC;QAC3D,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC;QACjD,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;KAChD,CAAC,CAAC;IAEH;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,UAAU,EAAE,eAAe,EAAE,CAAC;IAE9B;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,EAAE,MAAM,CAClB,MAAM,EACN;QACE,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClD,CACF,CAAC;IAEF;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnF;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,aAAa,CAyBvE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,cAAc,mBAAmB,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAC9F,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,GAAG,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,CA0BzC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nexus_js/graphql - Schema Stitching & Federation
|
|
3
|
+
*
|
|
4
|
+
* Merge remote GraphQL schemas (legacy backends) with Nexus-native resolvers.
|
|
5
|
+
* This lets you gradually migrate by adding new fields to old types.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Stitch multiple GraphQL schemas into one unified schema.
|
|
9
|
+
*
|
|
10
|
+
* This is the core of Nexus's "Legacy Bridge" feature.
|
|
11
|
+
* You can combine:
|
|
12
|
+
* - Remote schemas from old backends (via createRemoteExecutor)
|
|
13
|
+
* - Local Nexus schemas with Shield/Vault integration
|
|
14
|
+
* - New resolvers that add security to legacy fields
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { stitchSchemas } from '@nexus_js/graphql';
|
|
19
|
+
* import { createRemoteExecutor } from '@nexus_js/graphql';
|
|
20
|
+
*
|
|
21
|
+
* const legacyExecutor = createRemoteExecutor({
|
|
22
|
+
* url: 'https://old-api.example.com/graphql',
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const { executor: legacyExec, schema: legacySchema } =
|
|
26
|
+
* await createRemoteExecutorWithSchema({ url: '...' });
|
|
27
|
+
*
|
|
28
|
+
* const stitched = stitchSchemas({
|
|
29
|
+
* subschemas: [
|
|
30
|
+
* { schema: legacySchema, executor: legacyExec },
|
|
31
|
+
* { schema: nexusSchema },
|
|
32
|
+
* ],
|
|
33
|
+
* typeMerging: {
|
|
34
|
+
* User: {
|
|
35
|
+
* selectionSet: '{ id }',
|
|
36
|
+
* fieldName: 'user',
|
|
37
|
+
* args: (obj) => ({ id: obj.id }),
|
|
38
|
+
* }
|
|
39
|
+
* },
|
|
40
|
+
* resolvers: {
|
|
41
|
+
* User: {
|
|
42
|
+
* // Add Shield protection to legacy User.apiKey field
|
|
43
|
+
* apiKey: async (parent, args, context) => {
|
|
44
|
+
* if (context.user?.role !== 'admin') return null;
|
|
45
|
+
* return parent.apiKey; // Delegate to legacy
|
|
46
|
+
* }
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function stitchSchemas(opts) {
|
|
53
|
+
const { subschemas, typeMerging, resolvers, schemaDirectives } = opts;
|
|
54
|
+
// PLACEHOLDER IMPLEMENTATION
|
|
55
|
+
// Full implementation would use @graphql-tools/stitch or similar
|
|
56
|
+
// For now, return a mock schema to show intent
|
|
57
|
+
console.warn('[Nexus] Schema stitching requires @graphql-tools/stitch. Install it separately:', 'npm install @graphql-tools/stitch @graphql-tools/delegate');
|
|
58
|
+
// Return first schema as fallback
|
|
59
|
+
if (subschemas.length === 0) {
|
|
60
|
+
throw new Error('stitchSchemas requires at least one subschema');
|
|
61
|
+
}
|
|
62
|
+
// TODO: Implement full stitching logic with type merging
|
|
63
|
+
// This would involve:
|
|
64
|
+
// 1. Merging type definitions from all subschemas
|
|
65
|
+
// 2. Creating delegating resolvers for remote fields
|
|
66
|
+
// 3. Applying transforms and type merging config
|
|
67
|
+
// 4. Adding custom resolvers on top
|
|
68
|
+
return subschemas[0].schema;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Helper to create a "gateway" resolver that delegates to multiple backends.
|
|
72
|
+
*
|
|
73
|
+
* Use this when you want Nexus to act as a unified API gateway
|
|
74
|
+
* that routes requests to different legacy services.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* const gateway = createGatewayResolver({
|
|
79
|
+
* services: {
|
|
80
|
+
* auth: createRemoteExecutor({ url: 'http://auth.internal/graphql' }),
|
|
81
|
+
* payments: createRemoteExecutor({ url: 'http://payments.internal/graphql' }),
|
|
82
|
+
* },
|
|
83
|
+
* routing: {
|
|
84
|
+
* 'Query.user': 'auth',
|
|
85
|
+
* 'Query.payment': 'payments',
|
|
86
|
+
* }
|
|
87
|
+
* });
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function createGatewayResolver(opts) {
|
|
91
|
+
const { services, routing } = opts;
|
|
92
|
+
return async function gatewayResolver(parent, args, context, info) {
|
|
93
|
+
const fieldKey = `${info.parentType.name}.${info.fieldName}`;
|
|
94
|
+
const serviceName = routing[fieldKey];
|
|
95
|
+
if (!serviceName || !services[serviceName]) {
|
|
96
|
+
throw new Error(`No service configured for ${fieldKey}`);
|
|
97
|
+
}
|
|
98
|
+
const executor = services[serviceName];
|
|
99
|
+
const query = `
|
|
100
|
+
query ${info.fieldName}($args: JSON) {
|
|
101
|
+
${info.fieldName}(args: $args)
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
const result = await executor(query, { args }, { nexusContext: context });
|
|
105
|
+
if (result.errors && result.errors.length > 0) {
|
|
106
|
+
throw new Error(result.errors[0].message);
|
|
107
|
+
}
|
|
108
|
+
return result.data;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=stitching.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stitching.js","sourceRoot":"","sources":["../src/stitching.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA+EH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,UAAU,aAAa,CAAC,IAA0B;IACtD,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,gBAAgB,EAAE,GAAG,IAAI,CAAC;IAEtE,6BAA6B;IAC7B,iEAAiE;IACjE,+CAA+C;IAE/C,OAAO,CAAC,IAAI,CACV,iFAAiF,EACjF,2DAA2D,CAC5D,CAAC;IAEF,kCAAkC;IAClC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,yDAAyD;IACzD,sBAAsB;IACtB,kDAAkD;IAClD,qDAAqD;IACrD,iDAAiD;IACjD,oCAAoC;IAEpC,OAAO,UAAU,CAAC,CAAC,CAAE,CAAC,MAAM,CAAC;AAC/B,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAGrC;IACC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAEnC,OAAO,KAAK,UAAU,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI;QAC/D,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QAC7D,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QAEtC,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,EAAE,CAAC,CAAC;QAC3D,CAAC;QAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG;cACJ,IAAI,CAAC,SAAS;UAClB,IAAI,CAAC,SAAS;;KAEnB,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,YAAY,EAAE,OAAkC,EAAE,CAAC,CAAC;QAErG,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,CAAC;QAC7C,CAAC;QAED,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC,CAAC;AACJ,CAAC"}
|