@makerx/graphql-core 3.1.0 → 3.3.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 CHANGED
@@ -281,6 +281,38 @@ Refer to the `GraphQLLogOperationInfo` type for the definition of input.
281
281
 
282
282
  This function can be used across implementations, e.g. in a [GraphQL Envelop plugin](https://www.envelop.dev/docs/plugins) or [ApolloServer plugin](https://github.com/MakerXStudio/graphql-apollo-server/blob/main/src/plugins/graphql-operation-logging-plugin.ts).
283
283
 
284
+ ## Deprecation usage
285
+
286
+ ### collectDeprecatedElementUsage
287
+
288
+ Reports every `@deprecated` schema element an operation uses, so you can tell whether a deprecated element is safe to remove.
289
+
290
+ ```ts
291
+ const { elements, truncated } = collectDeprecatedElementUsage({ schema, document, operation, operationName, variables })
292
+ // elements: [{ kind: 'input-field', name: 'WidgetFilterInput.legacyId', path: '$input.legacyId' }]
293
+ // truncated: false
294
+ ```
295
+
296
+ Five kinds are detected: `output-field`, `argument`, `directive-argument`, `input-field` and `enum-value`.
297
+
298
+ Output fields and arguments are named in the operation document, so an AST walk finds them. Input fields and enum values are data the caller _supplies_ rather than selects, and can arrive either as a document literal or inside a variable — where the name appears nowhere in the AST — so the supplied variable values are walked too, against their declared input types.
299
+
300
+ Consumers generally don't call this directly. It is wired into `useSubscriptionsServer` via `includeDeprecatedElements` (below), and into Apollo Server by the `includeDeprecatedElements` option of [`graphqlOperationLoggingPlugin`](https://github.com/MakerXStudio/graphql-apollo-server), which adds the result to the operation log entry as `deprecatedElements`.
301
+
302
+ Behaviour worth knowing:
303
+
304
+ - **Results are deduplicated** on kind and name, and sorted. One request produces at most one entry per distinct element, so a 40-element list carrying the same deprecated field yields one record, not 40.
305
+ - **Only the executed operation counts.** A document may hold several operations but only one runs, so the others — and any fragments only they reach — are excluded.
306
+ - **Presence is the signal, explicit `null` included.** A client still sending a field would break if it were removed.
307
+ - **It never throws on malformed variables.** The walk runs before graphql-js coerces variables, so a payload whose shape contradicts its declared type reaches it; such values yield no records rather than an error.
308
+ - **Work is bounded** by `maxVariableDepth` (default 25), `maxVariableNodes` (default 10,000) and `maxElements` (default 50). Whenever any of them stops collection early, `truncated` is `true`.
309
+ - **`truncated` guards against a false negative.** Absence from `elements` only means an element went unused when `truncated` is `false`; on a truncated result the walk stopped before it finished. This matters because reading "nothing uses this" off an incomplete result is exactly how a still-used element gets deleted. Deduplication and null values do not set it — nothing is lost in either case.
310
+
311
+ Two limitations to design around:
312
+
313
+ - **This is request-side telemetry.** It observes values a caller _sends_, never values the server returns. A deprecated enum value that only ever appears in responses reads as unused however often the server returns it.
314
+ - **`path` is a best-effort debugging aid, not an aggregation key.** Aggregate on `name`. A field selected inside a fragment definition has no enclosing field in its ancestry, so its path is relative to the fragment rather than the operation — which is the common case for clients that use generated fragments.
315
+
284
316
  ## GraphQL subscriptions
285
317
 
286
318
  This library includes a `subscriptions` module to provide simple setup using the [GraphQL WS](https://the-guild.dev/graphql/ws) package.
@@ -335,6 +367,8 @@ This library includes a `subscriptions` module to provide simple setup using the
335
367
  - GraphQL context creation
336
368
  - Logging from the server `onConnect`, `onDisconnect`, `onOperation`, `onNext` and `onError` callbacks
337
369
 
370
+ Pass `includeDeprecatedElements: true` to add [deprecation usage](#collectdeprecatedelementusage) to the operation log entry as `deprecatedElements`. It is collected once, when the subscription is established, rather than per emitted payload — the usage is a property of the operation, not of each event.
371
+
338
372
  Example for Apollo Server (`wsServerCleanup` called in the `drainServer` plugin callback):
339
373
 
340
374
  ```ts
@@ -370,8 +404,26 @@ This library includes a `subscriptions` module to provide simple setup using the
370
404
 
371
405
  Wraps an `AsyncIterableIterator` (e.g. one returned by a pubsub `asyncIterableIterator(...)` call) with two optional behaviours:
372
406
 
373
- - `initialPayload` — a single event or array of events yielded before the wrapped iterator's events. Useful when a client subscribes after a state change has already happened, so they don't miss the current state while waiting for the next event.
374
- - `eventIsFinal` — a predicate that ends iteration eagerly when it returns `true` for an event. The final event is still yielded, then `wrapped.return()` is called to release resources. Only applied to events from the wrapped iterator, not to entries in `initialPayload`.
407
+ - `initialPayload` — a single event, an array of events, or a **factory** (`() => event | event[] | undefined | Promise<…>`) yielded before the wrapped iterator's events. Useful when a client subscribes after a state change has already happened, so they don't miss the current state while waiting for the next event. A factory may return `undefined` (e.g. a `findOne` that yields nothing) to mean "no snapshot", in which case iteration proceeds straight to the wrapped stream.
408
+ - `eventIsFinal` — a predicate that ends iteration eagerly when it returns `true` for an event. The triggering event is still yielded, then `wrapped.return()` is called to release resources. It is checked against every event from the wrapped iterator **and** against the _final_ entry of `initialPayload` (earlier entries are always yielded); if that final entry is terminal, iteration ends after the payload drains — in the value form without ever pulling the wrapped iterator.
409
+
410
+ #### Prefer the factory form for "snapshot then stream"
411
+
412
+ pubsub iterators (e.g. `graphql-subscriptions` / `graphql-redis-subscriptions`) subscribe to their channel **lazily, on the first `next()`**. If you read your snapshot (the initial payload) _before_ the iterator is pulled, the channel isn't live yet — any event published between the snapshot read and the subscription going live is **lost** _and_ missing from the snapshot, so the client can be left showing stale state until a manual refetch.
413
+
414
+ Passing `initialPayload` as a **factory** closes that window: the helper eagerly pulls the wrapped iterator once (establishing the subscription) _before_ invoking the factory to read the snapshot. Events fired during the snapshot read are then buffered by the wrapped iterator and delivered immediately after the snapshot, instead of being dropped.
415
+
416
+ ```ts
417
+ return wrapSubscriptionIterator({
418
+ iterator: pubsub().asyncIterableIterator(`topic.${id}`),
419
+ // factory runs AFTER the subscription is kicked, so an event fired during the read isn't lost
420
+ initialPayload: () => repo.find({ where: { id } }),
421
+ })
422
+ ```
423
+
424
+ > The plain value form (a single event or array) is unchanged and still subscribes lazily — fine when there is no read between subscribe and snapshot (e.g. a cached value already in hand, as below).
425
+ >
426
+ > Detection is via `typeof initialPayload === 'function'`. If your event type is itself callable, wrap a value-form payload in an array (`[value]`) to disambiguate it from a factory.
375
427
 
376
428
  Typical use is in a subscription resolver that needs to bridge a "current state" (cache or other persisted state lookup) with a live event stream, and close the subscription once a terminal event arrives:
377
429
 
@@ -0,0 +1,81 @@
1
+ import type { DocumentNode, GraphQLSchema, OperationDefinitionNode } from 'graphql';
2
+ /**
3
+ * Safety valve for the variable walk. Recursion follows the schema's input types, which may be
4
+ * mutually recursive, and variables are walked before graphql-js coerces them — so nothing else
5
+ * bounds how deep a hand-crafted payload can go. Far above any real operation's nesting.
6
+ */
7
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = 25;
8
+ /**
9
+ * Bounds the *breadth* of the variable walk, which the depth cap does not: a single large array of
10
+ * small input objects is shallow but arbitrarily wide.
11
+ */
12
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = 10000;
13
+ /**
14
+ * Caps the collected result so one hostile document cannot blow up a log entry.
15
+ */
16
+ export declare const DEFAULT_DEPRECATION_MAX_ELEMENTS = 50;
17
+ export type DeprecatedElementKind = 'output-field' | 'argument' | 'directive-argument' | 'input-field' | 'enum-value';
18
+ export interface DeprecatedElementUsage {
19
+ kind: DeprecatedElementKind;
20
+ /**
21
+ * Stable key for aggregating usage, formatted per kind:
22
+ * `Type.field`, `Type.field(arg)`, `@directive(arg)`, `InputType.field`, `EnumType.VALUE`.
23
+ */
24
+ name: string;
25
+ /**
26
+ * Best-effort location, for debugging a single record — not an aggregation key. A field selected
27
+ * inside a fragment definition has no enclosing field in its ancestry, so its path is relative to
28
+ * the fragment rather than the operation.
29
+ */
30
+ path?: string;
31
+ }
32
+ export interface CollectDeprecatedElementUsageOptions {
33
+ schema: GraphQLSchema;
34
+ document: DocumentNode;
35
+ /**
36
+ * The executed operation. Resolved from `document` and `operationName` when omitted.
37
+ */
38
+ operation?: OperationDefinitionNode | null;
39
+ operationName?: string | null;
40
+ /**
41
+ * Raw request variables, as supplied by the client and before graphql-js coerces them.
42
+ */
43
+ variables?: Record<string, unknown> | null;
44
+ maxVariableDepth?: number;
45
+ maxVariableNodes?: number;
46
+ maxElements?: number;
47
+ }
48
+ export interface CollectDeprecatedElementUsageResult {
49
+ elements: DeprecatedElementUsage[];
50
+ /**
51
+ * True when a limit — `maxElements`, `maxVariableDepth` or `maxVariableNodes` — stopped
52
+ * collection before it finished, so `elements` may be missing elements the operation really used.
53
+ * Absence from a truncated result is not evidence that an element is unused.
54
+ */
55
+ truncated: boolean;
56
+ }
57
+ /**
58
+ * Reports every `@deprecated` schema element an operation uses, across both the document and the
59
+ * supplied variables.
60
+ *
61
+ * Output fields and arguments are named in the document, so an AST walk finds them. Input fields
62
+ * and enum values are data the caller *supplies* rather than selects: they can arrive as a document
63
+ * literal, or inside a variable where the name appears nowhere in the AST, so both routes are
64
+ * walked and the results deduplicated.
65
+ *
66
+ * Results are deduplicated on kind and name — at most one entry per distinct element per request,
67
+ * bounded by the size of the schema rather than the size of the request — and sorted, so records
68
+ * are stable across requests.
69
+ *
70
+ * Returns `truncated` when any limit stopped collection. Absence from `elements` is only evidence
71
+ * that an element went unused when `truncated` is false — which matters, because concluding
72
+ * "nothing uses this" from a truncated result is how a still-used element gets deleted.
73
+ *
74
+ * The variable walk reads untrusted input before coercion, so a payload whose shape contradicts its
75
+ * declared type reaches it. Every shape it depends on is guarded: malformed variables yield no
76
+ * records rather than an error.
77
+ *
78
+ * This is request-side telemetry — it observes values a caller *sends*, never values the server
79
+ * returns. A deprecated element that only ever appears in responses reads as unused.
80
+ */
81
+ export declare function collectDeprecatedElementUsage({ schema, document, operation, operationName, variables, maxVariableDepth, maxVariableNodes, maxElements, }: CollectDeprecatedElementUsageOptions): CollectDeprecatedElementUsageResult;
@@ -0,0 +1,81 @@
1
+ import type { DocumentNode, GraphQLSchema, OperationDefinitionNode } from 'graphql';
2
+ /**
3
+ * Safety valve for the variable walk. Recursion follows the schema's input types, which may be
4
+ * mutually recursive, and variables are walked before graphql-js coerces them — so nothing else
5
+ * bounds how deep a hand-crafted payload can go. Far above any real operation's nesting.
6
+ */
7
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = 25;
8
+ /**
9
+ * Bounds the *breadth* of the variable walk, which the depth cap does not: a single large array of
10
+ * small input objects is shallow but arbitrarily wide.
11
+ */
12
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = 10000;
13
+ /**
14
+ * Caps the collected result so one hostile document cannot blow up a log entry.
15
+ */
16
+ export declare const DEFAULT_DEPRECATION_MAX_ELEMENTS = 50;
17
+ export type DeprecatedElementKind = 'output-field' | 'argument' | 'directive-argument' | 'input-field' | 'enum-value';
18
+ export interface DeprecatedElementUsage {
19
+ kind: DeprecatedElementKind;
20
+ /**
21
+ * Stable key for aggregating usage, formatted per kind:
22
+ * `Type.field`, `Type.field(arg)`, `@directive(arg)`, `InputType.field`, `EnumType.VALUE`.
23
+ */
24
+ name: string;
25
+ /**
26
+ * Best-effort location, for debugging a single record — not an aggregation key. A field selected
27
+ * inside a fragment definition has no enclosing field in its ancestry, so its path is relative to
28
+ * the fragment rather than the operation.
29
+ */
30
+ path?: string;
31
+ }
32
+ export interface CollectDeprecatedElementUsageOptions {
33
+ schema: GraphQLSchema;
34
+ document: DocumentNode;
35
+ /**
36
+ * The executed operation. Resolved from `document` and `operationName` when omitted.
37
+ */
38
+ operation?: OperationDefinitionNode | null;
39
+ operationName?: string | null;
40
+ /**
41
+ * Raw request variables, as supplied by the client and before graphql-js coerces them.
42
+ */
43
+ variables?: Record<string, unknown> | null;
44
+ maxVariableDepth?: number;
45
+ maxVariableNodes?: number;
46
+ maxElements?: number;
47
+ }
48
+ export interface CollectDeprecatedElementUsageResult {
49
+ elements: DeprecatedElementUsage[];
50
+ /**
51
+ * True when a limit — `maxElements`, `maxVariableDepth` or `maxVariableNodes` — stopped
52
+ * collection before it finished, so `elements` may be missing elements the operation really used.
53
+ * Absence from a truncated result is not evidence that an element is unused.
54
+ */
55
+ truncated: boolean;
56
+ }
57
+ /**
58
+ * Reports every `@deprecated` schema element an operation uses, across both the document and the
59
+ * supplied variables.
60
+ *
61
+ * Output fields and arguments are named in the document, so an AST walk finds them. Input fields
62
+ * and enum values are data the caller *supplies* rather than selects: they can arrive as a document
63
+ * literal, or inside a variable where the name appears nowhere in the AST, so both routes are
64
+ * walked and the results deduplicated.
65
+ *
66
+ * Results are deduplicated on kind and name — at most one entry per distinct element per request,
67
+ * bounded by the size of the schema rather than the size of the request — and sorted, so records
68
+ * are stable across requests.
69
+ *
70
+ * Returns `truncated` when any limit stopped collection. Absence from `elements` is only evidence
71
+ * that an element went unused when `truncated` is false — which matters, because concluding
72
+ * "nothing uses this" from a truncated result is how a still-used element gets deleted.
73
+ *
74
+ * The variable walk reads untrusted input before coercion, so a payload whose shape contradicts its
75
+ * declared type reaches it. Every shape it depends on is guarded: malformed variables yield no
76
+ * records rather than an error.
77
+ *
78
+ * This is request-side telemetry — it observes values a caller *sends*, never values the server
79
+ * returns. A deprecated element that only ever appears in responses reads as unused.
80
+ */
81
+ export declare function collectDeprecatedElementUsage({ schema, document, operation, operationName, variables, maxVariableDepth, maxVariableNodes, maxElements, }: CollectDeprecatedElementUsageOptions): CollectDeprecatedElementUsageResult;
@@ -0,0 +1,81 @@
1
+ import type { DocumentNode, GraphQLSchema, OperationDefinitionNode } from 'graphql';
2
+ /**
3
+ * Safety valve for the variable walk. Recursion follows the schema's input types, which may be
4
+ * mutually recursive, and variables are walked before graphql-js coerces them — so nothing else
5
+ * bounds how deep a hand-crafted payload can go. Far above any real operation's nesting.
6
+ */
7
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = 25;
8
+ /**
9
+ * Bounds the *breadth* of the variable walk, which the depth cap does not: a single large array of
10
+ * small input objects is shallow but arbitrarily wide.
11
+ */
12
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = 10000;
13
+ /**
14
+ * Caps the collected result so one hostile document cannot blow up a log entry.
15
+ */
16
+ export declare const DEFAULT_DEPRECATION_MAX_ELEMENTS = 50;
17
+ export type DeprecatedElementKind = 'output-field' | 'argument' | 'directive-argument' | 'input-field' | 'enum-value';
18
+ export interface DeprecatedElementUsage {
19
+ kind: DeprecatedElementKind;
20
+ /**
21
+ * Stable key for aggregating usage, formatted per kind:
22
+ * `Type.field`, `Type.field(arg)`, `@directive(arg)`, `InputType.field`, `EnumType.VALUE`.
23
+ */
24
+ name: string;
25
+ /**
26
+ * Best-effort location, for debugging a single record — not an aggregation key. A field selected
27
+ * inside a fragment definition has no enclosing field in its ancestry, so its path is relative to
28
+ * the fragment rather than the operation.
29
+ */
30
+ path?: string;
31
+ }
32
+ export interface CollectDeprecatedElementUsageOptions {
33
+ schema: GraphQLSchema;
34
+ document: DocumentNode;
35
+ /**
36
+ * The executed operation. Resolved from `document` and `operationName` when omitted.
37
+ */
38
+ operation?: OperationDefinitionNode | null;
39
+ operationName?: string | null;
40
+ /**
41
+ * Raw request variables, as supplied by the client and before graphql-js coerces them.
42
+ */
43
+ variables?: Record<string, unknown> | null;
44
+ maxVariableDepth?: number;
45
+ maxVariableNodes?: number;
46
+ maxElements?: number;
47
+ }
48
+ export interface CollectDeprecatedElementUsageResult {
49
+ elements: DeprecatedElementUsage[];
50
+ /**
51
+ * True when a limit — `maxElements`, `maxVariableDepth` or `maxVariableNodes` — stopped
52
+ * collection before it finished, so `elements` may be missing elements the operation really used.
53
+ * Absence from a truncated result is not evidence that an element is unused.
54
+ */
55
+ truncated: boolean;
56
+ }
57
+ /**
58
+ * Reports every `@deprecated` schema element an operation uses, across both the document and the
59
+ * supplied variables.
60
+ *
61
+ * Output fields and arguments are named in the document, so an AST walk finds them. Input fields
62
+ * and enum values are data the caller *supplies* rather than selects: they can arrive as a document
63
+ * literal, or inside a variable where the name appears nowhere in the AST, so both routes are
64
+ * walked and the results deduplicated.
65
+ *
66
+ * Results are deduplicated on kind and name — at most one entry per distinct element per request,
67
+ * bounded by the size of the schema rather than the size of the request — and sorted, so records
68
+ * are stable across requests.
69
+ *
70
+ * Returns `truncated` when any limit stopped collection. Absence from `elements` is only evidence
71
+ * that an element went unused when `truncated` is false — which matters, because concluding
72
+ * "nothing uses this" from a truncated result is how a still-used element gets deleted.
73
+ *
74
+ * The variable walk reads untrusted input before coercion, so a payload whose shape contradicts its
75
+ * declared type reaches it. Every shape it depends on is guarded: malformed variables yield no
76
+ * records rather than an error.
77
+ *
78
+ * This is request-side telemetry — it observes values a caller *sends*, never values the server
79
+ * returns. A deprecated element that only ever appears in responses reads as unused.
80
+ */
81
+ export declare function collectDeprecatedElementUsage({ schema, document, operation, operationName, variables, maxVariableDepth, maxVariableNodes, maxElements, }: CollectDeprecatedElementUsageOptions): CollectDeprecatedElementUsageResult;
package/deprecation.js ADDED
@@ -0,0 +1,240 @@
1
+ 'use strict';
2
+
3
+ var graphql = require('graphql');
4
+
5
+ /**
6
+ * Safety valve for the variable walk. Recursion follows the schema's input types, which may be
7
+ * mutually recursive, and variables are walked before graphql-js coerces them — so nothing else
8
+ * bounds how deep a hand-crafted payload can go. Far above any real operation's nesting.
9
+ */
10
+ const DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = 25;
11
+ /**
12
+ * Bounds the *breadth* of the variable walk, which the depth cap does not: a single large array of
13
+ * small input objects is shallow but arbitrarily wide.
14
+ */
15
+ const DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = 10000;
16
+ /**
17
+ * Caps the collected result so one hostile document cannot blow up a log entry.
18
+ */
19
+ const DEFAULT_DEPRECATION_MAX_ELEMENTS = 50;
20
+ const UNKNOWN = '<unknown>';
21
+ /**
22
+ * Reports every `@deprecated` schema element an operation uses, across both the document and the
23
+ * supplied variables.
24
+ *
25
+ * Output fields and arguments are named in the document, so an AST walk finds them. Input fields
26
+ * and enum values are data the caller *supplies* rather than selects: they can arrive as a document
27
+ * literal, or inside a variable where the name appears nowhere in the AST, so both routes are
28
+ * walked and the results deduplicated.
29
+ *
30
+ * Results are deduplicated on kind and name — at most one entry per distinct element per request,
31
+ * bounded by the size of the schema rather than the size of the request — and sorted, so records
32
+ * are stable across requests.
33
+ *
34
+ * Returns `truncated` when any limit stopped collection. Absence from `elements` is only evidence
35
+ * that an element went unused when `truncated` is false — which matters, because concluding
36
+ * "nothing uses this" from a truncated result is how a still-used element gets deleted.
37
+ *
38
+ * The variable walk reads untrusted input before coercion, so a payload whose shape contradicts its
39
+ * declared type reaches it. Every shape it depends on is guarded: malformed variables yield no
40
+ * records rather than an error.
41
+ *
42
+ * This is request-side telemetry — it observes values a caller *sends*, never values the server
43
+ * returns. A deprecated element that only ever appears in responses reads as unused.
44
+ */
45
+ function collectDeprecatedElementUsage({ schema, document, operation, operationName, variables, maxVariableDepth = DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH, maxVariableNodes = DEFAULT_DEPRECATION_MAX_VARIABLE_NODES, maxElements = DEFAULT_DEPRECATION_MAX_ELEMENTS, }) {
46
+ const collected = new Map();
47
+ const walk = { maxVariableDepth, remaining: maxVariableNodes, truncated: false };
48
+ const collect = (usage) => {
49
+ const key = `${usage.kind}:${usage.name}`;
50
+ // Already recorded is deduplication, not truncation — only a rejected *new* element is a loss.
51
+ if (collected.has(key))
52
+ return;
53
+ if (collected.size >= maxElements) {
54
+ walk.truncated = true;
55
+ return;
56
+ }
57
+ collected.set(key, usage);
58
+ };
59
+ const resolvedOperation = operation ?? graphql.getOperationAST(document, operationName ?? undefined);
60
+ collectFromDocument(schema, scopeToOperation(document, resolvedOperation), collect);
61
+ collectFromVariables(schema, resolvedOperation?.variableDefinitions, variables, walk, collect);
62
+ return {
63
+ elements: [...collected.values()].sort((a, b) => a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name)),
64
+ truncated: walk.truncated,
65
+ };
66
+ }
67
+ /**
68
+ * A document may carry several operations but only one runs, so anything the others select would be
69
+ * reported as usage that never happened. `separateOperations` keeps each operation with only the
70
+ * fragments it can reach, which is exactly the scope wanted here.
71
+ */
72
+ function scopeToOperation(document, operation) {
73
+ if (!operation)
74
+ return document;
75
+ let operationCount = 0;
76
+ for (const definition of document.definitions) {
77
+ if (definition.kind === graphql.Kind.OPERATION_DEFINITION)
78
+ operationCount++;
79
+ if (operationCount > 1)
80
+ return graphql.separateOperations(document)[operation.name?.value ?? ''] ?? document;
81
+ }
82
+ return document;
83
+ }
84
+ function collectFromDocument(schema, document, collect) {
85
+ const typeInfo = new graphql.TypeInfo(schema);
86
+ graphql.visit(document, graphql.visitWithTypeInfo(typeInfo, {
87
+ Field(_node, _key, _parent, _path, ancestors) {
88
+ const field = typeInfo.getFieldDef();
89
+ if (field?.deprecationReason == null)
90
+ return;
91
+ collect({
92
+ kind: 'output-field',
93
+ name: `${typeInfo.getParentType()?.name ?? UNKNOWN}.${field.name}`,
94
+ path: getPath(ancestors, field.name),
95
+ });
96
+ },
97
+ Argument(_node, _key, _parent, _path, ancestors) {
98
+ const argument = typeInfo.getArgument();
99
+ if (argument?.deprecationReason == null)
100
+ return;
101
+ // TypeInfo resolves an argument against the enclosing directive when there is one, so
102
+ // without this branch a directive's argument is reported as an argument of the field the
103
+ // directive is attached to — a field argument that does not exist.
104
+ const directive = typeInfo.getDirective();
105
+ collect({
106
+ kind: directive ? 'directive-argument' : 'argument',
107
+ name: directive
108
+ ? `@${directive.name}(${argument.name})`
109
+ : `${typeInfo.getParentType()?.name ?? UNKNOWN}.${typeInfo.getFieldDef()?.name ?? UNKNOWN}(${argument.name})`,
110
+ path: getPath(ancestors),
111
+ });
112
+ },
113
+ // Input-object fields written as literals in the document, including variable definition
114
+ // default values, which TypeInfo also tracks as input positions.
115
+ ObjectField(node, _key, _parent, _path, ancestors) {
116
+ const parentType = graphql.getNamedType(typeInfo.getParentInputType());
117
+ if (!graphql.isInputObjectType(parentType))
118
+ return;
119
+ const inputField = parentType.getFields()[node.name.value];
120
+ if (inputField?.deprecationReason == null)
121
+ return;
122
+ collect({
123
+ kind: 'input-field',
124
+ name: `${parentType.name}.${inputField.name}`,
125
+ path: getPath(ancestors, inputField.name),
126
+ });
127
+ },
128
+ EnumValue(node, _key, _parent, _path, ancestors) {
129
+ const enumType = graphql.getNamedType(typeInfo.getInputType());
130
+ if (!graphql.isEnumType(enumType))
131
+ return;
132
+ const enumValue = enumType.getValue(node.value);
133
+ if (enumValue?.deprecationReason == null)
134
+ return;
135
+ collect({
136
+ kind: 'enum-value',
137
+ name: `${enumType.name}.${enumValue.name}`,
138
+ path: getPath(ancestors),
139
+ });
140
+ },
141
+ }));
142
+ }
143
+ function isPathNode(node) {
144
+ return node.kind === graphql.Kind.FIELD || node.kind === graphql.Kind.OBJECT_FIELD;
145
+ }
146
+ function getPath(ancestors, leaf) {
147
+ const segments = ancestors
148
+ .filter((x) => !Array.isArray(x))
149
+ .filter(isPathNode)
150
+ .map((x) => x.name.value);
151
+ if (leaf)
152
+ segments.push(leaf);
153
+ return segments.join('.');
154
+ }
155
+ /**
156
+ * Walks each supplied variable value against its declared type, reporting every deprecated input
157
+ * field and enum value the client actually sent.
158
+ *
159
+ * Takes the definitions of the *resolved* operation rather than scanning the whole document: the
160
+ * same variable name may be declared with a different type in each operation, so only the executed
161
+ * one's declarations describe the supplied values.
162
+ */
163
+ function collectFromVariables(schema, variableDefinitions, variables, state, collect) {
164
+ if (variables == null)
165
+ return;
166
+ for (const variableDefinition of variableDefinitions ?? []) {
167
+ const variableName = variableDefinition.variable.name.value;
168
+ if (!Object.hasOwn(variables, variableName))
169
+ continue;
170
+ const type = graphql.typeFromAST(schema, variableDefinition.type);
171
+ if (!graphql.isInputType(type))
172
+ continue;
173
+ walkInputValue(type, variables[variableName], `$${variableName}`, 0, state, collect);
174
+ }
175
+ }
176
+ function walkInputValue(type, value, path, depth, state, collect) {
177
+ // A limit stopping the descent is a loss of telemetry, so it is recorded; a null value is simply
178
+ // nothing to walk, and must not be reported as one.
179
+ if (depth > state.maxVariableDepth || state.remaining <= 0) {
180
+ state.truncated = true;
181
+ return;
182
+ }
183
+ if (value == null)
184
+ return;
185
+ state.remaining--;
186
+ // Unwrapped inline rather than by recursing, so a nullability wrapper doesn't spend a node from
187
+ // the budget for a value that has not been descended into yet.
188
+ const unwrapped = graphql.isNonNullType(type) ? type.ofType : type;
189
+ if (graphql.isListType(unwrapped)) {
190
+ // GraphQL accepts a bare value in a list position, so handle both shapes.
191
+ const items = Array.isArray(value) ? value : [value];
192
+ for (const [index, item] of items.entries()) {
193
+ // Checked here rather than relying on the recursive call returning early: the loop itself is
194
+ // the unbounded part, since list length comes from the payload and each step builds a path.
195
+ if (state.remaining <= 0) {
196
+ state.truncated = true;
197
+ return;
198
+ }
199
+ walkInputValue(unwrapped.ofType, item, Array.isArray(value) ? `${path}.${index}` : path, depth + 1, state, collect);
200
+ }
201
+ return;
202
+ }
203
+ if (graphql.isEnumType(unwrapped)) {
204
+ // An unknown name is a coercion error graphql-js raises moments later; nothing to record here.
205
+ if (typeof value !== 'string')
206
+ return;
207
+ const enumValue = unwrapped.getValue(value);
208
+ if (enumValue?.deprecationReason != null) {
209
+ collect({ kind: 'enum-value', name: `${unwrapped.name}.${enumValue.name}`, path });
210
+ }
211
+ return;
212
+ }
213
+ // Scalars have no members that could carry a deprecation.
214
+ if (!graphql.isInputObjectType(unwrapped) || !isRecord(value))
215
+ return;
216
+ for (const field of Object.values(unwrapped.getFields())) {
217
+ if (state.remaining <= 0) {
218
+ state.truncated = true;
219
+ return;
220
+ }
221
+ if (!Object.hasOwn(value, field.name))
222
+ continue;
223
+ const fieldPath = `${path}.${field.name}`;
224
+ // Presence is the signal, explicit `null` included: a client still sending the field would
225
+ // break if it were removed from the schema.
226
+ if (field.deprecationReason != null) {
227
+ collect({ kind: 'input-field', name: `${unwrapped.name}.${field.name}`, path: fieldPath });
228
+ }
229
+ walkInputValue(field.type, value[field.name], fieldPath, depth + 1, state, collect);
230
+ }
231
+ }
232
+ function isRecord(value) {
233
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
234
+ }
235
+
236
+ exports.DEFAULT_DEPRECATION_MAX_ELEMENTS = DEFAULT_DEPRECATION_MAX_ELEMENTS;
237
+ exports.DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH;
238
+ exports.DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = DEFAULT_DEPRECATION_MAX_VARIABLE_NODES;
239
+ exports.collectDeprecatedElementUsage = collectDeprecatedElementUsage;
240
+ //# sourceMappingURL=deprecation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deprecation.js","sources":["../src/deprecation.ts"],"sourcesContent":["import type {\n ASTNode,\n DocumentNode,\n FieldNode,\n GraphQLInputType,\n GraphQLSchema,\n ObjectFieldNode,\n OperationDefinitionNode,\n VariableDefinitionNode,\n} from 'graphql'\nimport {\n getNamedType,\n getOperationAST,\n isEnumType,\n isInputObjectType,\n isInputType,\n isListType,\n isNonNullType,\n Kind,\n separateOperations,\n typeFromAST,\n TypeInfo,\n visit,\n visitWithTypeInfo,\n} from 'graphql'\n\n/**\n * Safety valve for the variable walk. Recursion follows the schema's input types, which may be\n * mutually recursive, and variables are walked before graphql-js coerces them — so nothing else\n * bounds how deep a hand-crafted payload can go. Far above any real operation's nesting.\n */\nexport const DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = 25\n\n/**\n * Bounds the *breadth* of the variable walk, which the depth cap does not: a single large array of\n * small input objects is shallow but arbitrarily wide.\n */\nexport const DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = 10_000\n\n/**\n * Caps the collected result so one hostile document cannot blow up a log entry.\n */\nexport const DEFAULT_DEPRECATION_MAX_ELEMENTS = 50\n\nconst UNKNOWN = '<unknown>'\n\nexport type DeprecatedElementKind = 'output-field' | 'argument' | 'directive-argument' | 'input-field' | 'enum-value'\n\nexport interface DeprecatedElementUsage {\n kind: DeprecatedElementKind\n /**\n * Stable key for aggregating usage, formatted per kind:\n * `Type.field`, `Type.field(arg)`, `@directive(arg)`, `InputType.field`, `EnumType.VALUE`.\n */\n name: string\n /**\n * Best-effort location, for debugging a single record — not an aggregation key. A field selected\n * inside a fragment definition has no enclosing field in its ancestry, so its path is relative to\n * the fragment rather than the operation.\n */\n path?: string\n}\n\nexport interface CollectDeprecatedElementUsageOptions {\n schema: GraphQLSchema\n document: DocumentNode\n /**\n * The executed operation. Resolved from `document` and `operationName` when omitted.\n */\n operation?: OperationDefinitionNode | null\n operationName?: string | null\n /**\n * Raw request variables, as supplied by the client and before graphql-js coerces them.\n */\n variables?: Record<string, unknown> | null\n maxVariableDepth?: number\n maxVariableNodes?: number\n maxElements?: number\n}\n\nexport interface CollectDeprecatedElementUsageResult {\n elements: DeprecatedElementUsage[]\n /**\n * True when a limit — `maxElements`, `maxVariableDepth` or `maxVariableNodes` — stopped\n * collection before it finished, so `elements` may be missing elements the operation really used.\n * Absence from a truncated result is not evidence that an element is unused.\n */\n truncated: boolean\n}\n\n/**\n * Reports every `@deprecated` schema element an operation uses, across both the document and the\n * supplied variables.\n *\n * Output fields and arguments are named in the document, so an AST walk finds them. Input fields\n * and enum values are data the caller *supplies* rather than selects: they can arrive as a document\n * literal, or inside a variable where the name appears nowhere in the AST, so both routes are\n * walked and the results deduplicated.\n *\n * Results are deduplicated on kind and name — at most one entry per distinct element per request,\n * bounded by the size of the schema rather than the size of the request — and sorted, so records\n * are stable across requests.\n *\n * Returns `truncated` when any limit stopped collection. Absence from `elements` is only evidence\n * that an element went unused when `truncated` is false — which matters, because concluding\n * \"nothing uses this\" from a truncated result is how a still-used element gets deleted.\n *\n * The variable walk reads untrusted input before coercion, so a payload whose shape contradicts its\n * declared type reaches it. Every shape it depends on is guarded: malformed variables yield no\n * records rather than an error.\n *\n * This is request-side telemetry — it observes values a caller *sends*, never values the server\n * returns. A deprecated element that only ever appears in responses reads as unused.\n */\nexport function collectDeprecatedElementUsage({\n schema,\n document,\n operation,\n operationName,\n variables,\n maxVariableDepth = DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH,\n maxVariableNodes = DEFAULT_DEPRECATION_MAX_VARIABLE_NODES,\n maxElements = DEFAULT_DEPRECATION_MAX_ELEMENTS,\n}: CollectDeprecatedElementUsageOptions): CollectDeprecatedElementUsageResult {\n const collected = new Map<string, DeprecatedElementUsage>()\n const walk: WalkState = { maxVariableDepth, remaining: maxVariableNodes, truncated: false }\n\n const collect = (usage: DeprecatedElementUsage): void => {\n const key = `${usage.kind}:${usage.name}`\n // Already recorded is deduplication, not truncation — only a rejected *new* element is a loss.\n if (collected.has(key)) return\n if (collected.size >= maxElements) {\n walk.truncated = true\n return\n }\n collected.set(key, usage)\n }\n\n const resolvedOperation = operation ?? getOperationAST(document, operationName ?? undefined)\n\n collectFromDocument(schema, scopeToOperation(document, resolvedOperation), collect)\n collectFromVariables(schema, resolvedOperation?.variableDefinitions, variables, walk, collect)\n\n return {\n elements: [...collected.values()].sort((a, b) => a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name)),\n truncated: walk.truncated,\n }\n}\n\ntype Collect = (usage: DeprecatedElementUsage) => void\n\n/**\n * A document may carry several operations but only one runs, so anything the others select would be\n * reported as usage that never happened. `separateOperations` keeps each operation with only the\n * fragments it can reach, which is exactly the scope wanted here.\n */\nfunction scopeToOperation(document: DocumentNode, operation: OperationDefinitionNode | null | undefined): DocumentNode {\n if (!operation) return document\n\n let operationCount = 0\n for (const definition of document.definitions) {\n if (definition.kind === Kind.OPERATION_DEFINITION) operationCount++\n if (operationCount > 1) return separateOperations(document)[operation.name?.value ?? ''] ?? document\n }\n return document\n}\n\nfunction collectFromDocument(schema: GraphQLSchema, document: DocumentNode, collect: Collect): void {\n const typeInfo = new TypeInfo(schema)\n\n visit(\n document,\n visitWithTypeInfo(typeInfo, {\n Field(_node, _key, _parent, _path, ancestors) {\n const field = typeInfo.getFieldDef()\n if (field?.deprecationReason == null) return\n collect({\n kind: 'output-field',\n name: `${typeInfo.getParentType()?.name ?? UNKNOWN}.${field.name}`,\n path: getPath(ancestors, field.name),\n })\n },\n Argument(_node, _key, _parent, _path, ancestors) {\n const argument = typeInfo.getArgument()\n if (argument?.deprecationReason == null) return\n // TypeInfo resolves an argument against the enclosing directive when there is one, so\n // without this branch a directive's argument is reported as an argument of the field the\n // directive is attached to — a field argument that does not exist.\n const directive = typeInfo.getDirective()\n collect({\n kind: directive ? 'directive-argument' : 'argument',\n name: directive\n ? `@${directive.name}(${argument.name})`\n : `${typeInfo.getParentType()?.name ?? UNKNOWN}.${typeInfo.getFieldDef()?.name ?? UNKNOWN}(${argument.name})`,\n path: getPath(ancestors),\n })\n },\n // Input-object fields written as literals in the document, including variable definition\n // default values, which TypeInfo also tracks as input positions.\n ObjectField(node, _key, _parent, _path, ancestors) {\n const parentType = getNamedType(typeInfo.getParentInputType())\n if (!isInputObjectType(parentType)) return\n const inputField = parentType.getFields()[node.name.value]\n if (inputField?.deprecationReason == null) return\n collect({\n kind: 'input-field',\n name: `${parentType.name}.${inputField.name}`,\n path: getPath(ancestors, inputField.name),\n })\n },\n EnumValue(node, _key, _parent, _path, ancestors) {\n const enumType = getNamedType(typeInfo.getInputType())\n if (!isEnumType(enumType)) return\n const enumValue = enumType.getValue(node.value)\n if (enumValue?.deprecationReason == null) return\n collect({\n kind: 'enum-value',\n name: `${enumType.name}.${enumValue.name}`,\n path: getPath(ancestors),\n })\n },\n }),\n )\n}\n\ntype PathNode = FieldNode | ObjectFieldNode\n\nfunction isPathNode(node: ASTNode): node is PathNode {\n return node.kind === Kind.FIELD || node.kind === Kind.OBJECT_FIELD\n}\n\nfunction getPath(ancestors: readonly (ASTNode | readonly ASTNode[])[], leaf?: string): string {\n const segments = ancestors\n .filter((x): x is ASTNode => !Array.isArray(x))\n .filter(isPathNode)\n .map((x) => x.name.value)\n\n if (leaf) segments.push(leaf)\n\n return segments.join('.')\n}\n\ninterface WalkState {\n maxVariableDepth: number\n /** Remaining node budget, shared across every variable of the operation. */\n remaining: number\n truncated: boolean\n}\n\n/**\n * Walks each supplied variable value against its declared type, reporting every deprecated input\n * field and enum value the client actually sent.\n *\n * Takes the definitions of the *resolved* operation rather than scanning the whole document: the\n * same variable name may be declared with a different type in each operation, so only the executed\n * one's declarations describe the supplied values.\n */\nfunction collectFromVariables(\n schema: GraphQLSchema,\n variableDefinitions: readonly VariableDefinitionNode[] | undefined,\n variables: Record<string, unknown> | null | undefined,\n state: WalkState,\n collect: Collect,\n): void {\n if (variables == null) return\n\n for (const variableDefinition of variableDefinitions ?? []) {\n const variableName = variableDefinition.variable.name.value\n if (!Object.hasOwn(variables, variableName)) continue\n\n const type = typeFromAST(schema, variableDefinition.type)\n if (!isInputType(type)) continue\n\n walkInputValue(type, variables[variableName], `$${variableName}`, 0, state, collect)\n }\n}\n\nfunction walkInputValue(type: GraphQLInputType, value: unknown, path: string, depth: number, state: WalkState, collect: Collect): void {\n // A limit stopping the descent is a loss of telemetry, so it is recorded; a null value is simply\n // nothing to walk, and must not be reported as one.\n if (depth > state.maxVariableDepth || state.remaining <= 0) {\n state.truncated = true\n return\n }\n if (value == null) return\n state.remaining--\n\n // Unwrapped inline rather than by recursing, so a nullability wrapper doesn't spend a node from\n // the budget for a value that has not been descended into yet.\n const unwrapped = isNonNullType(type) ? type.ofType : type\n\n if (isListType(unwrapped)) {\n // GraphQL accepts a bare value in a list position, so handle both shapes.\n const items = Array.isArray(value) ? value : [value]\n for (const [index, item] of items.entries()) {\n // Checked here rather than relying on the recursive call returning early: the loop itself is\n // the unbounded part, since list length comes from the payload and each step builds a path.\n if (state.remaining <= 0) {\n state.truncated = true\n return\n }\n walkInputValue(unwrapped.ofType, item, Array.isArray(value) ? `${path}.${index}` : path, depth + 1, state, collect)\n }\n return\n }\n\n if (isEnumType(unwrapped)) {\n // An unknown name is a coercion error graphql-js raises moments later; nothing to record here.\n if (typeof value !== 'string') return\n const enumValue = unwrapped.getValue(value)\n if (enumValue?.deprecationReason != null) {\n collect({ kind: 'enum-value', name: `${unwrapped.name}.${enumValue.name}`, path })\n }\n return\n }\n\n // Scalars have no members that could carry a deprecation.\n if (!isInputObjectType(unwrapped) || !isRecord(value)) return\n\n for (const field of Object.values(unwrapped.getFields())) {\n if (state.remaining <= 0) {\n state.truncated = true\n return\n }\n if (!Object.hasOwn(value, field.name)) continue\n\n const fieldPath = `${path}.${field.name}`\n // Presence is the signal, explicit `null` included: a client still sending the field would\n // break if it were removed from the schema.\n if (field.deprecationReason != null) {\n collect({ kind: 'input-field', name: `${unwrapped.name}.${field.name}`, path: fieldPath })\n }\n\n walkInputValue(field.type, value[field.name], fieldPath, depth + 1, state, collect)\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"],"names":["getOperationAST","Kind","separateOperations","TypeInfo","visit","visitWithTypeInfo","getNamedType","isInputObjectType","isEnumType","typeFromAST","isInputType","isNonNullType","isListType"],"mappings":"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"}