@makerx/graphql-core 3.2.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 +34 -0
- package/deprecation.d.cts +81 -0
- package/deprecation.d.mts +81 -0
- package/deprecation.d.ts +81 -0
- package/deprecation.js +240 -0
- package/deprecation.js.map +1 -0
- package/deprecation.mjs +235 -0
- package/deprecation.mjs.map +1 -0
- package/index.d.cts +1 -0
- package/index.d.mts +1 -0
- package/index.d.ts +1 -0
- package/index.js +5 -0
- package/index.js.map +1 -1
- package/index.mjs +1 -0
- package/index.mjs.map +1 -1
- package/logging.d.cts +17 -2
- package/logging.d.mts +17 -2
- package/logging.d.ts +17 -2
- package/logging.js +21 -3
- package/logging.js.map +1 -1
- package/logging.mjs +21 -3
- package/logging.mjs.map +1 -1
- package/package.json +1 -1
- package/subscriptions/server.d.cts +6 -1
- package/subscriptions/server.d.mts +6 -1
- package/subscriptions/server.d.ts +6 -1
- package/subscriptions/server.js +8 -2
- package/subscriptions/server.js.map +1 -1
- package/subscriptions/server.mjs +8 -2
- package/subscriptions/server.mjs.map +1 -1
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
|
|
@@ -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.d.ts
ADDED
|
@@ -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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"}
|