@trackunit/graphql-build-tools 0.0.18-alpha-6a85feaada3.0 → 0.0.18

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/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 0.0.18 (2026-06-29)
2
+
3
+ This was a version bump only for graphql-build-tools to align it with other projects, there were no code changes.
4
+
5
+ ## 0.0.17 (2026-06-25)
6
+
7
+ This was a version bump only for graphql-build-tools to align it with other projects, there were no code changes.
8
+
9
+ ## 0.0.16 (2026-06-24)
10
+
11
+ ### 🩹 Fixes
12
+
13
+ - **graphql-build-tools:** preserve schema directives and drop emptied root types when reconciling root operations ([fdc403007aa](https://github.com/Trackunit/manager/commit/fdc403007aa))
14
+ - **graphql-build-tools:** drop dangling root operation bindings when @hidden empties a root type ([af908369e9f](https://github.com/Trackunit/manager/commit/af908369e9f))
15
+
16
+ ### ❤️ Thank You
17
+
18
+ - Claude Opus 4.8
19
+ - Cursor @cursoragent
20
+ - Kieran Prince
21
+ - Ulrik Jørgensen
22
+
1
23
  ## 0.0.15 (2026-06-23)
2
24
 
3
25
  ### 🩹 Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/graphql-build-tools",
3
- "version": "0.0.18-alpha-6a85feaada3.0",
3
+ "version": "0.0.18",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "engines": {
6
6
  "node": ">=24.x"
@@ -0,0 +1,2 @@
1
+ import { GraphQLSchema } from "graphql";
2
+ export declare const reconcileRootOperationTypes: (schema: GraphQLSchema) => GraphQLSchema;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reconcileRootOperationTypes = void 0;
4
+ const graphql_1 = require("graphql");
5
+ /**
6
+ * Per-server @hidden filtering can prune a root operation type (query/mutation/subscription) away while
7
+ * the original explicit `schema {}` block still binds it. `printSchemaWithDirectives` then emits a
8
+ * dangling binding (e.g. `mutation: Mutation` with no `type Mutation`), which fails stitching
9
+ * composition with `Unknown type Mutation.`. A subgraph legitimately having no query or no mutation for
10
+ * a server is fine — only the dangling reference is not.
11
+ *
12
+ * This rebinds each operation to a live, non-empty root type and rewrites the schema-definition AST so it
13
+ * only lists operations whose root type is still live. Schema-level directives / extensions on that AST
14
+ * are preserved (they live nowhere else), and any now-empty root type is dropped from the type map so we
15
+ * never emit an empty `type X {}`. Returns the schema untouched when nothing dangles, keeping the common
16
+ * case byte-for-byte identical.
17
+ */
18
+ const isNonEmptyObjectType = (type) => type !== null && type !== undefined && Object.keys(type.getFields()).length > 0;
19
+ // Operations still bound by the schema-definition AST (what astFromSchema would print).
20
+ const boundOperationsFromAst = (schema) => {
21
+ const operations = new Set();
22
+ const nodes = [schema.astNode, ...schema.extensionASTNodes];
23
+ for (const node of nodes) {
24
+ if (node === null || node === undefined) {
25
+ continue;
26
+ }
27
+ for (const operationType of node.operationTypes ?? []) {
28
+ operations.add(operationType.operation);
29
+ }
30
+ }
31
+ return operations;
32
+ };
33
+ // Keep only the operation bindings whose root type is still live, dropping the dangling ones.
34
+ const liveOperationTypeNodes = (operationTypes, liveOperations) => (operationTypes ?? []).filter(operationType => liveOperations.has(operationType.operation));
35
+ const reconcileRootOperationTypes = (schema) => {
36
+ const config = schema.toConfig();
37
+ const liveQuery = isNonEmptyObjectType(config.query) ? config.query : undefined;
38
+ const liveMutation = isNonEmptyObjectType(config.mutation) ? config.mutation : undefined;
39
+ const liveSubscription = isNonEmptyObjectType(config.subscription) ? config.subscription : undefined;
40
+ const emptiedAnOperation = liveQuery !== config.query || liveMutation !== config.mutation || liveSubscription !== config.subscription;
41
+ const boundOperations = boundOperationsFromAst(schema);
42
+ const hasDanglingBinding = (boundOperations.has(graphql_1.OperationTypeNode.QUERY) && liveQuery === undefined) ||
43
+ (boundOperations.has(graphql_1.OperationTypeNode.MUTATION) && liveMutation === undefined) ||
44
+ (boundOperations.has(graphql_1.OperationTypeNode.SUBSCRIPTION) && liveSubscription === undefined);
45
+ // No emptied root and no dangling binding — leave the output identical.
46
+ if (!emptiedAnOperation && !hasDanglingBinding) {
47
+ return schema;
48
+ }
49
+ const liveOperations = new Set();
50
+ if (liveQuery !== undefined) {
51
+ liveOperations.add(graphql_1.OperationTypeNode.QUERY);
52
+ }
53
+ if (liveMutation !== undefined) {
54
+ liveOperations.add(graphql_1.OperationTypeNode.MUTATION);
55
+ }
56
+ if (liveSubscription !== undefined) {
57
+ liveOperations.add(graphql_1.OperationTypeNode.SUBSCRIPTION);
58
+ }
59
+ // Root types that are no longer live must leave the type map, otherwise an empty `type X {}` is printed.
60
+ const deadRootTypeNames = new Set();
61
+ const markDeadRoot = (original, live) => {
62
+ if (original !== null && original !== undefined && live === undefined) {
63
+ deadRootTypeNames.add(original.name);
64
+ }
65
+ };
66
+ markDeadRoot(config.query, liveQuery);
67
+ markDeadRoot(config.mutation, liveMutation);
68
+ markDeadRoot(config.subscription, liveSubscription);
69
+ const reconciledAstNode = config.astNode === null || config.astNode === undefined
70
+ ? config.astNode
71
+ : { ...config.astNode, operationTypes: liveOperationTypeNodes(config.astNode.operationTypes, liveOperations) };
72
+ return new graphql_1.GraphQLSchema({
73
+ ...config,
74
+ query: liveQuery,
75
+ mutation: liveMutation,
76
+ subscription: liveSubscription,
77
+ // Drop dead roots from the explicit type list. `new GraphQLSchema` re-collects any type still
78
+ // referenced by a live field, so a former root that is also used as a field type survives as a
79
+ // regular type — only the genuinely unreferenced root is removed.
80
+ types: config.types.filter(type => !deadRootTypeNames.has(type.name)),
81
+ // Keep schema-level directives/extensions; only strip operation entries whose root type is gone.
82
+ astNode: reconciledAstNode,
83
+ extensionASTNodes: config.extensionASTNodes.map(node => ({
84
+ ...node,
85
+ operationTypes: liveOperationTypeNodes(node.operationTypes, liveOperations),
86
+ })),
87
+ });
88
+ };
89
+ exports.reconcileRootOperationTypes = reconcileRootOperationTypes;
90
+ //# sourceMappingURL=reconcile-root-operation-types.js.map
@@ -8,6 +8,7 @@ const complexity_directive_1 = require("./complexity-directive");
8
8
  const global_id_transformer_1 = require("./global-id-transformer");
9
9
  const hidden_directive_1 = require("./hidden-directive");
10
10
  const preview_directive_1 = require("./preview-directive");
11
+ const reconcile_root_operation_types_1 = require("./reconcile-root-operation-types");
11
12
  exports.graphqlGatewayNames = ["internal", "public", "report", "v3-mobile"];
12
13
  const applySdlDirectiveTransformers = (schema, gatewayName, { preserveStitchingEntrypoints = false } = {}) => {
13
14
  const { complexityDirectiveTransformer } = (0, complexity_directive_1.complexityDirective)();
@@ -16,7 +17,10 @@ const applySdlDirectiveTransformers = (schema, gatewayName, { preserveStitchingE
16
17
  const { authorizationDirectiveTransformer } = (0, authorization_directive_1.authorizationDirective)();
17
18
  const hiddenSchema = hiddenDirectiveTransformer(schema);
18
19
  const previewSchema = gatewayName === "report" ? hiddenSchema : previewDirectiveTransformer(hiddenSchema);
19
- return authorizationDirectiveTransformer(complexityDirectiveTransformer((0, global_id_transformer_1.globalIdTransformer)(previewSchema)));
20
+ const transformedSchema = authorizationDirectiveTransformer(complexityDirectiveTransformer((0, global_id_transformer_1.globalIdTransformer)(previewSchema)));
21
+ // Drop dangling/empty root operations left when @hidden empties a root type. Complements
22
+ // preserveStitchingEntrypoints, which only rescues roots whose fields carry a stitching directive.
23
+ return (0, reconcile_root_operation_types_1.reconcileRootOperationTypes)(transformedSchema);
20
24
  };
21
25
  exports.applySdlDirectiveTransformers = applySdlDirectiveTransformers;
22
26
  exports.applyDirectiveTransformers = exports.applySdlDirectiveTransformers;