@soda-gql/core 0.11.25 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -18
- package/dist/adapter.d.cts +2 -2
- package/dist/adapter.d.ts +2 -2
- package/dist/{index-BcK5E-j7.d.ts → index-BnsQQUy6.d.cts} +618 -126
- package/dist/index-BnsQQUy6.d.cts.map +1 -0
- package/dist/{index-BJ6nWenl.d.ts → index-CcqI7_ms.d.ts} +10 -10
- package/dist/index-CcqI7_ms.d.ts.map +1 -0
- package/dist/{index-HucRBWMQ.d.cts → index-PnGDsBZE.d.cts} +10 -10
- package/dist/index-PnGDsBZE.d.cts.map +1 -0
- package/dist/{index-D8HbO9Y9.d.cts → index-nBIepz3b.d.ts} +618 -126
- package/dist/index-nBIepz3b.d.ts.map +1 -0
- package/dist/index.cjs +1526 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1502 -68
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.cts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/{schema-builder-DfC-aBUY.d.ts → schema-builder-B0DcWTQ-.d.ts} +2 -2
- package/dist/{schema-builder-DfC-aBUY.d.ts.map → schema-builder-B0DcWTQ-.d.ts.map} +1 -1
- package/dist/{schema-builder-DsXxY5Rn.d.cts → schema-builder-C7bceM7O.d.cts} +2 -2
- package/dist/{schema-builder-DsXxY5Rn.d.cts.map → schema-builder-C7bceM7O.d.cts.map} +1 -1
- package/package.json +8 -8
- package/dist/index-BJ6nWenl.d.ts.map +0 -1
- package/dist/index-BcK5E-j7.d.ts.map +0 -1
- package/dist/index-D8HbO9Y9.d.cts.map +0 -1
- package/dist/index-HucRBWMQ.d.cts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as applyContextTransformer } from "./context-transformer-sAzKGoyJ.js";
|
|
2
2
|
import { i as wrapByKey, n as defineOperationRoots, r as defineScalar, t as defineEnum } from "./schema-builder-oVVErGOB.js";
|
|
3
|
-
import { Kind, OperationTypeNode } from "graphql";
|
|
3
|
+
import { Kind, OperationTypeNode, parse } from "graphql";
|
|
4
4
|
|
|
5
5
|
//#region packages/core/src/types/type-foundation/var-ref.ts
|
|
6
6
|
var VarRef = class {
|
|
@@ -68,13 +68,15 @@ var DirectiveRef = class {
|
|
|
68
68
|
const INPUT_KIND_MAP = {
|
|
69
69
|
s: "scalar",
|
|
70
70
|
e: "enum",
|
|
71
|
-
i: "input"
|
|
71
|
+
i: "input",
|
|
72
|
+
x: "excluded"
|
|
72
73
|
};
|
|
73
74
|
const OUTPUT_KIND_MAP = {
|
|
74
75
|
s: "scalar",
|
|
75
76
|
e: "enum",
|
|
76
77
|
o: "object",
|
|
77
|
-
u: "union"
|
|
78
|
+
u: "union",
|
|
79
|
+
x: "excluded"
|
|
78
80
|
};
|
|
79
81
|
/**
|
|
80
82
|
* Parse a deferred input specifier string into a structured object.
|
|
@@ -91,7 +93,8 @@ function parseInputSpecifier(spec) {
|
|
|
91
93
|
const parts = spec.split("|");
|
|
92
94
|
const kindChar = parts[0];
|
|
93
95
|
const name = parts[1];
|
|
94
|
-
|
|
96
|
+
if (!kindChar || !name) throw new Error(`Invalid input specifier format: ${spec}`);
|
|
97
|
+
const modifier = hasDefault ? parts[2] ?? "" : parts.slice(2).join("|");
|
|
95
98
|
const kind = INPUT_KIND_MAP[kindChar];
|
|
96
99
|
if (!kind) throw new Error(`Invalid input specifier kind: ${kindChar}`);
|
|
97
100
|
return {
|
|
@@ -115,6 +118,7 @@ function parseOutputSpecifier(spec) {
|
|
|
115
118
|
const kindChar = parts[0];
|
|
116
119
|
const name = parts[1];
|
|
117
120
|
const modifier = parts[2];
|
|
121
|
+
if (!kindChar || !name || modifier === void 0) throw new Error(`Invalid output specifier format: ${spec}`);
|
|
118
122
|
const kind = OUTPUT_KIND_MAP[kindChar];
|
|
119
123
|
if (!kind) throw new Error(`Invalid output specifier kind: ${kindChar}`);
|
|
120
124
|
return {
|
|
@@ -368,6 +372,14 @@ const buildUnionSelection = (union, schema) => {
|
|
|
368
372
|
* @param typeName - Parent type name (required for shorthand expansion)
|
|
369
373
|
*/
|
|
370
374
|
const buildField = (fields, schema, typeName) => Object.entries(fields).map(([alias, value]) => {
|
|
375
|
+
if (alias === "__typename" && isShorthand(value)) return {
|
|
376
|
+
kind: Kind.FIELD,
|
|
377
|
+
name: {
|
|
378
|
+
kind: Kind.NAME,
|
|
379
|
+
value: "__typename"
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
if (isShorthand(value) && !typeName) throw new Error(`typeName is required for shorthand expansion of field "${alias}"`);
|
|
371
383
|
const { args, field, object, union, directives, type } = isShorthand(value) ? expandShorthand(schema, typeName, alias) : value;
|
|
372
384
|
const parsedType = parseOutputField(type);
|
|
373
385
|
const builtDirectives = buildDirectives(directives, "FIELD", schema);
|
|
@@ -634,6 +646,16 @@ const createColocateHelper = () => {
|
|
|
634
646
|
return $colocate;
|
|
635
647
|
};
|
|
636
648
|
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region packages/core/src/types/element/compat-spec.ts
|
|
651
|
+
/**
|
|
652
|
+
* Type guard to distinguish TemplateCompatSpec from CompatSpec at runtime.
|
|
653
|
+
* Uses structural discrimination (presence of `graphqlSource` field).
|
|
654
|
+
*/
|
|
655
|
+
const isTemplateCompatSpec = (spec) => {
|
|
656
|
+
return "graphqlSource" in spec && !("fieldsBuilder" in spec);
|
|
657
|
+
};
|
|
658
|
+
|
|
637
659
|
//#endregion
|
|
638
660
|
//#region packages/core/src/utils/promise.ts
|
|
639
661
|
/**
|
|
@@ -998,6 +1020,46 @@ const createCompatComposer = (schema, operationType) => {
|
|
|
998
1020
|
};
|
|
999
1021
|
};
|
|
1000
1022
|
|
|
1023
|
+
//#endregion
|
|
1024
|
+
//#region packages/core/src/composer/compat-tagged-template.ts
|
|
1025
|
+
/**
|
|
1026
|
+
* Compat tagged template function for creating deferred GraphQL operation specs.
|
|
1027
|
+
* Callback builder compat path is in compat.ts.
|
|
1028
|
+
* @module
|
|
1029
|
+
*/
|
|
1030
|
+
/**
|
|
1031
|
+
* Creates a curried tagged template function for compat mode operations.
|
|
1032
|
+
* New API: `query.compat("name")\`($vars) { fields }\`` returns GqlDefine<TemplateCompatSpec>.
|
|
1033
|
+
*
|
|
1034
|
+
* @param schema - The GraphQL schema definition
|
|
1035
|
+
* @param operationType - The operation type (query, mutation, subscription)
|
|
1036
|
+
*/
|
|
1037
|
+
const createCompatTaggedTemplate = (schema, operationType) => {
|
|
1038
|
+
if (schema.operations[operationType] === null) throw new Error(`Operation type ${operationType} is not defined in schema roots`);
|
|
1039
|
+
return (operationName) => {
|
|
1040
|
+
return (strings, ...values) => {
|
|
1041
|
+
if (values.length > 0) throw new Error("Tagged templates must not contain interpolated expressions");
|
|
1042
|
+
const source = `${operationType} ${operationName} ${(strings[0] ?? "").trim()}`;
|
|
1043
|
+
let document;
|
|
1044
|
+
try {
|
|
1045
|
+
document = parse(source);
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1048
|
+
throw new Error(`GraphQL parse error in tagged template: ${message}`);
|
|
1049
|
+
}
|
|
1050
|
+
const opDefs = document.definitions.filter((def) => def.kind === Kind.OPERATION_DEFINITION);
|
|
1051
|
+
if (opDefs.length !== 1) throw new Error(`Internal error: expected exactly one operation definition in synthesized source`);
|
|
1052
|
+
if (opDefs[0].kind !== Kind.OPERATION_DEFINITION) throw new Error("Unexpected definition kind");
|
|
1053
|
+
return GqlDefine.create(() => ({
|
|
1054
|
+
schema,
|
|
1055
|
+
operationType,
|
|
1056
|
+
operationName,
|
|
1057
|
+
graphqlSource: source
|
|
1058
|
+
}));
|
|
1059
|
+
};
|
|
1060
|
+
};
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1001
1063
|
//#endregion
|
|
1002
1064
|
//#region packages/core/src/composer/directive-builder.ts
|
|
1003
1065
|
/**
|
|
@@ -1104,6 +1166,981 @@ const isDirectiveRef = (value) => {
|
|
|
1104
1166
|
return value instanceof DirectiveRef;
|
|
1105
1167
|
};
|
|
1106
1168
|
|
|
1169
|
+
//#endregion
|
|
1170
|
+
//#region packages/core/src/graphql/fragment-args-preprocessor.ts
|
|
1171
|
+
/**
|
|
1172
|
+
* Find the matching closing parenthesis for a balanced group.
|
|
1173
|
+
* Handles nested parentheses, string literals, and comments.
|
|
1174
|
+
* Returns the index of the closing ')' or -1 if not found.
|
|
1175
|
+
*/
|
|
1176
|
+
const findMatchingParen = (content, openIndex) => {
|
|
1177
|
+
let depth = 1;
|
|
1178
|
+
let inString = false;
|
|
1179
|
+
for (let i = openIndex + 1; i < content.length; i++) {
|
|
1180
|
+
const ch = content[i];
|
|
1181
|
+
if (inString) {
|
|
1182
|
+
if (ch === inString) {
|
|
1183
|
+
let backslashes = 0;
|
|
1184
|
+
for (let j = i - 1; j >= 0 && content[j] === "\\"; j--) backslashes++;
|
|
1185
|
+
if (backslashes % 2 === 0) inString = false;
|
|
1186
|
+
}
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
if (ch === "\"" || ch === "'") {
|
|
1190
|
+
inString = ch;
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
if (ch === "(") depth++;
|
|
1194
|
+
else if (ch === ")") {
|
|
1195
|
+
depth--;
|
|
1196
|
+
if (depth === 0) return i;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return -1;
|
|
1200
|
+
};
|
|
1201
|
+
/**
|
|
1202
|
+
* Replace a range [start, end] (inclusive) with spaces, preserving newlines.
|
|
1203
|
+
*/
|
|
1204
|
+
const replaceWithSpaces = (content, start, end) => {
|
|
1205
|
+
let result = content.slice(0, start);
|
|
1206
|
+
for (let i = start; i <= end; i++) result += content[i] === "\n" ? "\n" : " ";
|
|
1207
|
+
result += content.slice(end + 1);
|
|
1208
|
+
return result;
|
|
1209
|
+
};
|
|
1210
|
+
const FRAGMENT_DEF_PATTERN = /\bfragment\s+(\w+)\s*\(/g;
|
|
1211
|
+
const FRAGMENT_SPREAD_PATTERN = /\.\.\.(\w+)\s*\(/g;
|
|
1212
|
+
/**
|
|
1213
|
+
* Preprocess Fragment Arguments RFC syntax by replacing argument lists with spaces.
|
|
1214
|
+
*
|
|
1215
|
+
* Transforms:
|
|
1216
|
+
* - `fragment UserProfile($showEmail: Boolean = false) on User` -> `fragment UserProfile on User`
|
|
1217
|
+
* - `...UserProfile(showEmail: true)` -> `...UserProfile `
|
|
1218
|
+
*/
|
|
1219
|
+
const preprocessFragmentArgs = (content) => {
|
|
1220
|
+
let result = content;
|
|
1221
|
+
let modified = false;
|
|
1222
|
+
let match;
|
|
1223
|
+
FRAGMENT_DEF_PATTERN.lastIndex = 0;
|
|
1224
|
+
while ((match = FRAGMENT_DEF_PATTERN.exec(result)) !== null) {
|
|
1225
|
+
const openParenIndex = match.index + match[0].length - 1;
|
|
1226
|
+
const closeParenIndex = findMatchingParen(result, openParenIndex);
|
|
1227
|
+
if (closeParenIndex === -1) continue;
|
|
1228
|
+
if (!result.slice(closeParenIndex + 1).trimStart().startsWith("on")) continue;
|
|
1229
|
+
result = replaceWithSpaces(result, openParenIndex, closeParenIndex);
|
|
1230
|
+
modified = true;
|
|
1231
|
+
FRAGMENT_DEF_PATTERN.lastIndex = 0;
|
|
1232
|
+
}
|
|
1233
|
+
FRAGMENT_SPREAD_PATTERN.lastIndex = 0;
|
|
1234
|
+
while ((match = FRAGMENT_SPREAD_PATTERN.exec(result)) !== null) {
|
|
1235
|
+
const openParenIndex = match.index + match[0].length - 1;
|
|
1236
|
+
const closeParenIndex = findMatchingParen(result, openParenIndex);
|
|
1237
|
+
if (closeParenIndex === -1) continue;
|
|
1238
|
+
result = replaceWithSpaces(result, openParenIndex, closeParenIndex);
|
|
1239
|
+
modified = true;
|
|
1240
|
+
FRAGMENT_SPREAD_PATTERN.lastIndex = 0;
|
|
1241
|
+
}
|
|
1242
|
+
return {
|
|
1243
|
+
preprocessed: result,
|
|
1244
|
+
modified
|
|
1245
|
+
};
|
|
1246
|
+
};
|
|
1247
|
+
|
|
1248
|
+
//#endregion
|
|
1249
|
+
//#region packages/core/src/graphql/result.ts
|
|
1250
|
+
/** Create a successful Result */
|
|
1251
|
+
const ok = (value) => ({
|
|
1252
|
+
ok: true,
|
|
1253
|
+
value
|
|
1254
|
+
});
|
|
1255
|
+
/** Create a failed Result */
|
|
1256
|
+
const err = (error) => ({
|
|
1257
|
+
ok: false,
|
|
1258
|
+
error
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
//#endregion
|
|
1262
|
+
//#region packages/core/src/graphql/parser.ts
|
|
1263
|
+
/**
|
|
1264
|
+
* GraphQL parser utilities.
|
|
1265
|
+
* Extracts operations and fragments from GraphQL source strings.
|
|
1266
|
+
* @module
|
|
1267
|
+
*/
|
|
1268
|
+
/** Parse GraphQL source string directly. No file I/O. */
|
|
1269
|
+
const parseGraphqlSource = (source, sourceFile) => {
|
|
1270
|
+
try {
|
|
1271
|
+
const document = parse(source);
|
|
1272
|
+
return ok({
|
|
1273
|
+
document,
|
|
1274
|
+
...extractFromDocument(document, sourceFile)
|
|
1275
|
+
});
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
return err({
|
|
1278
|
+
code: "GRAPHQL_PARSE_ERROR",
|
|
1279
|
+
message: `GraphQL parse error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1280
|
+
filePath: sourceFile
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
1284
|
+
/**
|
|
1285
|
+
* Parse a GraphQL TypeNode into type name and modifier.
|
|
1286
|
+
*
|
|
1287
|
+
* Format: inner nullability + list modifiers
|
|
1288
|
+
* - Inner: `!` (non-null) or `?` (nullable)
|
|
1289
|
+
* - List: `[]!` (non-null list) or `[]?` (nullable list)
|
|
1290
|
+
*/
|
|
1291
|
+
const parseTypeNode = (node) => {
|
|
1292
|
+
const levels = [];
|
|
1293
|
+
const collect = (n, nonNull) => {
|
|
1294
|
+
if (n.kind === Kind.NON_NULL_TYPE) return collect(n.type, true);
|
|
1295
|
+
if (n.kind === Kind.LIST_TYPE) {
|
|
1296
|
+
levels.push({
|
|
1297
|
+
kind: "list",
|
|
1298
|
+
nonNull
|
|
1299
|
+
});
|
|
1300
|
+
return collect(n.type, false);
|
|
1301
|
+
}
|
|
1302
|
+
levels.push({
|
|
1303
|
+
kind: "named",
|
|
1304
|
+
nonNull
|
|
1305
|
+
});
|
|
1306
|
+
return n.name.value;
|
|
1307
|
+
};
|
|
1308
|
+
const typeName = collect(node, false);
|
|
1309
|
+
let modifier = "?";
|
|
1310
|
+
for (const level of levels.slice().reverse()) {
|
|
1311
|
+
if (level.kind === "named") {
|
|
1312
|
+
modifier = level.nonNull ? "!" : "?";
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
const listSuffix = level.nonNull ? "[]!" : "[]?";
|
|
1316
|
+
modifier = `${modifier}${listSuffix}`;
|
|
1317
|
+
}
|
|
1318
|
+
return {
|
|
1319
|
+
typeName,
|
|
1320
|
+
modifier
|
|
1321
|
+
};
|
|
1322
|
+
};
|
|
1323
|
+
/** Extract operations and fragments from a parsed GraphQL document. */
|
|
1324
|
+
const extractFromDocument = (document, sourceFile) => {
|
|
1325
|
+
const operations = [];
|
|
1326
|
+
const fragments = [];
|
|
1327
|
+
for (const definition of document.definitions) if (definition.kind === Kind.OPERATION_DEFINITION) {
|
|
1328
|
+
const operation = extractOperation(definition, sourceFile);
|
|
1329
|
+
if (operation) operations.push(operation);
|
|
1330
|
+
} else if (definition.kind === Kind.FRAGMENT_DEFINITION) fragments.push(extractFragment(definition, sourceFile));
|
|
1331
|
+
return {
|
|
1332
|
+
operations,
|
|
1333
|
+
fragments
|
|
1334
|
+
};
|
|
1335
|
+
};
|
|
1336
|
+
/** Extract a single operation from an OperationDefinitionNode. */
|
|
1337
|
+
const extractOperation = (node, sourceFile) => {
|
|
1338
|
+
if (!node.name) return null;
|
|
1339
|
+
const variables = (node.variableDefinitions ?? []).map(extractVariable);
|
|
1340
|
+
const selections = extractSelections(node.selectionSet.selections);
|
|
1341
|
+
return {
|
|
1342
|
+
kind: node.operation,
|
|
1343
|
+
name: node.name.value,
|
|
1344
|
+
variables,
|
|
1345
|
+
selections,
|
|
1346
|
+
sourceFile
|
|
1347
|
+
};
|
|
1348
|
+
};
|
|
1349
|
+
/** Extract a fragment from a FragmentDefinitionNode. */
|
|
1350
|
+
const extractFragment = (node, sourceFile) => {
|
|
1351
|
+
const selections = extractSelections(node.selectionSet.selections);
|
|
1352
|
+
return {
|
|
1353
|
+
name: node.name.value,
|
|
1354
|
+
onType: node.typeCondition.name.value,
|
|
1355
|
+
selections,
|
|
1356
|
+
sourceFile
|
|
1357
|
+
};
|
|
1358
|
+
};
|
|
1359
|
+
/** Extract a variable definition. */
|
|
1360
|
+
const extractVariable = (node) => {
|
|
1361
|
+
const { typeName, modifier } = parseTypeNode(node.type);
|
|
1362
|
+
const defaultValue = node.defaultValue ? extractValue(node.defaultValue) : void 0;
|
|
1363
|
+
return {
|
|
1364
|
+
name: node.variable.name.value,
|
|
1365
|
+
typeName,
|
|
1366
|
+
modifier,
|
|
1367
|
+
typeKind: "scalar",
|
|
1368
|
+
defaultValue
|
|
1369
|
+
};
|
|
1370
|
+
};
|
|
1371
|
+
/** Extract selections from a SelectionSet. */
|
|
1372
|
+
const extractSelections = (selections) => {
|
|
1373
|
+
return selections.map(extractSelection);
|
|
1374
|
+
};
|
|
1375
|
+
/** Extract a single selection. */
|
|
1376
|
+
const extractSelection = (node) => {
|
|
1377
|
+
switch (node.kind) {
|
|
1378
|
+
case Kind.FIELD: return extractFieldSelection(node);
|
|
1379
|
+
case Kind.FRAGMENT_SPREAD: return extractFragmentSpread(node);
|
|
1380
|
+
case Kind.INLINE_FRAGMENT: return extractInlineFragment(node);
|
|
1381
|
+
}
|
|
1382
|
+
};
|
|
1383
|
+
/** Extract a field selection. */
|
|
1384
|
+
const extractFieldSelection = (node) => {
|
|
1385
|
+
const args = node.arguments?.length ? node.arguments.map(extractArgument) : void 0;
|
|
1386
|
+
const selections = node.selectionSet ? extractSelections(node.selectionSet.selections) : void 0;
|
|
1387
|
+
return {
|
|
1388
|
+
kind: "field",
|
|
1389
|
+
name: node.name.value,
|
|
1390
|
+
alias: node.alias?.value,
|
|
1391
|
+
arguments: args,
|
|
1392
|
+
selections
|
|
1393
|
+
};
|
|
1394
|
+
};
|
|
1395
|
+
/** Extract a fragment spread. */
|
|
1396
|
+
const extractFragmentSpread = (node) => {
|
|
1397
|
+
return {
|
|
1398
|
+
kind: "fragmentSpread",
|
|
1399
|
+
name: node.name.value
|
|
1400
|
+
};
|
|
1401
|
+
};
|
|
1402
|
+
/** Extract an inline fragment. */
|
|
1403
|
+
const extractInlineFragment = (node) => {
|
|
1404
|
+
return {
|
|
1405
|
+
kind: "inlineFragment",
|
|
1406
|
+
onType: node.typeCondition?.name.value ?? "",
|
|
1407
|
+
selections: extractSelections(node.selectionSet.selections)
|
|
1408
|
+
};
|
|
1409
|
+
};
|
|
1410
|
+
/** Extract an argument. */
|
|
1411
|
+
const extractArgument = (node) => {
|
|
1412
|
+
return {
|
|
1413
|
+
name: node.name.value,
|
|
1414
|
+
value: extractValue(node.value)
|
|
1415
|
+
};
|
|
1416
|
+
};
|
|
1417
|
+
/** Assert unreachable code path (for exhaustiveness checks). */
|
|
1418
|
+
const assertUnreachable = (value) => {
|
|
1419
|
+
throw new Error(`Unexpected value: ${JSON.stringify(value)}`);
|
|
1420
|
+
};
|
|
1421
|
+
/** Extract a value (literal or variable reference). */
|
|
1422
|
+
const extractValue = (node) => {
|
|
1423
|
+
switch (node.kind) {
|
|
1424
|
+
case Kind.VARIABLE: return {
|
|
1425
|
+
kind: "variable",
|
|
1426
|
+
name: node.name.value
|
|
1427
|
+
};
|
|
1428
|
+
case Kind.INT: return {
|
|
1429
|
+
kind: "int",
|
|
1430
|
+
value: node.value
|
|
1431
|
+
};
|
|
1432
|
+
case Kind.FLOAT: return {
|
|
1433
|
+
kind: "float",
|
|
1434
|
+
value: node.value
|
|
1435
|
+
};
|
|
1436
|
+
case Kind.STRING: return {
|
|
1437
|
+
kind: "string",
|
|
1438
|
+
value: node.value
|
|
1439
|
+
};
|
|
1440
|
+
case Kind.BOOLEAN: return {
|
|
1441
|
+
kind: "boolean",
|
|
1442
|
+
value: node.value
|
|
1443
|
+
};
|
|
1444
|
+
case Kind.NULL: return { kind: "null" };
|
|
1445
|
+
case Kind.ENUM: return {
|
|
1446
|
+
kind: "enum",
|
|
1447
|
+
value: node.value
|
|
1448
|
+
};
|
|
1449
|
+
case Kind.LIST: return {
|
|
1450
|
+
kind: "list",
|
|
1451
|
+
values: node.values.map(extractValue)
|
|
1452
|
+
};
|
|
1453
|
+
case Kind.OBJECT: return {
|
|
1454
|
+
kind: "object",
|
|
1455
|
+
fields: node.fields.map((field) => ({
|
|
1456
|
+
name: field.name.value,
|
|
1457
|
+
value: extractValue(field.value)
|
|
1458
|
+
}))
|
|
1459
|
+
};
|
|
1460
|
+
default: return assertUnreachable(node);
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
|
|
1464
|
+
//#endregion
|
|
1465
|
+
//#region packages/core/src/graphql/schema-adapter.ts
|
|
1466
|
+
/**
|
|
1467
|
+
* Create a minimal SchemaIndex from AnyGraphqlSchema.
|
|
1468
|
+
*
|
|
1469
|
+
* IMPORTANT: This adapter produces a "name-resolution only" SchemaIndex.
|
|
1470
|
+
* Only the name-level Maps are populated (.has() lookups work).
|
|
1471
|
+
* Field-level data (FieldDefinitionNode, InputValueDefinitionNode, etc.)
|
|
1472
|
+
* is NOT populated -- those Maps are empty.
|
|
1473
|
+
*
|
|
1474
|
+
* Use this when you need SchemaIndex for type kind resolution only
|
|
1475
|
+
* (e.g., buildVarSpecifier). For full SchemaIndex with field-level data,
|
|
1476
|
+
* use createSchemaIndex(DocumentNode) from schema-index.ts.
|
|
1477
|
+
*/
|
|
1478
|
+
const createSchemaIndexFromSchema = (schema) => {
|
|
1479
|
+
const scalars = new Map(Object.keys(schema.scalar).map((n) => [n, {
|
|
1480
|
+
name: n,
|
|
1481
|
+
directives: []
|
|
1482
|
+
}]));
|
|
1483
|
+
const enums = new Map(Object.keys(schema.enum).map((n) => [n, {
|
|
1484
|
+
name: n,
|
|
1485
|
+
values: /* @__PURE__ */ new Map(),
|
|
1486
|
+
directives: []
|
|
1487
|
+
}]));
|
|
1488
|
+
const inputs = new Map(Object.keys(schema.input).map((n) => [n, {
|
|
1489
|
+
name: n,
|
|
1490
|
+
fields: /* @__PURE__ */ new Map(),
|
|
1491
|
+
directives: []
|
|
1492
|
+
}]));
|
|
1493
|
+
return {
|
|
1494
|
+
objects: new Map(Object.keys(schema.object).map((n) => [n, {
|
|
1495
|
+
name: n,
|
|
1496
|
+
fields: /* @__PURE__ */ new Map(),
|
|
1497
|
+
directives: []
|
|
1498
|
+
}])),
|
|
1499
|
+
inputs,
|
|
1500
|
+
enums,
|
|
1501
|
+
unions: new Map(Object.keys(schema.union).map((n) => [n, {
|
|
1502
|
+
name: n,
|
|
1503
|
+
members: /* @__PURE__ */ new Map(),
|
|
1504
|
+
directives: []
|
|
1505
|
+
}])),
|
|
1506
|
+
scalars,
|
|
1507
|
+
directives: /* @__PURE__ */ new Map(),
|
|
1508
|
+
operationTypes: {
|
|
1509
|
+
query: schema.operations.query ?? void 0,
|
|
1510
|
+
mutation: schema.operations.mutation ?? void 0,
|
|
1511
|
+
subscription: schema.operations.subscription ?? void 0
|
|
1512
|
+
}
|
|
1513
|
+
};
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1516
|
+
//#endregion
|
|
1517
|
+
//#region packages/core/src/graphql/schema-index.ts
|
|
1518
|
+
const ensureRecord = (collection, key, factory) => {
|
|
1519
|
+
const existing = collection.get(key);
|
|
1520
|
+
if (existing) return existing;
|
|
1521
|
+
const created = factory(key);
|
|
1522
|
+
collection.set(key, created);
|
|
1523
|
+
return created;
|
|
1524
|
+
};
|
|
1525
|
+
const addObjectFields = (target, fields) => {
|
|
1526
|
+
if (!fields) return;
|
|
1527
|
+
for (const field of fields) target.set(field.name.value, field);
|
|
1528
|
+
};
|
|
1529
|
+
const addInputFields = (target, fields) => {
|
|
1530
|
+
if (!fields) return;
|
|
1531
|
+
for (const field of fields) target.set(field.name.value, field);
|
|
1532
|
+
};
|
|
1533
|
+
const addEnumValues = (target, values) => {
|
|
1534
|
+
if (!values) return;
|
|
1535
|
+
for (const value of values) target.set(value.name.value, value);
|
|
1536
|
+
};
|
|
1537
|
+
const addUnionMembers = (target, members) => {
|
|
1538
|
+
if (!members) return;
|
|
1539
|
+
for (const member of members) target.set(member.name.value, member);
|
|
1540
|
+
};
|
|
1541
|
+
const mergeDirectives = (existing, incoming, precedence) => {
|
|
1542
|
+
const current = existing ?? [];
|
|
1543
|
+
const next = incoming ? Array.from(incoming) : [];
|
|
1544
|
+
return precedence === "definition" ? [...next, ...current] : [...current, ...next];
|
|
1545
|
+
};
|
|
1546
|
+
const updateOperationTypes = (operationTypes, definition) => {
|
|
1547
|
+
for (const operation of definition.operationTypes ?? []) {
|
|
1548
|
+
const typeName = operation.type.name.value;
|
|
1549
|
+
switch (operation.operation) {
|
|
1550
|
+
case "query":
|
|
1551
|
+
operationTypes.query = typeName;
|
|
1552
|
+
break;
|
|
1553
|
+
case "mutation":
|
|
1554
|
+
operationTypes.mutation = typeName;
|
|
1555
|
+
break;
|
|
1556
|
+
case "subscription":
|
|
1557
|
+
operationTypes.subscription = typeName;
|
|
1558
|
+
break;
|
|
1559
|
+
default: break;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
const addDirectiveArgs = (target, args) => {
|
|
1564
|
+
if (!args) return;
|
|
1565
|
+
for (const arg of args) target.set(arg.name.value, arg);
|
|
1566
|
+
};
|
|
1567
|
+
/** Build a schema index from a parsed GraphQL schema document. */
|
|
1568
|
+
const createSchemaIndex = (document) => {
|
|
1569
|
+
const objects = /* @__PURE__ */ new Map();
|
|
1570
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
1571
|
+
const enums = /* @__PURE__ */ new Map();
|
|
1572
|
+
const unions = /* @__PURE__ */ new Map();
|
|
1573
|
+
const scalars = /* @__PURE__ */ new Map();
|
|
1574
|
+
const directives = /* @__PURE__ */ new Map();
|
|
1575
|
+
const operationTypes = {};
|
|
1576
|
+
for (const definition of document.definitions) switch (definition.kind) {
|
|
1577
|
+
case Kind.OBJECT_TYPE_DEFINITION:
|
|
1578
|
+
case Kind.OBJECT_TYPE_EXTENSION: {
|
|
1579
|
+
const precedence = definition.kind === Kind.OBJECT_TYPE_DEFINITION ? "definition" : "extension";
|
|
1580
|
+
const record = ensureRecord(objects, definition.name.value, (name) => ({
|
|
1581
|
+
name,
|
|
1582
|
+
fields: /* @__PURE__ */ new Map(),
|
|
1583
|
+
directives: []
|
|
1584
|
+
}));
|
|
1585
|
+
addObjectFields(record.fields, definition.fields);
|
|
1586
|
+
record.directives = mergeDirectives(record.directives, definition.directives, precedence);
|
|
1587
|
+
break;
|
|
1588
|
+
}
|
|
1589
|
+
case Kind.INPUT_OBJECT_TYPE_DEFINITION:
|
|
1590
|
+
case Kind.INPUT_OBJECT_TYPE_EXTENSION: {
|
|
1591
|
+
const precedence = definition.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION ? "definition" : "extension";
|
|
1592
|
+
const record = ensureRecord(inputs, definition.name.value, (name) => ({
|
|
1593
|
+
name,
|
|
1594
|
+
fields: /* @__PURE__ */ new Map(),
|
|
1595
|
+
directives: []
|
|
1596
|
+
}));
|
|
1597
|
+
addInputFields(record.fields, definition.fields);
|
|
1598
|
+
record.directives = mergeDirectives(record.directives, definition.directives, precedence);
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1601
|
+
case Kind.ENUM_TYPE_DEFINITION:
|
|
1602
|
+
case Kind.ENUM_TYPE_EXTENSION: {
|
|
1603
|
+
const precedence = definition.kind === Kind.ENUM_TYPE_DEFINITION ? "definition" : "extension";
|
|
1604
|
+
const record = ensureRecord(enums, definition.name.value, (name) => ({
|
|
1605
|
+
name,
|
|
1606
|
+
values: /* @__PURE__ */ new Map(),
|
|
1607
|
+
directives: []
|
|
1608
|
+
}));
|
|
1609
|
+
addEnumValues(record.values, definition.values);
|
|
1610
|
+
record.directives = mergeDirectives(record.directives, definition.directives, precedence);
|
|
1611
|
+
break;
|
|
1612
|
+
}
|
|
1613
|
+
case Kind.UNION_TYPE_DEFINITION:
|
|
1614
|
+
case Kind.UNION_TYPE_EXTENSION: {
|
|
1615
|
+
const precedence = definition.kind === Kind.UNION_TYPE_DEFINITION ? "definition" : "extension";
|
|
1616
|
+
const record = ensureRecord(unions, definition.name.value, (name) => ({
|
|
1617
|
+
name,
|
|
1618
|
+
members: /* @__PURE__ */ new Map(),
|
|
1619
|
+
directives: []
|
|
1620
|
+
}));
|
|
1621
|
+
addUnionMembers(record.members, definition.types);
|
|
1622
|
+
record.directives = mergeDirectives(record.directives, definition.directives, precedence);
|
|
1623
|
+
break;
|
|
1624
|
+
}
|
|
1625
|
+
case Kind.SCALAR_TYPE_DEFINITION:
|
|
1626
|
+
case Kind.SCALAR_TYPE_EXTENSION: {
|
|
1627
|
+
const precedence = definition.kind === Kind.SCALAR_TYPE_DEFINITION ? "definition" : "extension";
|
|
1628
|
+
const record = ensureRecord(scalars, definition.name.value, (name) => ({
|
|
1629
|
+
name,
|
|
1630
|
+
directives: []
|
|
1631
|
+
}));
|
|
1632
|
+
record.directives = mergeDirectives(record.directives, definition.directives, precedence);
|
|
1633
|
+
break;
|
|
1634
|
+
}
|
|
1635
|
+
case Kind.DIRECTIVE_DEFINITION: {
|
|
1636
|
+
const name = definition.name.value;
|
|
1637
|
+
if (name === "skip" || name === "include" || name === "deprecated" || name === "specifiedBy") break;
|
|
1638
|
+
const args = /* @__PURE__ */ new Map();
|
|
1639
|
+
addDirectiveArgs(args, definition.arguments);
|
|
1640
|
+
directives.set(name, {
|
|
1641
|
+
name,
|
|
1642
|
+
locations: definition.locations.map((loc) => loc.value),
|
|
1643
|
+
args,
|
|
1644
|
+
isRepeatable: definition.repeatable
|
|
1645
|
+
});
|
|
1646
|
+
break;
|
|
1647
|
+
}
|
|
1648
|
+
case Kind.SCHEMA_DEFINITION:
|
|
1649
|
+
case Kind.SCHEMA_EXTENSION:
|
|
1650
|
+
updateOperationTypes(operationTypes, definition);
|
|
1651
|
+
break;
|
|
1652
|
+
default: break;
|
|
1653
|
+
}
|
|
1654
|
+
if (!operationTypes.query && objects.has("Query")) operationTypes.query = "Query";
|
|
1655
|
+
if (!operationTypes.mutation && objects.has("Mutation")) operationTypes.mutation = "Mutation";
|
|
1656
|
+
if (!operationTypes.subscription && objects.has("Subscription")) operationTypes.subscription = "Subscription";
|
|
1657
|
+
return {
|
|
1658
|
+
objects,
|
|
1659
|
+
inputs,
|
|
1660
|
+
enums,
|
|
1661
|
+
unions,
|
|
1662
|
+
scalars,
|
|
1663
|
+
directives,
|
|
1664
|
+
operationTypes
|
|
1665
|
+
};
|
|
1666
|
+
};
|
|
1667
|
+
|
|
1668
|
+
//#endregion
|
|
1669
|
+
//#region packages/core/src/graphql/transformer.ts
|
|
1670
|
+
const builtinScalarTypes$1 = new Set([
|
|
1671
|
+
"ID",
|
|
1672
|
+
"String",
|
|
1673
|
+
"Int",
|
|
1674
|
+
"Float",
|
|
1675
|
+
"Boolean"
|
|
1676
|
+
]);
|
|
1677
|
+
const parseModifierStructure = (modifier) => {
|
|
1678
|
+
const inner = modifier[0] === "!" ? "!" : "?";
|
|
1679
|
+
const lists = [];
|
|
1680
|
+
const listPattern = /\[\]([!?])/g;
|
|
1681
|
+
let match;
|
|
1682
|
+
while ((match = listPattern.exec(modifier)) !== null) lists.push(`[]${match[1]}`);
|
|
1683
|
+
return {
|
|
1684
|
+
inner,
|
|
1685
|
+
lists
|
|
1686
|
+
};
|
|
1687
|
+
};
|
|
1688
|
+
const buildModifier = (structure) => {
|
|
1689
|
+
return structure.inner + structure.lists.join("");
|
|
1690
|
+
};
|
|
1691
|
+
const isModifierAssignable = (source, target) => {
|
|
1692
|
+
const srcStruct = parseModifierStructure(source);
|
|
1693
|
+
const tgtStruct = parseModifierStructure(target);
|
|
1694
|
+
const depthDiff = tgtStruct.lists.length - srcStruct.lists.length;
|
|
1695
|
+
if (depthDiff < 0 || depthDiff > 1) return false;
|
|
1696
|
+
const tgtListsToCompare = depthDiff === 1 ? tgtStruct.lists.slice(1) : tgtStruct.lists;
|
|
1697
|
+
if (depthDiff === 1 && srcStruct.lists.length === 0 && srcStruct.inner === "?" && tgtStruct.lists[0] === "[]!") return false;
|
|
1698
|
+
if (srcStruct.inner === "?" && tgtStruct.inner === "!") return false;
|
|
1699
|
+
for (let i = 0; i < srcStruct.lists.length; i++) {
|
|
1700
|
+
const srcList = srcStruct.lists[i];
|
|
1701
|
+
const tgtList = tgtListsToCompare[i];
|
|
1702
|
+
if (srcList === "[]?" && tgtList === "[]!") return false;
|
|
1703
|
+
}
|
|
1704
|
+
return true;
|
|
1705
|
+
};
|
|
1706
|
+
const deriveMinimumModifier = (expectedModifier) => {
|
|
1707
|
+
const struct = parseModifierStructure(expectedModifier);
|
|
1708
|
+
if (struct.lists.length > 0) return buildModifier({
|
|
1709
|
+
inner: struct.inner,
|
|
1710
|
+
lists: struct.lists.slice(1)
|
|
1711
|
+
});
|
|
1712
|
+
return expectedModifier;
|
|
1713
|
+
};
|
|
1714
|
+
const mergeModifiers = (a, b) => {
|
|
1715
|
+
const structA = parseModifierStructure(a);
|
|
1716
|
+
const structB = parseModifierStructure(b);
|
|
1717
|
+
if (structA.lists.length !== structB.lists.length) return {
|
|
1718
|
+
ok: false,
|
|
1719
|
+
reason: `Incompatible list depths: "${a}" has ${structA.lists.length} list level(s), "${b}" has ${structB.lists.length}`
|
|
1720
|
+
};
|
|
1721
|
+
const mergedInner = structA.inner === "!" || structB.inner === "!" ? "!" : "?";
|
|
1722
|
+
const mergedLists = [];
|
|
1723
|
+
for (let i = 0; i < structA.lists.length; i++) {
|
|
1724
|
+
const listA = structA.lists[i];
|
|
1725
|
+
const listB = structB.lists[i];
|
|
1726
|
+
mergedLists.push(listA === "[]!" || listB === "[]!" ? "[]!" : "[]?");
|
|
1727
|
+
}
|
|
1728
|
+
return {
|
|
1729
|
+
ok: true,
|
|
1730
|
+
value: buildModifier({
|
|
1731
|
+
inner: mergedInner,
|
|
1732
|
+
lists: mergedLists
|
|
1733
|
+
})
|
|
1734
|
+
};
|
|
1735
|
+
};
|
|
1736
|
+
const getArgumentType = (schema, parentTypeName, fieldName, argumentName) => {
|
|
1737
|
+
const objectRecord = schema.objects.get(parentTypeName);
|
|
1738
|
+
if (!objectRecord) return null;
|
|
1739
|
+
const fieldDef = objectRecord.fields.get(fieldName);
|
|
1740
|
+
if (!fieldDef) return null;
|
|
1741
|
+
const argDef = fieldDef.arguments?.find((arg) => arg.name.value === argumentName);
|
|
1742
|
+
if (!argDef) return null;
|
|
1743
|
+
return parseTypeNode(argDef.type);
|
|
1744
|
+
};
|
|
1745
|
+
const getInputFieldType = (schema, inputTypeName, fieldName) => {
|
|
1746
|
+
const inputRecord = schema.inputs.get(inputTypeName);
|
|
1747
|
+
if (!inputRecord) return null;
|
|
1748
|
+
const fieldDef = inputRecord.fields.get(fieldName);
|
|
1749
|
+
if (!fieldDef) return null;
|
|
1750
|
+
return parseTypeNode(fieldDef.type);
|
|
1751
|
+
};
|
|
1752
|
+
const resolveTypeKindFromName = (schema, typeName) => {
|
|
1753
|
+
if (isScalarName(schema, typeName)) return "scalar";
|
|
1754
|
+
if (isEnumName(schema, typeName)) return "enum";
|
|
1755
|
+
if (schema.inputs.has(typeName)) return "input";
|
|
1756
|
+
return null;
|
|
1757
|
+
};
|
|
1758
|
+
const collectVariablesFromValue = (value, expectedTypeName, expectedModifier, schema, usages) => {
|
|
1759
|
+
if (value.kind === "variable") {
|
|
1760
|
+
const typeKind = resolveTypeKindFromName(schema, expectedTypeName);
|
|
1761
|
+
if (!typeKind) return {
|
|
1762
|
+
code: "GRAPHQL_UNKNOWN_TYPE",
|
|
1763
|
+
message: `Unknown type "${expectedTypeName}" for variable "$${value.name}"`,
|
|
1764
|
+
typeName: expectedTypeName
|
|
1765
|
+
};
|
|
1766
|
+
usages.push({
|
|
1767
|
+
name: value.name,
|
|
1768
|
+
typeName: expectedTypeName,
|
|
1769
|
+
expectedModifier,
|
|
1770
|
+
minimumModifier: deriveMinimumModifier(expectedModifier),
|
|
1771
|
+
typeKind
|
|
1772
|
+
});
|
|
1773
|
+
return null;
|
|
1774
|
+
}
|
|
1775
|
+
if (value.kind === "object") {
|
|
1776
|
+
for (const field of value.fields) {
|
|
1777
|
+
const fieldType = getInputFieldType(schema, expectedTypeName, field.name);
|
|
1778
|
+
if (!fieldType) return {
|
|
1779
|
+
code: "GRAPHQL_UNKNOWN_FIELD",
|
|
1780
|
+
message: `Unknown field "${field.name}" on input type "${expectedTypeName}"`,
|
|
1781
|
+
typeName: expectedTypeName,
|
|
1782
|
+
fieldName: field.name
|
|
1783
|
+
};
|
|
1784
|
+
const error = collectVariablesFromValue(field.value, fieldType.typeName, fieldType.modifier, schema, usages);
|
|
1785
|
+
if (error) return error;
|
|
1786
|
+
}
|
|
1787
|
+
return null;
|
|
1788
|
+
}
|
|
1789
|
+
if (value.kind === "list") {
|
|
1790
|
+
const struct = parseModifierStructure(expectedModifier);
|
|
1791
|
+
if (struct.lists.length > 0) {
|
|
1792
|
+
const innerModifier = buildModifier({
|
|
1793
|
+
inner: struct.inner,
|
|
1794
|
+
lists: struct.lists.slice(1)
|
|
1795
|
+
});
|
|
1796
|
+
for (const item of value.values) {
|
|
1797
|
+
const error = collectVariablesFromValue(item, expectedTypeName, innerModifier, schema, usages);
|
|
1798
|
+
if (error) return error;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
return null;
|
|
1803
|
+
};
|
|
1804
|
+
const collectVariablesFromArguments = (args, parentTypeName, fieldName, schema, usages) => {
|
|
1805
|
+
for (const arg of args) {
|
|
1806
|
+
const argType = getArgumentType(schema, parentTypeName, fieldName, arg.name);
|
|
1807
|
+
if (!argType) return {
|
|
1808
|
+
code: "GRAPHQL_UNKNOWN_ARGUMENT",
|
|
1809
|
+
message: `Unknown argument "${arg.name}" on field "${fieldName}"`,
|
|
1810
|
+
fieldName,
|
|
1811
|
+
argumentName: arg.name
|
|
1812
|
+
};
|
|
1813
|
+
const error = collectVariablesFromValue(arg.value, argType.typeName, argType.modifier, schema, usages);
|
|
1814
|
+
if (error) return error;
|
|
1815
|
+
}
|
|
1816
|
+
return null;
|
|
1817
|
+
};
|
|
1818
|
+
const collectVariableUsages = (selections, parentTypeName, schema) => {
|
|
1819
|
+
const usages = [];
|
|
1820
|
+
const collect = (sels, parentType) => {
|
|
1821
|
+
for (const sel of sels) switch (sel.kind) {
|
|
1822
|
+
case "field":
|
|
1823
|
+
if (sel.arguments && sel.arguments.length > 0) {
|
|
1824
|
+
const error$1 = collectVariablesFromArguments(sel.arguments, parentType, sel.name, schema, usages);
|
|
1825
|
+
if (error$1) return error$1;
|
|
1826
|
+
}
|
|
1827
|
+
if (sel.selections && sel.selections.length > 0) {
|
|
1828
|
+
const fieldReturnType = getFieldReturnType(schema, parentType, sel.name);
|
|
1829
|
+
if (!fieldReturnType) return {
|
|
1830
|
+
code: "GRAPHQL_UNKNOWN_FIELD",
|
|
1831
|
+
message: `Unknown field "${sel.name}" on type "${parentType}"`,
|
|
1832
|
+
typeName: parentType,
|
|
1833
|
+
fieldName: sel.name
|
|
1834
|
+
};
|
|
1835
|
+
const error$1 = collect(sel.selections, fieldReturnType);
|
|
1836
|
+
if (error$1) return error$1;
|
|
1837
|
+
}
|
|
1838
|
+
break;
|
|
1839
|
+
case "inlineFragment": {
|
|
1840
|
+
const error$1 = collect(sel.selections, sel.onType);
|
|
1841
|
+
if (error$1) return error$1;
|
|
1842
|
+
break;
|
|
1843
|
+
}
|
|
1844
|
+
case "fragmentSpread": break;
|
|
1845
|
+
}
|
|
1846
|
+
return null;
|
|
1847
|
+
};
|
|
1848
|
+
const error = collect(selections, parentTypeName);
|
|
1849
|
+
if (error) return err(error);
|
|
1850
|
+
return ok(usages);
|
|
1851
|
+
};
|
|
1852
|
+
const getFieldReturnType = (schema, parentTypeName, fieldName) => {
|
|
1853
|
+
const objectRecord = schema.objects.get(parentTypeName);
|
|
1854
|
+
if (!objectRecord) return null;
|
|
1855
|
+
const fieldDef = objectRecord.fields.get(fieldName);
|
|
1856
|
+
if (!fieldDef) return null;
|
|
1857
|
+
const { typeName } = parseTypeNode(fieldDef.type);
|
|
1858
|
+
return typeName;
|
|
1859
|
+
};
|
|
1860
|
+
const mergeVariableUsages = (variableName, usages) => {
|
|
1861
|
+
if (usages.length === 0) return err({
|
|
1862
|
+
code: "GRAPHQL_UNDECLARED_VARIABLE",
|
|
1863
|
+
message: `No usages found for variable "${variableName}"`,
|
|
1864
|
+
variableName
|
|
1865
|
+
});
|
|
1866
|
+
const first = usages[0];
|
|
1867
|
+
for (const usage of usages) if (usage.typeName !== first.typeName) return err({
|
|
1868
|
+
code: "GRAPHQL_VARIABLE_TYPE_MISMATCH",
|
|
1869
|
+
message: `Variable "$${variableName}" has conflicting types: "${first.typeName}" and "${usage.typeName}"`,
|
|
1870
|
+
variableName
|
|
1871
|
+
});
|
|
1872
|
+
let candidateModifier = first.minimumModifier;
|
|
1873
|
+
for (let i = 1; i < usages.length; i++) {
|
|
1874
|
+
const result = mergeModifiers(candidateModifier, usages[i].minimumModifier);
|
|
1875
|
+
if (!result.ok) return err({
|
|
1876
|
+
code: "GRAPHQL_VARIABLE_MODIFIER_INCOMPATIBLE",
|
|
1877
|
+
message: `Variable "$${variableName}" has incompatible modifiers: ${result.reason}`,
|
|
1878
|
+
variableName
|
|
1879
|
+
});
|
|
1880
|
+
candidateModifier = result.value;
|
|
1881
|
+
}
|
|
1882
|
+
for (const usage of usages) if (!isModifierAssignable(candidateModifier, usage.expectedModifier)) return err({
|
|
1883
|
+
code: "GRAPHQL_VARIABLE_MODIFIER_INCOMPATIBLE",
|
|
1884
|
+
message: `Variable "$${variableName}" with modifier "${candidateModifier}" cannot satisfy expected "${usage.expectedModifier}"`,
|
|
1885
|
+
variableName
|
|
1886
|
+
});
|
|
1887
|
+
return ok({
|
|
1888
|
+
name: variableName,
|
|
1889
|
+
typeName: first.typeName,
|
|
1890
|
+
modifier: candidateModifier,
|
|
1891
|
+
typeKind: first.typeKind
|
|
1892
|
+
});
|
|
1893
|
+
};
|
|
1894
|
+
const inferVariablesFromUsages = (usages) => {
|
|
1895
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1896
|
+
for (const usage of usages) {
|
|
1897
|
+
const existing = byName.get(usage.name);
|
|
1898
|
+
if (existing) existing.push(usage);
|
|
1899
|
+
else byName.set(usage.name, [usage]);
|
|
1900
|
+
}
|
|
1901
|
+
const variables = [];
|
|
1902
|
+
for (const [name, group] of byName) {
|
|
1903
|
+
const result = mergeVariableUsages(name, group);
|
|
1904
|
+
if (!result.ok) return err(result.error);
|
|
1905
|
+
variables.push(result.value);
|
|
1906
|
+
}
|
|
1907
|
+
variables.sort((a, b) => a.name.localeCompare(b.name));
|
|
1908
|
+
return ok(variables);
|
|
1909
|
+
};
|
|
1910
|
+
const isScalarName = (schema, name) => builtinScalarTypes$1.has(name) || schema.scalars.has(name);
|
|
1911
|
+
const sortFragmentsByDependency = (fragments) => {
|
|
1912
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1913
|
+
for (const fragment of fragments) {
|
|
1914
|
+
const deps = collectFragmentDependenciesSet(fragment.selections);
|
|
1915
|
+
graph.set(fragment.name, deps);
|
|
1916
|
+
}
|
|
1917
|
+
const fragmentByName = /* @__PURE__ */ new Map();
|
|
1918
|
+
for (const f of fragments) fragmentByName.set(f.name, f);
|
|
1919
|
+
const sorted = [];
|
|
1920
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1921
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1922
|
+
const visit = (name, path) => {
|
|
1923
|
+
if (visited.has(name)) return null;
|
|
1924
|
+
if (visiting.has(name)) {
|
|
1925
|
+
const cycleStart = path.indexOf(name);
|
|
1926
|
+
const cycle = path.slice(cycleStart).concat(name);
|
|
1927
|
+
return {
|
|
1928
|
+
code: "GRAPHQL_FRAGMENT_CIRCULAR_DEPENDENCY",
|
|
1929
|
+
message: `Circular dependency detected in fragments: ${cycle.join(" -> ")}`,
|
|
1930
|
+
fragmentNames: cycle
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
const fragment = fragmentByName.get(name);
|
|
1934
|
+
if (!fragment) {
|
|
1935
|
+
visited.add(name);
|
|
1936
|
+
return null;
|
|
1937
|
+
}
|
|
1938
|
+
visiting.add(name);
|
|
1939
|
+
const deps = graph.get(name) ?? /* @__PURE__ */ new Set();
|
|
1940
|
+
for (const dep of deps) {
|
|
1941
|
+
const error = visit(dep, [...path, name]);
|
|
1942
|
+
if (error) return error;
|
|
1943
|
+
}
|
|
1944
|
+
visiting.delete(name);
|
|
1945
|
+
visited.add(name);
|
|
1946
|
+
sorted.push(fragment);
|
|
1947
|
+
return null;
|
|
1948
|
+
};
|
|
1949
|
+
for (const fragment of fragments) {
|
|
1950
|
+
const error = visit(fragment.name, []);
|
|
1951
|
+
if (error) return err(error);
|
|
1952
|
+
}
|
|
1953
|
+
return ok(sorted);
|
|
1954
|
+
};
|
|
1955
|
+
const collectFragmentDependenciesSet = (selections) => {
|
|
1956
|
+
const deps = /* @__PURE__ */ new Set();
|
|
1957
|
+
const collect = (sels) => {
|
|
1958
|
+
for (const sel of sels) switch (sel.kind) {
|
|
1959
|
+
case "fragmentSpread":
|
|
1960
|
+
deps.add(sel.name);
|
|
1961
|
+
break;
|
|
1962
|
+
case "field":
|
|
1963
|
+
if (sel.selections) collect(sel.selections);
|
|
1964
|
+
break;
|
|
1965
|
+
case "inlineFragment":
|
|
1966
|
+
collect(sel.selections);
|
|
1967
|
+
break;
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
collect(selections);
|
|
1971
|
+
return deps;
|
|
1972
|
+
};
|
|
1973
|
+
const isEnumName = (schema, name) => schema.enums.has(name);
|
|
1974
|
+
const transformParsedGraphql = (parsed, options) => {
|
|
1975
|
+
const schema = createSchemaIndex(options.schemaDocument);
|
|
1976
|
+
const sortResult = sortFragmentsByDependency(parsed.fragments);
|
|
1977
|
+
if (!sortResult.ok) return err(sortResult.error);
|
|
1978
|
+
const sortedFragments = sortResult.value;
|
|
1979
|
+
const resolvedFragmentVariables = /* @__PURE__ */ new Map();
|
|
1980
|
+
const fragments = [];
|
|
1981
|
+
for (const frag of sortedFragments) {
|
|
1982
|
+
const result = transformFragment(frag, schema, resolvedFragmentVariables);
|
|
1983
|
+
if (!result.ok) return err(result.error);
|
|
1984
|
+
resolvedFragmentVariables.set(frag.name, result.value.variables);
|
|
1985
|
+
fragments.push(result.value);
|
|
1986
|
+
}
|
|
1987
|
+
const operations = [];
|
|
1988
|
+
for (const op of parsed.operations) {
|
|
1989
|
+
const result = transformOperation(op, schema);
|
|
1990
|
+
if (!result.ok) return err(result.error);
|
|
1991
|
+
operations.push(result.value);
|
|
1992
|
+
}
|
|
1993
|
+
return ok({
|
|
1994
|
+
operations,
|
|
1995
|
+
fragments
|
|
1996
|
+
});
|
|
1997
|
+
};
|
|
1998
|
+
const transformOperation = (op, schema) => {
|
|
1999
|
+
const variables = [];
|
|
2000
|
+
for (const v of op.variables) {
|
|
2001
|
+
const typeKind = resolveTypeKind$1(schema, v.typeName);
|
|
2002
|
+
if (typeKind === null) return err({
|
|
2003
|
+
code: "GRAPHQL_UNKNOWN_TYPE",
|
|
2004
|
+
message: `Unknown type "${v.typeName}" in variable "${v.name}"`,
|
|
2005
|
+
typeName: v.typeName
|
|
2006
|
+
});
|
|
2007
|
+
variables.push({
|
|
2008
|
+
...v,
|
|
2009
|
+
typeKind
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
const fragmentDependencies = collectFragmentDependencies(op.selections);
|
|
2013
|
+
return ok({
|
|
2014
|
+
...op,
|
|
2015
|
+
variables,
|
|
2016
|
+
fragmentDependencies
|
|
2017
|
+
});
|
|
2018
|
+
};
|
|
2019
|
+
const transformFragment = (frag, schema, resolvedFragmentVariables) => {
|
|
2020
|
+
const fragmentDependencies = collectFragmentDependencies(frag.selections);
|
|
2021
|
+
const directUsagesResult = collectVariableUsages(frag.selections, frag.onType, schema);
|
|
2022
|
+
if (!directUsagesResult.ok) return err(directUsagesResult.error);
|
|
2023
|
+
const directUsages = directUsagesResult.value;
|
|
2024
|
+
const spreadVariables = [];
|
|
2025
|
+
for (const depName of fragmentDependencies) {
|
|
2026
|
+
const depVariables = resolvedFragmentVariables.get(depName);
|
|
2027
|
+
if (depVariables) spreadVariables.push(...depVariables);
|
|
2028
|
+
}
|
|
2029
|
+
const variablesResult = inferVariablesFromUsages([...directUsages, ...spreadVariables.map((v) => ({
|
|
2030
|
+
name: v.name,
|
|
2031
|
+
typeName: v.typeName,
|
|
2032
|
+
expectedModifier: v.modifier,
|
|
2033
|
+
minimumModifier: v.modifier,
|
|
2034
|
+
typeKind: v.typeKind
|
|
2035
|
+
}))]);
|
|
2036
|
+
if (!variablesResult.ok) return err(variablesResult.error);
|
|
2037
|
+
return ok({
|
|
2038
|
+
...frag,
|
|
2039
|
+
fragmentDependencies,
|
|
2040
|
+
variables: variablesResult.value
|
|
2041
|
+
});
|
|
2042
|
+
};
|
|
2043
|
+
const resolveTypeKind$1 = (schema, typeName) => {
|
|
2044
|
+
if (isScalarName(schema, typeName)) return "scalar";
|
|
2045
|
+
if (isEnumName(schema, typeName)) return "enum";
|
|
2046
|
+
if (schema.inputs.has(typeName)) return "input";
|
|
2047
|
+
return null;
|
|
2048
|
+
};
|
|
2049
|
+
const collectFragmentDependencies = (selections) => {
|
|
2050
|
+
const fragments = /* @__PURE__ */ new Set();
|
|
2051
|
+
const collect = (sels) => {
|
|
2052
|
+
for (const sel of sels) switch (sel.kind) {
|
|
2053
|
+
case "fragmentSpread":
|
|
2054
|
+
fragments.add(sel.name);
|
|
2055
|
+
break;
|
|
2056
|
+
case "field":
|
|
2057
|
+
if (sel.selections) collect(sel.selections);
|
|
2058
|
+
break;
|
|
2059
|
+
case "inlineFragment":
|
|
2060
|
+
collect(sel.selections);
|
|
2061
|
+
break;
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
collect(selections);
|
|
2065
|
+
return [...fragments];
|
|
2066
|
+
};
|
|
2067
|
+
|
|
2068
|
+
//#endregion
|
|
2069
|
+
//#region packages/core/src/graphql/var-specifier-builder.ts
|
|
2070
|
+
/**
|
|
2071
|
+
* Converts VariableDefinitionNode from graphql-js AST into VarSpecifier objects
|
|
2072
|
+
* compatible with the composer's GenericVarSpecifier type.
|
|
2073
|
+
*
|
|
2074
|
+
* Uses throw (not Result) because it will be called from the composer layer.
|
|
2075
|
+
* @module
|
|
2076
|
+
*/
|
|
2077
|
+
const builtinScalarTypes = new Set([
|
|
2078
|
+
"ID",
|
|
2079
|
+
"String",
|
|
2080
|
+
"Int",
|
|
2081
|
+
"Float",
|
|
2082
|
+
"Boolean"
|
|
2083
|
+
]);
|
|
2084
|
+
const resolveTypeKind = (schema, typeName) => {
|
|
2085
|
+
if (builtinScalarTypes.has(typeName) || schema.scalars.has(typeName)) return "scalar";
|
|
2086
|
+
if (schema.enums.has(typeName)) return "enum";
|
|
2087
|
+
if (schema.inputs.has(typeName)) return "input";
|
|
2088
|
+
throw new Error(`Cannot resolve type kind for "${typeName}": not found in schema as scalar, enum, or input`);
|
|
2089
|
+
};
|
|
2090
|
+
/**
|
|
2091
|
+
* Extract a constant value from a ValueNode (for default values).
|
|
2092
|
+
* Similar to graphql-js valueFromAST but without type coercion.
|
|
2093
|
+
*/
|
|
2094
|
+
const extractConstValue = (node) => {
|
|
2095
|
+
switch (node.kind) {
|
|
2096
|
+
case Kind.INT: return Number.parseInt(node.value, 10);
|
|
2097
|
+
case Kind.FLOAT: return Number.parseFloat(node.value);
|
|
2098
|
+
case Kind.STRING: return node.value;
|
|
2099
|
+
case Kind.BOOLEAN: return node.value;
|
|
2100
|
+
case Kind.NULL: return null;
|
|
2101
|
+
case Kind.ENUM: return node.value;
|
|
2102
|
+
case Kind.LIST: return node.values.map(extractConstValue);
|
|
2103
|
+
case Kind.OBJECT: {
|
|
2104
|
+
const obj = {};
|
|
2105
|
+
for (const field of node.fields) obj[field.name.value] = extractConstValue(field.value);
|
|
2106
|
+
return obj;
|
|
2107
|
+
}
|
|
2108
|
+
case Kind.VARIABLE: throw new Error("Variable references are not allowed in default values");
|
|
2109
|
+
default: throw new Error(`Unexpected value kind: ${node.kind}`);
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
2112
|
+
const extractDefaultValue = (node) => {
|
|
2113
|
+
if (!node.defaultValue) return null;
|
|
2114
|
+
return { default: extractConstValue(node.defaultValue) };
|
|
2115
|
+
};
|
|
2116
|
+
/**
|
|
2117
|
+
* Convert a VariableDefinitionNode to a VarSpecifier.
|
|
2118
|
+
* Resolves `kind` (scalar/enum/input) from the schema index.
|
|
2119
|
+
*
|
|
2120
|
+
* @throws Error if type name cannot be resolved in schema
|
|
2121
|
+
*/
|
|
2122
|
+
const buildVarSpecifier = (node, schema) => {
|
|
2123
|
+
const { typeName, modifier } = parseTypeNode(node.type);
|
|
2124
|
+
return {
|
|
2125
|
+
kind: resolveTypeKind(schema, typeName),
|
|
2126
|
+
name: typeName,
|
|
2127
|
+
modifier,
|
|
2128
|
+
defaultValue: extractDefaultValue(node),
|
|
2129
|
+
directives: {}
|
|
2130
|
+
};
|
|
2131
|
+
};
|
|
2132
|
+
/**
|
|
2133
|
+
* Convert all variable definitions from a list of VariableDefinitionNodes
|
|
2134
|
+
* into a record keyed by variable name.
|
|
2135
|
+
*
|
|
2136
|
+
* @throws Error if any type name cannot be resolved in schema
|
|
2137
|
+
*/
|
|
2138
|
+
const buildVarSpecifiers = (nodes, schema) => {
|
|
2139
|
+
const result = {};
|
|
2140
|
+
for (const node of nodes) result[node.variable.name.value] = buildVarSpecifier(node, schema);
|
|
2141
|
+
return result;
|
|
2142
|
+
};
|
|
2143
|
+
|
|
1107
2144
|
//#endregion
|
|
1108
2145
|
//#region packages/core/src/types/metadata/adapter.ts
|
|
1109
2146
|
/**
|
|
@@ -1369,6 +2406,11 @@ const createVarRefs = (definitions) => mapValues(definitions, (_, name) => creat
|
|
|
1369
2406
|
//#endregion
|
|
1370
2407
|
//#region packages/core/src/composer/operation-core.ts
|
|
1371
2408
|
/**
|
|
2409
|
+
* Core operation building logic shared by operation and extend composers.
|
|
2410
|
+
* @module
|
|
2411
|
+
* @internal
|
|
2412
|
+
*/
|
|
2413
|
+
/**
|
|
1372
2414
|
* Builds an operation artifact from the provided parameters.
|
|
1373
2415
|
*
|
|
1374
2416
|
* This function contains the core logic for:
|
|
@@ -1381,25 +2423,39 @@ const createVarRefs = (definitions) => mapValues(definitions, (_, name) => creat
|
|
|
1381
2423
|
* @param params - Operation building parameters
|
|
1382
2424
|
* @returns Operation artifact or Promise of artifact (if async metadata)
|
|
1383
2425
|
*
|
|
1384
|
-
* @internal Used by operation.ts and
|
|
2426
|
+
* @internal Used by operation.ts, extend.ts, and operation-tagged-template.ts
|
|
1385
2427
|
*/
|
|
1386
2428
|
const buildOperationArtifact = (params) => {
|
|
1387
|
-
const { schema, operationType, operationTypeName, operationName, variables,
|
|
2429
|
+
const { schema, operationType, operationTypeName, operationName, variables, adapter, metadata: metadataBuilder, transformDocument: operationTransformDocument, adapterTransformDocument } = params;
|
|
1388
2430
|
const $ = createVarRefs(variables);
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
schema
|
|
1401
|
-
|
|
1402
|
-
|
|
2431
|
+
let document;
|
|
2432
|
+
let variableNames;
|
|
2433
|
+
let fields;
|
|
2434
|
+
let fragmentUsages;
|
|
2435
|
+
if (params.mode === "prebuilt") {
|
|
2436
|
+
document = params.prebuiltDocument;
|
|
2437
|
+
variableNames = params.prebuiltVariableNames ?? Object.keys(variables);
|
|
2438
|
+
fields = {};
|
|
2439
|
+
fragmentUsages = [];
|
|
2440
|
+
} else {
|
|
2441
|
+
const { fieldsFactory } = params;
|
|
2442
|
+
const f = createFieldFactories(schema, operationTypeName);
|
|
2443
|
+
const collected = withFragmentUsageCollection(() => fieldsFactory({
|
|
2444
|
+
f,
|
|
2445
|
+
$
|
|
2446
|
+
}));
|
|
2447
|
+
fields = collected.result;
|
|
2448
|
+
fragmentUsages = collected.usages;
|
|
2449
|
+
document = buildDocument({
|
|
2450
|
+
operationName,
|
|
2451
|
+
operationType,
|
|
2452
|
+
operationTypeName,
|
|
2453
|
+
variables,
|
|
2454
|
+
fields,
|
|
2455
|
+
schema
|
|
2456
|
+
});
|
|
2457
|
+
variableNames = Object.keys(variables);
|
|
2458
|
+
}
|
|
1403
2459
|
if (!fragmentUsages.some((u) => u.metadataBuilder) && !metadataBuilder && !adapterTransformDocument && !operationTransformDocument) return {
|
|
1404
2460
|
operationType,
|
|
1405
2461
|
operationName,
|
|
@@ -1463,6 +2519,34 @@ const buildOperationArtifact = (params) => {
|
|
|
1463
2519
|
if (isPromiseLike(operationMetadataResult)) return operationMetadataResult.then((metadata) => createArtifact({ metadata }));
|
|
1464
2520
|
return createArtifact({ metadata: operationMetadataResult });
|
|
1465
2521
|
};
|
|
2522
|
+
/**
|
|
2523
|
+
* Wraps a buildOperationArtifact call into an Operation.create() invocation,
|
|
2524
|
+
* handling both sync and async artifact results.
|
|
2525
|
+
*
|
|
2526
|
+
* @param artifactFactory - Factory that produces the artifact (may return Promise for async metadata)
|
|
2527
|
+
* @param overrideDocumentSource - When true, overrides documentSource to return empty object.
|
|
2528
|
+
* Required for pre-built document mode where fields = {} and the real documentSource is meaningless.
|
|
2529
|
+
* Must be false for fieldsFactory mode to preserve real field selections.
|
|
2530
|
+
*
|
|
2531
|
+
* @internal Used by extend.ts and operation-tagged-template.ts
|
|
2532
|
+
*/
|
|
2533
|
+
const wrapArtifactAsOperation = (artifactFactory, overrideDocumentSource) => {
|
|
2534
|
+
return Operation.create((() => {
|
|
2535
|
+
const artifact = artifactFactory();
|
|
2536
|
+
if (overrideDocumentSource) {
|
|
2537
|
+
if (isPromiseLike(artifact)) return artifact.then((a) => ({
|
|
2538
|
+
...a,
|
|
2539
|
+
documentSource: () => ({})
|
|
2540
|
+
}));
|
|
2541
|
+
return {
|
|
2542
|
+
...artifact,
|
|
2543
|
+
documentSource: () => ({})
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
if (isPromiseLike(artifact)) return artifact;
|
|
2547
|
+
return artifact;
|
|
2548
|
+
}));
|
|
2549
|
+
};
|
|
1466
2550
|
|
|
1467
2551
|
//#endregion
|
|
1468
2552
|
//#region packages/core/src/composer/extend.ts
|
|
@@ -1486,9 +2570,12 @@ const buildOperationArtifact = (params) => {
|
|
|
1486
2570
|
const createExtendComposer = (schema, adapter, transformDocument) => {
|
|
1487
2571
|
const resolvedAdapter = adapter ?? defaultMetadataAdapter;
|
|
1488
2572
|
return (compat, options) => {
|
|
1489
|
-
const
|
|
2573
|
+
const spec = compat.value;
|
|
2574
|
+
if (isTemplateCompatSpec(spec)) return buildOperationFromTemplateSpec(schema, spec, resolvedAdapter, options, transformDocument);
|
|
2575
|
+
const { operationType, operationName, variables, fieldsBuilder } = spec;
|
|
1490
2576
|
const operationTypeName = schema.operations[operationType];
|
|
1491
2577
|
return Operation.create((() => buildOperationArtifact({
|
|
2578
|
+
mode: "fieldsFactory",
|
|
1492
2579
|
schema,
|
|
1493
2580
|
operationType,
|
|
1494
2581
|
operationTypeName,
|
|
@@ -1502,52 +2589,298 @@ const createExtendComposer = (schema, adapter, transformDocument) => {
|
|
|
1502
2589
|
})));
|
|
1503
2590
|
};
|
|
1504
2591
|
};
|
|
2592
|
+
/**
|
|
2593
|
+
* Builds an Operation from a TemplateCompatSpec by parsing the raw GraphQL source.
|
|
2594
|
+
* Delegates to buildOperationArtifact with pre-built document mode.
|
|
2595
|
+
*/
|
|
2596
|
+
const buildOperationFromTemplateSpec = (schema, spec, adapter, options, adapterTransformDocument) => {
|
|
2597
|
+
const { operationType, operationName, graphqlSource } = spec;
|
|
2598
|
+
const document = parse(graphqlSource);
|
|
2599
|
+
const opDef = document.definitions.find((d) => d.kind === Kind.OPERATION_DEFINITION);
|
|
2600
|
+
if (!opDef) throw new Error("No operation definition found in compat template spec");
|
|
2601
|
+
const schemaIndex = createSchemaIndexFromSchema(spec.schema);
|
|
2602
|
+
const varSpecifiers = buildVarSpecifiers(opDef.variableDefinitions ?? [], schemaIndex);
|
|
2603
|
+
const operationTypeName = schema.operations[operationType];
|
|
2604
|
+
return wrapArtifactAsOperation(() => buildOperationArtifact({
|
|
2605
|
+
mode: "prebuilt",
|
|
2606
|
+
schema,
|
|
2607
|
+
operationType,
|
|
2608
|
+
operationTypeName,
|
|
2609
|
+
operationName,
|
|
2610
|
+
variables: varSpecifiers,
|
|
2611
|
+
prebuiltDocument: document,
|
|
2612
|
+
prebuiltVariableNames: Object.keys(varSpecifiers),
|
|
2613
|
+
adapter,
|
|
2614
|
+
metadata: options?.metadata,
|
|
2615
|
+
transformDocument: options?.transformDocument,
|
|
2616
|
+
adapterTransformDocument
|
|
2617
|
+
}), true);
|
|
2618
|
+
};
|
|
1505
2619
|
|
|
1506
2620
|
//#endregion
|
|
1507
|
-
//#region packages/core/src/composer/
|
|
2621
|
+
//#region packages/core/src/composer/merge-variable-definitions.ts
|
|
1508
2622
|
/**
|
|
1509
|
-
*
|
|
2623
|
+
* Shared utility for merging variable definitions from interpolated fragments.
|
|
2624
|
+
* Used by both fragment and operation tagged template implementations.
|
|
1510
2625
|
* @module
|
|
1511
2626
|
*/
|
|
1512
2627
|
/**
|
|
1513
|
-
*
|
|
1514
|
-
*
|
|
1515
|
-
* Returns an object with a builder for each type (e.g., `fragment.User`, `fragment.Post`).
|
|
1516
|
-
* Each builder creates a `Fragment` that can be spread into operations.
|
|
1517
|
-
*
|
|
1518
|
-
* @param schema - The GraphQL schema definition
|
|
1519
|
-
* @param _adapter - Optional metadata adapter (for fragment metadata)
|
|
1520
|
-
* @returns Object mapping type names to fragment builder functions
|
|
1521
|
-
*
|
|
1522
|
-
* @internal Used by `createGqlElementComposer`
|
|
2628
|
+
* Merge variable definitions from interpolated fragments into the parent's variable definitions.
|
|
2629
|
+
* Deduplicates variables with matching names and types, throws on conflicting types.
|
|
1523
2630
|
*/
|
|
1524
|
-
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
2631
|
+
function mergeVariableDefinitions(parentVars, interpolationMap) {
|
|
2632
|
+
const merged = { ...parentVars };
|
|
2633
|
+
for (const value of interpolationMap.values()) if (value instanceof Fragment) {
|
|
2634
|
+
const childVars = value.variableDefinitions;
|
|
2635
|
+
for (const [varName, varDef] of Object.entries(childVars)) if (varName in merged) {
|
|
2636
|
+
const existing = merged[varName];
|
|
2637
|
+
if (existing?.kind !== varDef.kind || existing?.name !== varDef.name || existing?.modifier !== varDef.modifier) throw new Error(`Variable definition conflict: $${varName} is defined with incompatible types (${existing?.kind}:${existing?.name}:${existing?.modifier} vs ${varDef.kind}:${varDef.name}:${varDef.modifier})`);
|
|
2638
|
+
} else merged[varName] = varDef;
|
|
2639
|
+
}
|
|
2640
|
+
return merged;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
//#endregion
|
|
2644
|
+
//#region packages/core/src/composer/fragment-tagged-template.ts
|
|
2645
|
+
/**
|
|
2646
|
+
* Fragment tagged template function for creating GraphQL fragments from template literals.
|
|
2647
|
+
* Supports Fragment Arguments RFC syntax for parameterized fragments.
|
|
2648
|
+
* @module
|
|
2649
|
+
*/
|
|
2650
|
+
/**
|
|
2651
|
+
* Extract the argument list text from a fragment definition with Fragment Arguments syntax.
|
|
2652
|
+
* Returns the text between parens in `fragment Name(...) on Type`, or null if no args.
|
|
2653
|
+
*/
|
|
2654
|
+
function extractFragmentArgText(rawSource) {
|
|
2655
|
+
const match = /\bfragment\s+\w+\s*\(/.exec(rawSource);
|
|
2656
|
+
if (!match) return null;
|
|
2657
|
+
const openIndex = match.index + match[0].length - 1;
|
|
2658
|
+
const closeIndex = findMatchingParen(rawSource, openIndex);
|
|
2659
|
+
if (closeIndex === -1) return null;
|
|
2660
|
+
if (rawSource.slice(closeIndex + 1).trimStart().startsWith("on")) return rawSource.slice(openIndex + 1, closeIndex);
|
|
2661
|
+
return null;
|
|
2662
|
+
}
|
|
2663
|
+
/**
|
|
2664
|
+
* Extract variable definitions from Fragment Arguments syntax.
|
|
2665
|
+
* Wraps the argument list in a synthetic query to parse with graphql-js.
|
|
2666
|
+
*/
|
|
2667
|
+
function extractFragmentVariables(rawSource, schemaIndex) {
|
|
2668
|
+
const argText = extractFragmentArgText(rawSource);
|
|
2669
|
+
if (!argText?.trim()) return {};
|
|
2670
|
+
const syntheticQuery = `query _Synthetic(${argText}) { __typename }`;
|
|
2671
|
+
let syntheticDoc;
|
|
2672
|
+
try {
|
|
2673
|
+
syntheticDoc = parse(syntheticQuery);
|
|
2674
|
+
} catch (error) {
|
|
2675
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2676
|
+
throw new Error(`Failed to parse fragment argument definitions: ${message}`);
|
|
2677
|
+
}
|
|
2678
|
+
const opDef = syntheticDoc.definitions[0];
|
|
2679
|
+
if (!opDef || opDef.kind !== Kind.OPERATION_DEFINITION) return {};
|
|
2680
|
+
return buildVarSpecifiers(opDef.variableDefinitions ?? [], schemaIndex);
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* Builds field selections from a GraphQL AST SelectionSet by driving field factories.
|
|
2684
|
+
* Converts parsed AST selections into the AnyFieldsExtended format that the document builder expects.
|
|
2685
|
+
* Also used by typegen for static field extraction from tagged templates.
|
|
2686
|
+
*/
|
|
2687
|
+
function buildFieldsFromSelectionSet(selectionSet, schema, typeName, varAssignments, interpolationMap) {
|
|
2688
|
+
const f = createFieldFactories(schema, typeName);
|
|
2689
|
+
const result = {};
|
|
2690
|
+
for (const selection of selectionSet.selections) if (selection.kind === Kind.FIELD) {
|
|
2691
|
+
const fieldName = selection.name.value;
|
|
2692
|
+
const alias = selection.alias?.value ?? fieldName;
|
|
2693
|
+
if (fieldName === "__typename") {
|
|
2694
|
+
result[alias] = true;
|
|
2695
|
+
continue;
|
|
2696
|
+
}
|
|
2697
|
+
const factory = f[fieldName];
|
|
2698
|
+
if (!factory) throw new Error(`Field "${fieldName}" is not defined on type "${typeName}"`);
|
|
2699
|
+
const args = buildArgsFromASTArguments(selection.arguments ?? [], varAssignments);
|
|
2700
|
+
const extras = alias !== fieldName ? { alias } : void 0;
|
|
2701
|
+
if (selection.selectionSet) {
|
|
2702
|
+
const curried = factory(args, extras);
|
|
2703
|
+
if (typeof curried === "function") {
|
|
2704
|
+
const fieldSpec = schema.object[typeName]?.fields[fieldName];
|
|
2705
|
+
const parsedType = parseOutputField(fieldSpec);
|
|
2706
|
+
if (parsedType.kind === "union") {
|
|
2707
|
+
const unionInput = {};
|
|
2708
|
+
let hasTypename = false;
|
|
2709
|
+
const unsupportedSelections = [];
|
|
2710
|
+
for (const sel of selection.selectionSet.selections) if (sel.kind === Kind.INLINE_FRAGMENT) {
|
|
2711
|
+
if (sel.directives?.length) throw new Error("Directives on inline fragments are not supported in tagged templates");
|
|
2712
|
+
if (!sel.typeCondition) throw new Error("Inline fragments without type conditions are not supported in tagged templates");
|
|
2713
|
+
const memberName = sel.typeCondition.name.value;
|
|
2714
|
+
if (!schema.union[parsedType.name]?.types[memberName]) throw new Error(`Type "${memberName}" is not a member of union "${parsedType.name}" in tagged template inline fragment`);
|
|
2715
|
+
if (memberName in unionInput) throw new Error(`Duplicate inline fragment for union member "${memberName}" in tagged template. Merge selections into a single "... on ${memberName} { ... }" block.`);
|
|
2716
|
+
const memberFields = buildFieldsFromSelectionSet(sel.selectionSet, schema, memberName, varAssignments, interpolationMap);
|
|
2717
|
+
unionInput[memberName] = ({ f: _f }) => memberFields;
|
|
2718
|
+
} else if (sel.kind === Kind.FIELD && sel.name.value === "__typename") {
|
|
2719
|
+
if (sel.alias) throw new Error("Aliases on __typename in union selections are not supported in tagged templates. Use \"__typename\" without an alias.");
|
|
2720
|
+
if (sel.directives?.length) throw new Error(`Directives on __typename in union selections are not supported in tagged templates.`);
|
|
2721
|
+
hasTypename = true;
|
|
2722
|
+
} else {
|
|
2723
|
+
const desc = sel.kind === Kind.FIELD ? `Field "${sel.name.value}"` : "Fragment spread";
|
|
2724
|
+
unsupportedSelections.push(desc);
|
|
2725
|
+
}
|
|
2726
|
+
const hasInlineFragments = Object.keys(unionInput).length > 0;
|
|
2727
|
+
if (unsupportedSelections.length > 0) {
|
|
2728
|
+
if (hasInlineFragments) throw new Error(`${unsupportedSelections[0]} alongside inline fragments in union selection is not supported in tagged templates. Use per-member inline fragments instead.`);
|
|
2729
|
+
throw new Error(`Union field "${fieldName}" requires at least __typename or inline fragment syntax (... on Type { fields }) in tagged templates`);
|
|
2730
|
+
}
|
|
2731
|
+
if (!hasInlineFragments && !hasTypename) throw new Error(`Union field "${fieldName}" requires at least __typename or inline fragment syntax (... on Type { fields }) in tagged templates`);
|
|
2732
|
+
if (hasTypename) unionInput.__typename = true;
|
|
2733
|
+
const fieldResult = curried(unionInput);
|
|
2734
|
+
Object.assign(result, fieldResult);
|
|
2735
|
+
} else {
|
|
2736
|
+
const nestedFields = buildFieldsFromSelectionSet(selection.selectionSet, schema, resolveFieldTypeName(schema, typeName, fieldName), varAssignments, interpolationMap);
|
|
2737
|
+
const fieldResult = curried(({ f: nestedFactories }) => {
|
|
2738
|
+
return nestedFields;
|
|
1544
2739
|
});
|
|
2740
|
+
Object.assign(result, fieldResult);
|
|
1545
2741
|
}
|
|
1546
|
-
})
|
|
2742
|
+
} else Object.assign(result, curried);
|
|
2743
|
+
} else {
|
|
2744
|
+
const fieldResult = factory(args, extras);
|
|
2745
|
+
if (typeof fieldResult === "function") {
|
|
2746
|
+
const emptyResult = fieldResult(() => ({}));
|
|
2747
|
+
Object.assign(result, emptyResult);
|
|
2748
|
+
} else Object.assign(result, fieldResult);
|
|
2749
|
+
}
|
|
2750
|
+
} else if (selection.kind === Kind.FRAGMENT_SPREAD) {
|
|
2751
|
+
const fragmentName = selection.name.value;
|
|
2752
|
+
if (interpolationMap?.has(fragmentName)) {
|
|
2753
|
+
const interpolatedValue = interpolationMap.get(fragmentName);
|
|
2754
|
+
if (!interpolatedValue) throw new Error(`Interpolation placeholder "${fragmentName}" has no value`);
|
|
2755
|
+
let spreadFields;
|
|
2756
|
+
if (interpolatedValue instanceof Fragment) spreadFields = interpolatedValue.spread(varAssignments);
|
|
2757
|
+
else {
|
|
2758
|
+
if (!varAssignments) throw new Error(`Callback interpolation requires variable context`);
|
|
2759
|
+
spreadFields = interpolatedValue({ $: varAssignments });
|
|
2760
|
+
}
|
|
2761
|
+
Object.assign(result, spreadFields);
|
|
2762
|
+
} else throw new Error(`Fragment spread "...${fragmentName}" in tagged template must use interpolation syntax. Use \`...@\${fragment}\` instead of \`...FragmentName\`.`);
|
|
2763
|
+
} else if (selection.kind === Kind.INLINE_FRAGMENT) throw new Error("Inline fragments (... on Type) at the top level are not supported in tagged templates. Use inline fragments only inside union field selections.");
|
|
2764
|
+
return result;
|
|
2765
|
+
}
|
|
2766
|
+
/**
|
|
2767
|
+
* Build a simple args object from GraphQL AST argument nodes.
|
|
2768
|
+
* Extracts literal values from the AST for passing to field factories.
|
|
2769
|
+
*/
|
|
2770
|
+
function buildArgsFromASTArguments(args, varAssignments) {
|
|
2771
|
+
if (args.length === 0) return {};
|
|
2772
|
+
const result = {};
|
|
2773
|
+
for (const arg of args) result[arg.name.value] = extractASTValue(arg.value, varAssignments);
|
|
2774
|
+
return result;
|
|
2775
|
+
}
|
|
2776
|
+
/**
|
|
2777
|
+
* Extract a runtime value from a GraphQL AST ValueNode.
|
|
2778
|
+
*/
|
|
2779
|
+
function extractASTValue(node, varAssignments) {
|
|
2780
|
+
switch (node.kind) {
|
|
2781
|
+
case Kind.INT: return Number.parseInt(node.value, 10);
|
|
2782
|
+
case Kind.FLOAT: return Number.parseFloat(node.value);
|
|
2783
|
+
case Kind.STRING:
|
|
2784
|
+
case Kind.BOOLEAN:
|
|
2785
|
+
case Kind.ENUM: return node.value;
|
|
2786
|
+
case Kind.NULL: return null;
|
|
2787
|
+
case Kind.LIST: return node.values?.map((v) => extractASTValue(v, varAssignments)) ?? [];
|
|
2788
|
+
case Kind.OBJECT: return Object.fromEntries((node.fields ?? []).map((f) => [f.name.value, extractASTValue(f.value, varAssignments)]));
|
|
2789
|
+
case Kind.VARIABLE: {
|
|
2790
|
+
const varName = node.name.value;
|
|
2791
|
+
if (varAssignments && varName in varAssignments) return varAssignments[varName];
|
|
2792
|
+
return createVarRefFromVariable(varName);
|
|
2793
|
+
}
|
|
2794
|
+
default: return;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
/**
|
|
2798
|
+
* Resolve the output type name for a field on a given type.
|
|
2799
|
+
* Looks up the field's type specifier in the schema and extracts the type name.
|
|
2800
|
+
* Handles both string specifiers ("o|Avatar|?") and object specifiers ({ spec: "o|Avatar|?" }).
|
|
2801
|
+
*/
|
|
2802
|
+
function resolveFieldTypeName(schema, typeName, fieldName) {
|
|
2803
|
+
const typeDef = schema.object[typeName];
|
|
2804
|
+
if (!typeDef) throw new Error(`Type "${typeName}" is not defined in schema objects`);
|
|
2805
|
+
const fieldDef = typeDef.fields[fieldName];
|
|
2806
|
+
if (!fieldDef) throw new Error(`Field "${fieldName}" is not defined on type "${typeName}"`);
|
|
2807
|
+
return (typeof fieldDef === "string" ? fieldDef : fieldDef.spec).split("|")[1] ?? typeName;
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* Construct a synthetic GraphQL fragment source from JS arguments and template body.
|
|
2811
|
+
* Handles optional variable declarations: `($var: Type!) { fields }` or `{ fields }`.
|
|
2812
|
+
*/
|
|
2813
|
+
function buildSyntheticFragmentSource(name, typeName, body) {
|
|
2814
|
+
const trimmed = body.trim();
|
|
2815
|
+
if (trimmed.startsWith("(")) {
|
|
2816
|
+
const closeIndex = findMatchingParen(trimmed, 0);
|
|
2817
|
+
if (closeIndex === -1) throw new Error("Unmatched parenthesis in fragment variable declarations");
|
|
2818
|
+
return `fragment ${name}${trimmed.slice(0, closeIndex + 1)} on ${typeName} ${trimmed.slice(closeIndex + 1).trim()}`;
|
|
2819
|
+
}
|
|
2820
|
+
return `fragment ${name} on ${typeName} ${trimmed}`;
|
|
2821
|
+
}
|
|
2822
|
+
/**
|
|
2823
|
+
* Creates a curried tagged template function for fragments.
|
|
2824
|
+
* New API: `fragment("name", "type")\`{ fields }\`` returns TemplateResult<AnyFragment>.
|
|
2825
|
+
*
|
|
2826
|
+
* @param schema - The GraphQL schema definition
|
|
2827
|
+
*/
|
|
2828
|
+
function createFragmentTaggedTemplate(schema) {
|
|
2829
|
+
const schemaIndex = createSchemaIndexFromSchema(schema);
|
|
2830
|
+
return (fragmentName, onType) => {
|
|
2831
|
+
if (!(onType in schema.object)) throw new Error(`Type "${onType}" is not defined in schema objects`);
|
|
2832
|
+
return (strings, ...values) => {
|
|
2833
|
+
for (let i = 0; i < values.length; i++) {
|
|
2834
|
+
const value = values[i];
|
|
2835
|
+
if (!(value instanceof Fragment) && typeof value !== "function") throw new Error(`Tagged templates only accept Fragment instances or callback functions as interpolated values. Received ${typeof value} at position ${i}.`);
|
|
2836
|
+
}
|
|
2837
|
+
let body = strings[0] ?? "";
|
|
2838
|
+
const interpolationMap = /* @__PURE__ */ new Map();
|
|
2839
|
+
for (let i = 0; i < values.length; i++) {
|
|
2840
|
+
const placeholderName = `__INTERPOLATION_${i}__`;
|
|
2841
|
+
interpolationMap.set(placeholderName, values[i]);
|
|
2842
|
+
body += placeholderName + (strings[i + 1] ?? "");
|
|
2843
|
+
}
|
|
2844
|
+
const rawSource = buildSyntheticFragmentSource(fragmentName, onType, body);
|
|
2845
|
+
let varSpecifiers = extractFragmentVariables(rawSource, schemaIndex);
|
|
2846
|
+
varSpecifiers = mergeVariableDefinitions(varSpecifiers, interpolationMap);
|
|
2847
|
+
const { preprocessed } = preprocessFragmentArgs(rawSource);
|
|
2848
|
+
let document;
|
|
2849
|
+
try {
|
|
2850
|
+
document = parse(preprocessed);
|
|
2851
|
+
} catch (error) {
|
|
2852
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2853
|
+
throw new Error(`GraphQL parse error in tagged template: ${message}`);
|
|
2854
|
+
}
|
|
2855
|
+
const fragmentDefs = document.definitions.filter((def) => def.kind === Kind.FRAGMENT_DEFINITION);
|
|
2856
|
+
if (fragmentDefs.length !== 1) throw new Error(`Internal error: expected exactly one fragment definition in synthesized source`);
|
|
2857
|
+
const fragNode = fragmentDefs[0];
|
|
2858
|
+
if (fragNode.kind !== Kind.FRAGMENT_DEFINITION) throw new Error("Unexpected definition kind");
|
|
2859
|
+
return (options) => {
|
|
2860
|
+
return Fragment.create(() => ({
|
|
2861
|
+
typename: onType,
|
|
2862
|
+
key: fragmentName,
|
|
2863
|
+
schemaLabel: schema.label,
|
|
2864
|
+
variableDefinitions: varSpecifiers,
|
|
2865
|
+
spread: (variables) => {
|
|
2866
|
+
const $ = createVarAssignments(varSpecifiers, variables);
|
|
2867
|
+
let metadataBuilder = null;
|
|
2868
|
+
if (options?.metadata !== void 0) {
|
|
2869
|
+
const metadata = options.metadata;
|
|
2870
|
+
if (typeof metadata === "function") metadataBuilder = () => metadata({ $ });
|
|
2871
|
+
else metadataBuilder = () => metadata;
|
|
2872
|
+
}
|
|
2873
|
+
recordFragmentUsage({
|
|
2874
|
+
metadataBuilder,
|
|
2875
|
+
path: null
|
|
2876
|
+
});
|
|
2877
|
+
return buildFieldsFromSelectionSet(fragNode.selectionSet, schema, onType, $, interpolationMap);
|
|
2878
|
+
}
|
|
2879
|
+
}));
|
|
2880
|
+
};
|
|
1547
2881
|
};
|
|
1548
2882
|
};
|
|
1549
|
-
|
|
1550
|
-
};
|
|
2883
|
+
}
|
|
1551
2884
|
|
|
1552
2885
|
//#endregion
|
|
1553
2886
|
//#region packages/core/src/composer/operation.ts
|
|
@@ -1578,6 +2911,7 @@ const createOperationComposerFactory = (schema, adapter, transformDocument) => {
|
|
|
1578
2911
|
if (operationTypeName === null) throw new Error(`Operation type ${operationType} is not defined in schema roots`);
|
|
1579
2912
|
return (options) => {
|
|
1580
2913
|
return Operation.create((() => buildOperationArtifact({
|
|
2914
|
+
mode: "fieldsFactory",
|
|
1581
2915
|
schema,
|
|
1582
2916
|
operationType,
|
|
1583
2917
|
operationTypeName,
|
|
@@ -1593,6 +2927,106 @@ const createOperationComposerFactory = (schema, adapter, transformDocument) => {
|
|
|
1593
2927
|
};
|
|
1594
2928
|
};
|
|
1595
2929
|
|
|
2930
|
+
//#endregion
|
|
2931
|
+
//#region packages/core/src/composer/operation-tagged-template.ts
|
|
2932
|
+
/**
|
|
2933
|
+
* Operation tagged template function for creating GraphQL operations from template literals.
|
|
2934
|
+
* @module
|
|
2935
|
+
*/
|
|
2936
|
+
/**
|
|
2937
|
+
* Construct a synthetic GraphQL operation source from JS arguments and template body.
|
|
2938
|
+
* Handles optional variable declarations: `($var: Type!) { fields }` or `{ fields }`.
|
|
2939
|
+
*/
|
|
2940
|
+
function buildSyntheticOperationSource(operationType, operationName, body) {
|
|
2941
|
+
return `${operationType} ${operationName} ${body.trim()}`;
|
|
2942
|
+
}
|
|
2943
|
+
/**
|
|
2944
|
+
* Resolves a metadata option from OperationTemplateMetadataOptions into a MetadataBuilder
|
|
2945
|
+
* compatible with buildOperationArtifact.
|
|
2946
|
+
*
|
|
2947
|
+
* - `undefined` → `undefined` (no metadata)
|
|
2948
|
+
* - Raw value → `() => value` (static metadata)
|
|
2949
|
+
* - Callback → forwarded directly (receives full pipeline context)
|
|
2950
|
+
*/
|
|
2951
|
+
const resolveMetadataOption = (metadataOption) => {
|
|
2952
|
+
if (metadataOption === void 0) return void 0;
|
|
2953
|
+
if (typeof metadataOption === "function") return metadataOption;
|
|
2954
|
+
return () => metadataOption;
|
|
2955
|
+
};
|
|
2956
|
+
/**
|
|
2957
|
+
* Creates a curried tagged template function for a specific operation type.
|
|
2958
|
+
* New API: `query("name")\`($var: Type!) { fields }\`` returns TemplateResult<Operation>.
|
|
2959
|
+
*
|
|
2960
|
+
* @param schema - The GraphQL schema definition
|
|
2961
|
+
* @param operationType - The operation type (query, mutation, subscription)
|
|
2962
|
+
* @param metadataAdapter - Optional metadata adapter for metadata aggregation
|
|
2963
|
+
* @param adapterTransformDocument - Optional document transformer from adapter
|
|
2964
|
+
*/
|
|
2965
|
+
const createOperationTaggedTemplate = (schema, operationType, metadataAdapter, adapterTransformDocument) => {
|
|
2966
|
+
const schemaIndex = createSchemaIndexFromSchema(schema);
|
|
2967
|
+
return (operationName) => {
|
|
2968
|
+
return (strings, ...values) => {
|
|
2969
|
+
for (let i = 0; i < values.length; i++) {
|
|
2970
|
+
const value = values[i];
|
|
2971
|
+
if (!(value instanceof Fragment) && typeof value !== "function") throw new Error(`Tagged templates only accept Fragment instances or callback functions as interpolated values. Received ${typeof value} at position ${i}.`);
|
|
2972
|
+
}
|
|
2973
|
+
let body = strings[0] ?? "";
|
|
2974
|
+
const interpolationMap = /* @__PURE__ */ new Map();
|
|
2975
|
+
for (let i = 0; i < values.length; i++) {
|
|
2976
|
+
const placeholderName = `__INTERPOLATION_${i}__`;
|
|
2977
|
+
interpolationMap.set(placeholderName, values[i]);
|
|
2978
|
+
body += placeholderName + (strings[i + 1] ?? "");
|
|
2979
|
+
}
|
|
2980
|
+
const source = buildSyntheticOperationSource(operationType, operationName, body);
|
|
2981
|
+
let document;
|
|
2982
|
+
try {
|
|
2983
|
+
document = parse(source);
|
|
2984
|
+
} catch (error) {
|
|
2985
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2986
|
+
throw new Error(`GraphQL parse error in tagged template: ${message}`);
|
|
2987
|
+
}
|
|
2988
|
+
const opDefs = document.definitions.filter((def) => def.kind === Kind.OPERATION_DEFINITION);
|
|
2989
|
+
if (opDefs.length !== 1) throw new Error(`Internal error: expected exactly one operation definition in synthesized source`);
|
|
2990
|
+
const opNode = opDefs[0];
|
|
2991
|
+
if (opNode.kind !== Kind.OPERATION_DEFINITION) throw new Error("Unexpected definition kind");
|
|
2992
|
+
let varSpecifiers = buildVarSpecifiers(opNode.variableDefinitions ?? [], schemaIndex);
|
|
2993
|
+
varSpecifiers = mergeVariableDefinitions(varSpecifiers, interpolationMap);
|
|
2994
|
+
const operationTypeName = schema.operations[operationType];
|
|
2995
|
+
const resolvedAdapter = metadataAdapter ?? defaultMetadataAdapter;
|
|
2996
|
+
return (options) => {
|
|
2997
|
+
const resolvedMetadata = resolveMetadataOption(options?.metadata);
|
|
2998
|
+
if (interpolationMap.size === 0) return wrapArtifactAsOperation(() => buildOperationArtifact({
|
|
2999
|
+
mode: "prebuilt",
|
|
3000
|
+
schema,
|
|
3001
|
+
operationType,
|
|
3002
|
+
operationTypeName,
|
|
3003
|
+
operationName,
|
|
3004
|
+
variables: varSpecifiers,
|
|
3005
|
+
prebuiltDocument: document,
|
|
3006
|
+
prebuiltVariableNames: Object.keys(varSpecifiers),
|
|
3007
|
+
adapter: resolvedAdapter,
|
|
3008
|
+
metadata: resolvedMetadata,
|
|
3009
|
+
adapterTransformDocument
|
|
3010
|
+
}), true);
|
|
3011
|
+
return wrapArtifactAsOperation(() => buildOperationArtifact({
|
|
3012
|
+
mode: "fieldsFactory",
|
|
3013
|
+
schema,
|
|
3014
|
+
operationType,
|
|
3015
|
+
operationTypeName,
|
|
3016
|
+
operationName,
|
|
3017
|
+
variables: varSpecifiers,
|
|
3018
|
+
fieldsFactory: ({ $ }) => {
|
|
3019
|
+
return buildFieldsFromSelectionSet(opNode.selectionSet, schema, operationTypeName, $, interpolationMap);
|
|
3020
|
+
},
|
|
3021
|
+
adapter: resolvedAdapter,
|
|
3022
|
+
metadata: resolvedMetadata,
|
|
3023
|
+
adapterTransformDocument
|
|
3024
|
+
}), false);
|
|
3025
|
+
};
|
|
3026
|
+
};
|
|
3027
|
+
};
|
|
3028
|
+
};
|
|
3029
|
+
|
|
1596
3030
|
//#endregion
|
|
1597
3031
|
//#region packages/core/src/composer/var-ref-tools.ts
|
|
1598
3032
|
/**
|
|
@@ -1811,7 +3245,7 @@ const createVarBuilder = (inputTypeMethods) => {
|
|
|
1811
3245
|
*
|
|
1812
3246
|
* This is the main entry point for defining GraphQL operations and fragments.
|
|
1813
3247
|
* The returned function provides a context with:
|
|
1814
|
-
* - `fragment`:
|
|
3248
|
+
* - `fragment`: Tagged template function for fragment definitions
|
|
1815
3249
|
* - `query/mutation/subscription`: Operation builders
|
|
1816
3250
|
* - `$var`: Variable definition helpers
|
|
1817
3251
|
* - `$dir`: Field directive helpers (@skip, @include)
|
|
@@ -1844,22 +3278,22 @@ const createGqlElementComposer = (schema, options) => {
|
|
|
1844
3278
|
const helpers = adapter?.helpers;
|
|
1845
3279
|
const metadataAdapter = adapter?.metadata;
|
|
1846
3280
|
const transformDocument = adapter?.transformDocument;
|
|
1847
|
-
const fragment =
|
|
3281
|
+
const fragment = createFragmentTaggedTemplate(schema);
|
|
1848
3282
|
const createOperationComposer = createOperationComposerFactory(schema, metadataAdapter, transformDocument);
|
|
1849
3283
|
const transformedContext = applyContextTransformer({
|
|
1850
3284
|
fragment,
|
|
1851
|
-
query: {
|
|
3285
|
+
query: Object.assign(createOperationTaggedTemplate(schema, "query", metadataAdapter, transformDocument), {
|
|
1852
3286
|
operation: createOperationComposer("query"),
|
|
1853
|
-
compat:
|
|
1854
|
-
},
|
|
1855
|
-
mutation: {
|
|
3287
|
+
compat: createCompatTaggedTemplate(schema, "query")
|
|
3288
|
+
}),
|
|
3289
|
+
mutation: Object.assign(createOperationTaggedTemplate(schema, "mutation", metadataAdapter, transformDocument), {
|
|
1856
3290
|
operation: createOperationComposer("mutation"),
|
|
1857
|
-
compat:
|
|
1858
|
-
},
|
|
1859
|
-
subscription: {
|
|
3291
|
+
compat: createCompatTaggedTemplate(schema, "mutation")
|
|
3292
|
+
}),
|
|
3293
|
+
subscription: Object.assign(createOperationTaggedTemplate(schema, "subscription", metadataAdapter, transformDocument), {
|
|
1860
3294
|
operation: createOperationComposer("subscription"),
|
|
1861
|
-
compat:
|
|
1862
|
-
},
|
|
3295
|
+
compat: createCompatTaggedTemplate(schema, "subscription")
|
|
3296
|
+
}),
|
|
1863
3297
|
define: (factory) => GqlDefine.create(factory),
|
|
1864
3298
|
extend: createExtendComposer(schema, metadataAdapter, transformDocument),
|
|
1865
3299
|
$var: createVarBuilder(inputTypeMethods),
|
|
@@ -2202,5 +3636,5 @@ const generateInputTypeFromVarDefs = (schema, varDefs, options = {}) => {
|
|
|
2202
3636
|
};
|
|
2203
3637
|
|
|
2204
3638
|
//#endregion
|
|
2205
|
-
export { Fragment, GqlDefine, GqlElement, Operation, VarRef, appendToPath, applyTypeModifier, buildArgumentValue, buildConstValueNode, buildDocument, buildOperationArtifact, buildOperationTypeNode, buildWithTypeModifier, calculateFieldType, calculateFieldsType, createColocateHelper, createCompatComposer, createDefaultAdapter, createDirectiveBuilder, createDirectiveMethod, createExtendComposer, createFieldFactories,
|
|
3639
|
+
export { Fragment, GqlDefine, GqlElement, Operation, VarRef, appendToPath, applyTypeModifier, buildArgumentValue, buildConstValueNode, buildDocument, buildFieldsFromSelectionSet, buildOperationArtifact, buildOperationTypeNode, buildVarSpecifier, buildVarSpecifiers, buildWithTypeModifier, calculateFieldType, calculateFieldsType, collectVariableUsages, createColocateHelper, createCompatComposer, createCompatTaggedTemplate, createDefaultAdapter, createDirectiveBuilder, createDirectiveMethod, createExtendComposer, createFieldFactories, createFragmentTaggedTemplate, createGqlElementComposer, createOperationComposerFactory, createOperationTaggedTemplate, createSchemaIndex, createSchemaIndexFromSchema, createStandardDirectives, createTypedDirectiveMethod, createVarAssignments, createVarBuilder, createVarMethod, createVarMethodFactory, createVarRefFromVariable, createVarRefs, defaultMetadataAdapter, defineEnum, defineOperationRoots, defineScalar, err, extractFragmentVariables, generateInputObjectType, generateInputType, generateInputTypeFromSpecifiers, generateInputTypeFromVarDefs, getArgumentType, getCurrentFieldPath, getEnumType, getFieldReturnType, getInputFieldType, getScalarInputType, getScalarOutputType, graphqlTypeToTypeScript, inferVariablesFromUsages, isDirectiveRef, isListType, isModifierAssignable, isTemplateCompatSpec, mergeModifiers, mergeVariableUsages, ok, parseGraphqlSource, parseInputSpecifier, parseOutputSpecifier, parseTypeNode, preprocessFragmentArgs, recordFragmentUsage, sortFragmentsByDependency, transformParsedGraphql, withFieldPath, withFragmentUsageCollection, wrapArtifactAsOperation };
|
|
2206
3640
|
//# sourceMappingURL=index.js.map
|