@soda-gql/codegen 0.11.26 → 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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as generateMultiSchemaModule, t as createSchemaIndex } from "./generator-CsQAp9Xj.mjs";
1
+ import { a as compileTypeFilter, i as generatePrebuiltStub, n as generateIndexModule, r as generateMultiSchemaModule, t as createSchemaIndex } from "./generator-76iJu4D4.mjs";
2
2
  import { err, ok } from "neverthrow";
3
3
  import { Kind, concatAST, parse, print } from "graphql";
4
4
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
@@ -328,6 +328,7 @@ const isModifierAssignable = (source, target) => {
328
328
  for (let i = 0; i < srcStruct.lists.length; i++) {
329
329
  const srcList = srcStruct.lists[i];
330
330
  const tgtList = tgtListsToCompare[i];
331
+ if (srcList === undefined || tgtList === undefined) break;
331
332
  if (srcList === "[]?" && tgtList === "[]!") return false;
332
333
  }
333
334
  return true;
@@ -372,6 +373,7 @@ const mergeModifiers = (a, b) => {
372
373
  for (let i = 0; i < structA.lists.length; i++) {
373
374
  const listA = structA.lists[i];
374
375
  const listB = structB.lists[i];
376
+ if (listA === undefined || listB === undefined) break;
375
377
  mergedLists.push(listA === "[]!" || listB === "[]!" ? "[]!" : "[]?");
376
378
  }
377
379
  return {
@@ -550,14 +552,14 @@ const getFieldReturnType = (schema, parentTypeName, fieldName) => {
550
552
  * 3. Verify the candidate can satisfy ALL expected modifiers via isModifierAssignable
551
553
  */
552
554
  const mergeVariableUsages = (variableName, usages) => {
553
- if (usages.length === 0) {
555
+ const first = usages[0];
556
+ if (!first) {
554
557
  return err({
555
558
  code: "GRAPHQL_UNDECLARED_VARIABLE",
556
559
  message: `No usages found for variable "${variableName}"`,
557
560
  variableName
558
561
  });
559
562
  }
560
- const first = usages[0];
561
563
  for (const usage of usages) {
562
564
  if (usage.typeName !== first.typeName) {
563
565
  return err({
@@ -569,7 +571,9 @@ const mergeVariableUsages = (variableName, usages) => {
569
571
  }
570
572
  let candidateModifier = first.minimumModifier;
571
573
  for (let i = 1; i < usages.length; i++) {
572
- const result = mergeModifiers(candidateModifier, usages[i].minimumModifier);
574
+ const usage = usages[i];
575
+ if (!usage) break;
576
+ const result = mergeModifiers(candidateModifier, usage.minimumModifier);
573
577
  if (!result.ok) {
574
578
  return err({
575
579
  code: "GRAPHQL_VARIABLE_MODIFIER_INCOMPATIBLE",
@@ -1271,6 +1275,221 @@ const writeInjectTemplate = (outPath) => {
1271
1275
  };
1272
1276
  const getInjectTemplate = () => `${templateContents}\n`;
1273
1277
 
1278
+ //#endregion
1279
+ //#region packages/codegen/src/reachability.ts
1280
+ /**
1281
+ * Schema type reachability analysis.
1282
+ *
1283
+ * Determines which types are reachable from root types (Query/Mutation/Subscription)
1284
+ * to specified target types (e.g., fragment onType values from .graphql files).
1285
+ * Produces a CompiledFilter for use with existing buildExclusionSet.
1286
+ *
1287
+ * @module
1288
+ */
1289
+ const extractNamedType = (typeNode) => {
1290
+ switch (typeNode.kind) {
1291
+ case Kind.NAMED_TYPE: return typeNode.name.value;
1292
+ case Kind.LIST_TYPE: return extractNamedType(typeNode.type);
1293
+ case Kind.NON_NULL_TYPE: return extractNamedType(typeNode.type);
1294
+ }
1295
+ };
1296
+ const addEdge = (graph, from, to) => {
1297
+ let edges = graph.get(from);
1298
+ if (!edges) {
1299
+ edges = new Set();
1300
+ graph.set(from, edges);
1301
+ }
1302
+ edges.add(to);
1303
+ };
1304
+ const buildTypeGraph = (document) => {
1305
+ const schema = createSchemaIndex(document);
1306
+ const forward = new Map();
1307
+ const reverse = new Map();
1308
+ const addBidirectional = (from, to) => {
1309
+ addEdge(forward, from, to);
1310
+ addEdge(reverse, to, from);
1311
+ };
1312
+ for (const [typeName, record] of schema.objects) {
1313
+ for (const field of record.fields.values()) {
1314
+ const returnType = extractNamedType(field.type);
1315
+ addBidirectional(typeName, returnType);
1316
+ if (field.arguments) {
1317
+ for (const arg of field.arguments) {
1318
+ const argType = extractNamedType(arg.type);
1319
+ addBidirectional(typeName, argType);
1320
+ }
1321
+ }
1322
+ }
1323
+ }
1324
+ for (const [typeName, record] of schema.inputs) {
1325
+ for (const field of record.fields.values()) {
1326
+ const fieldType = extractNamedType(field.type);
1327
+ addBidirectional(typeName, fieldType);
1328
+ }
1329
+ }
1330
+ for (const [typeName, record] of schema.unions) {
1331
+ for (const memberName of record.members.keys()) {
1332
+ addBidirectional(typeName, memberName);
1333
+ }
1334
+ }
1335
+ return {
1336
+ graph: {
1337
+ forward,
1338
+ reverse
1339
+ },
1340
+ schema
1341
+ };
1342
+ };
1343
+ /**
1344
+ * BFS traversal collecting all reachable nodes from seeds.
1345
+ */
1346
+ const bfs = (adjacency, seeds, constraint) => {
1347
+ const visited = new Set();
1348
+ const queue = [];
1349
+ for (const seed of seeds) {
1350
+ if (!visited.has(seed)) {
1351
+ visited.add(seed);
1352
+ queue.push(seed);
1353
+ }
1354
+ }
1355
+ let head = 0;
1356
+ while (head < queue.length) {
1357
+ const current = queue[head++];
1358
+ if (current === undefined) break;
1359
+ const neighbors = adjacency.get(current);
1360
+ if (!neighbors) continue;
1361
+ for (const neighbor of neighbors) {
1362
+ if (visited.has(neighbor)) continue;
1363
+ if (constraint && !constraint.has(neighbor)) continue;
1364
+ visited.add(neighbor);
1365
+ queue.push(neighbor);
1366
+ }
1367
+ }
1368
+ return visited;
1369
+ };
1370
+ /**
1371
+ * Compute the set of type names reachable on paths from root types to target types.
1372
+ *
1373
+ * Algorithm:
1374
+ * 1. Backward BFS from target types to find all upstream types
1375
+ * 2. Forward BFS from root types, constrained to upstream set, to find actual paths
1376
+ * 3. Collect input/enum/scalar types used as field arguments on reachable object types
1377
+ */
1378
+ const computeReachableTypes = (graph, schema, targetTypes, usedArgumentTypes) => {
1379
+ const upstream = bfs(graph.reverse, targetTypes);
1380
+ const rootTypes = [];
1381
+ if (schema.operationTypes.query) rootTypes.push(schema.operationTypes.query);
1382
+ if (schema.operationTypes.mutation) rootTypes.push(schema.operationTypes.mutation);
1383
+ if (schema.operationTypes.subscription) rootTypes.push(schema.operationTypes.subscription);
1384
+ const validRoots = rootTypes.filter((r) => upstream.has(r));
1385
+ const pathTypes = bfs(graph.forward, validRoots, upstream);
1386
+ const reachable = new Set(pathTypes);
1387
+ const inputQueue = [];
1388
+ for (const typeName of pathTypes) {
1389
+ const objectRecord = schema.objects.get(typeName);
1390
+ if (!objectRecord) continue;
1391
+ for (const field of objectRecord.fields.values()) {
1392
+ const returnType = extractNamedType(field.type);
1393
+ if (!reachable.has(returnType)) {
1394
+ const isKnownComposite = schema.objects.has(returnType) || schema.inputs.has(returnType) || schema.unions.has(returnType);
1395
+ if (!isKnownComposite) {
1396
+ reachable.add(returnType);
1397
+ }
1398
+ }
1399
+ if (!usedArgumentTypes && field.arguments) {
1400
+ for (const arg of field.arguments) {
1401
+ const argType = extractNamedType(arg.type);
1402
+ if (!reachable.has(argType)) {
1403
+ reachable.add(argType);
1404
+ if (schema.inputs.has(argType)) {
1405
+ inputQueue.push(argType);
1406
+ }
1407
+ }
1408
+ }
1409
+ }
1410
+ }
1411
+ }
1412
+ if (usedArgumentTypes) {
1413
+ for (const inputName of usedArgumentTypes) {
1414
+ if (!reachable.has(inputName)) {
1415
+ reachable.add(inputName);
1416
+ inputQueue.push(inputName);
1417
+ }
1418
+ }
1419
+ }
1420
+ let inputHead = 0;
1421
+ while (inputHead < inputQueue.length) {
1422
+ const inputName = inputQueue[inputHead++];
1423
+ if (inputName === undefined) break;
1424
+ const inputRecord = schema.inputs.get(inputName);
1425
+ if (!inputRecord) continue;
1426
+ for (const field of inputRecord.fields.values()) {
1427
+ const fieldType = extractNamedType(field.type);
1428
+ if (!reachable.has(fieldType)) {
1429
+ reachable.add(fieldType);
1430
+ if (schema.inputs.has(fieldType)) {
1431
+ inputQueue.push(fieldType);
1432
+ }
1433
+ }
1434
+ }
1435
+ }
1436
+ return reachable;
1437
+ };
1438
+ /**
1439
+ * Compute a filter function that includes only types reachable from root types
1440
+ * to the specified target types.
1441
+ *
1442
+ * When targetTypes is empty, returns a pass-all filter (no filtering).
1443
+ * Warns when target types are not found in the schema.
1444
+ *
1445
+ * @param document - The parsed GraphQL schema document
1446
+ * @param targetTypes - Set of type names that fragments target (e.g., from ParsedFragment.onType)
1447
+ * @returns Filter function and any warnings
1448
+ */
1449
+ const computeReachabilityFilter = (document, targetTypes, usedArgumentTypes) => {
1450
+ if (targetTypes.size === 0) {
1451
+ return {
1452
+ filter: () => true,
1453
+ warnings: []
1454
+ };
1455
+ }
1456
+ const { graph, schema } = buildTypeGraph(document);
1457
+ const allTypeNames = new Set([
1458
+ ...schema.objects.keys(),
1459
+ ...schema.inputs.keys(),
1460
+ ...schema.enums.keys(),
1461
+ ...schema.unions.keys(),
1462
+ ...schema.scalars.keys()
1463
+ ]);
1464
+ const warnings = [];
1465
+ const validTargets = new Set();
1466
+ for (const target of targetTypes) {
1467
+ if (allTypeNames.has(target)) {
1468
+ validTargets.add(target);
1469
+ } else {
1470
+ warnings.push(`Target type "${target}" not found in schema`);
1471
+ }
1472
+ }
1473
+ if (validTargets.size === 0) {
1474
+ return {
1475
+ filter: () => true,
1476
+ warnings
1477
+ };
1478
+ }
1479
+ const reachable = computeReachableTypes(graph, schema, validTargets, usedArgumentTypes);
1480
+ if (reachable.size === 0) {
1481
+ warnings.push(`No types reachable from root operations to target types: ${[...validTargets].join(", ")}; skipping reachability filter`);
1482
+ return {
1483
+ filter: () => true,
1484
+ warnings
1485
+ };
1486
+ }
1487
+ return {
1488
+ filter: (context) => reachable.has(context.name),
1489
+ warnings
1490
+ };
1491
+ };
1492
+
1274
1493
  //#endregion
1275
1494
  //#region packages/codegen/src/bundler/esbuild.ts
1276
1495
  const esbuildBundler = {
@@ -1485,6 +1704,20 @@ const removeDirectory = (dirPath) => {
1485
1704
  });
1486
1705
  }
1487
1706
  };
1707
+ const readModule = (filePath) => {
1708
+ const targetPath = resolve(filePath);
1709
+ try {
1710
+ const content = readFileSync(targetPath, "utf-8");
1711
+ return ok(content);
1712
+ } catch (error) {
1713
+ const message = error instanceof Error ? error.message : String(error);
1714
+ return err({
1715
+ code: "READ_FAILED",
1716
+ message,
1717
+ outPath: targetPath
1718
+ });
1719
+ }
1720
+ };
1488
1721
  const writeModule = (outPath, contents) => {
1489
1722
  const targetPath = resolve(outPath);
1490
1723
  try {
@@ -1612,11 +1845,16 @@ const runCodegen = async (options) => {
1612
1845
  const schemas = new Map();
1613
1846
  const schemaHashes = {};
1614
1847
  for (const [name, schemaConfig] of Object.entries(options.schemas)) {
1615
- const result = await loadSchema(schemaConfig.schema).match((doc) => Promise.resolve(ok(doc)), (error) => Promise.resolve(err(error)));
1616
- if (result.isErr()) {
1617
- return err(result.error);
1848
+ const preloaded = options.preloadedSchemas?.get(name);
1849
+ if (preloaded) {
1850
+ schemas.set(name, preloaded);
1851
+ } else {
1852
+ const result = await loadSchema(schemaConfig.schema).match((doc) => Promise.resolve(ok(doc)), (error) => Promise.resolve(err(error)));
1853
+ if (result.isErr()) {
1854
+ return err(result.error);
1855
+ }
1856
+ schemas.set(name, result.value);
1618
1857
  }
1619
- schemas.set(name, result.value);
1620
1858
  }
1621
1859
  const injectionConfig = new Map();
1622
1860
  for (const [schemaName, schemaConfig] of Object.entries(options.schemas)) {
@@ -1650,15 +1888,10 @@ const runCodegen = async (options) => {
1650
1888
  chunkSize,
1651
1889
  typeFilters: typeFiltersConfig.size > 0 ? typeFiltersConfig : undefined
1652
1890
  });
1653
- const indexCode = `/**
1654
- * Generated by @soda-gql/codegen
1655
- * @module
1656
- * @generated
1657
- */
1658
- export * from "./_internal";
1659
- `;
1891
+ const schemaNames = Object.keys(options.schemas);
1892
+ const indexCode = generateIndexModule(schemaNames);
1660
1893
  for (const [name, document] of schemas.entries()) {
1661
- const schemaIndex = (await import("./generator-DSYhdRB2.mjs")).createSchemaIndex(document);
1894
+ const schemaIndex = (await import("./generator-DOfX1Run.mjs")).createSchemaIndex(document);
1662
1895
  const objects = Array.from(schemaIndex.objects.keys()).filter((n) => !n.startsWith("__")).length;
1663
1896
  const enums = Array.from(schemaIndex.enums.keys()).filter((n) => !n.startsWith("__")).length;
1664
1897
  const inputs = Array.from(schemaIndex.inputs.keys()).filter((n) => !n.startsWith("__")).length;
@@ -1719,6 +1952,35 @@ export * from "./_internal";
1719
1952
  if (indexWriteResult.isErr()) {
1720
1953
  return err(indexWriteResult.error);
1721
1954
  }
1955
+ const prebuiltStubPath = join(dirname(outPath), "types.prebuilt.ts");
1956
+ if (!existsSync(prebuiltStubPath)) {
1957
+ const prebuiltStubCode = generatePrebuiltStub(schemaNames);
1958
+ const prebuiltWriteResult = await writeModule(prebuiltStubPath, prebuiltStubCode).match(() => Promise.resolve(ok(undefined)), (error) => Promise.resolve(err(error)));
1959
+ if (prebuiltWriteResult.isErr()) {
1960
+ return err(prebuiltWriteResult.error);
1961
+ }
1962
+ } else {
1963
+ const readResult = readModule(prebuiltStubPath);
1964
+ if (readResult.isErr()) {
1965
+ return err(readResult.error);
1966
+ }
1967
+ const existingContent = readResult.value;
1968
+ const existingNames = new Set();
1969
+ for (const match of existingContent.matchAll(/export type PrebuiltTypes_(\w+)/g)) {
1970
+ const name = match[1];
1971
+ if (name) existingNames.add(name);
1972
+ }
1973
+ const missingNames = schemaNames.filter((name) => !existingNames.has(name));
1974
+ if (missingNames.length > 0) {
1975
+ const missingStubs = generatePrebuiltStub(missingNames);
1976
+ const stubDeclarations = missingStubs.replace(/^\/\*\*[\s\S]*?\*\/\n\n/, "");
1977
+ const updatedContent = `${existingContent.trimEnd()}\n\n${stubDeclarations}`;
1978
+ const patchResult = writeModule(prebuiltStubPath, updatedContent);
1979
+ if (patchResult.isErr()) {
1980
+ return err(patchResult.error);
1981
+ }
1982
+ }
1983
+ }
1722
1984
  const bundleOutcome = await esbuildBundler.bundle({
1723
1985
  sourcePath: outPath,
1724
1986
  external: ["@soda-gql/core", "@soda-gql/runtime"]
@@ -1738,5 +2000,5 @@ export * from "./_internal";
1738
2000
  };
1739
2001
 
1740
2002
  //#endregion
1741
- export { collectVariableUsages, emitFragment, emitOperation, getArgumentType, getFieldReturnType, getInputFieldType, hashSchema, inferVariablesFromUsages, isModifierAssignable, loadSchema, mergeModifiers, mergeVariableUsages, parseGraphqlFile, parseGraphqlSource, parseTypeNode, runCodegen, sortFragmentsByDependency, transformParsedGraphql, writeInjectTemplate };
2003
+ export { collectVariableUsages, compileTypeFilter, computeReachabilityFilter, emitFragment, emitOperation, getArgumentType, getFieldReturnType, getInputFieldType, hashSchema, inferVariablesFromUsages, isModifierAssignable, loadSchema, mergeModifiers, mergeVariableUsages, parseGraphqlFile, parseGraphqlSource, parseTypeNode, runCodegen, sortFragmentsByDependency, transformParsedGraphql, writeInjectTemplate };
1742
2004
  //# sourceMappingURL=index.mjs.map