@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.
@@ -0,0 +1,235 @@
1
+ import { getOperationAST, TypeInfo, visit, visitWithTypeInfo, getNamedType, isEnumType, isInputObjectType, separateOperations, typeFromAST, isInputType, isNonNullType, isListType, Kind } from 'graphql';
2
+
3
+ /**
4
+ * Safety valve for the variable walk. Recursion follows the schema's input types, which may be
5
+ * mutually recursive, and variables are walked before graphql-js coerces them — so nothing else
6
+ * bounds how deep a hand-crafted payload can go. Far above any real operation's nesting.
7
+ */
8
+ const DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = 25;
9
+ /**
10
+ * Bounds the *breadth* of the variable walk, which the depth cap does not: a single large array of
11
+ * small input objects is shallow but arbitrarily wide.
12
+ */
13
+ const DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = 10000;
14
+ /**
15
+ * Caps the collected result so one hostile document cannot blow up a log entry.
16
+ */
17
+ const DEFAULT_DEPRECATION_MAX_ELEMENTS = 50;
18
+ const UNKNOWN = '<unknown>';
19
+ /**
20
+ * Reports every `@deprecated` schema element an operation uses, across both the document and the
21
+ * supplied variables.
22
+ *
23
+ * Output fields and arguments are named in the document, so an AST walk finds them. Input fields
24
+ * and enum values are data the caller *supplies* rather than selects: they can arrive as a document
25
+ * literal, or inside a variable where the name appears nowhere in the AST, so both routes are
26
+ * walked and the results deduplicated.
27
+ *
28
+ * Results are deduplicated on kind and name — at most one entry per distinct element per request,
29
+ * bounded by the size of the schema rather than the size of the request — and sorted, so records
30
+ * are stable across requests.
31
+ *
32
+ * Returns `truncated` when any limit stopped collection. Absence from `elements` is only evidence
33
+ * that an element went unused when `truncated` is false — which matters, because concluding
34
+ * "nothing uses this" from a truncated result is how a still-used element gets deleted.
35
+ *
36
+ * The variable walk reads untrusted input before coercion, so a payload whose shape contradicts its
37
+ * declared type reaches it. Every shape it depends on is guarded: malformed variables yield no
38
+ * records rather than an error.
39
+ *
40
+ * This is request-side telemetry — it observes values a caller *sends*, never values the server
41
+ * returns. A deprecated element that only ever appears in responses reads as unused.
42
+ */
43
+ function collectDeprecatedElementUsage({ schema, document, operation, operationName, variables, maxVariableDepth = DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH, maxVariableNodes = DEFAULT_DEPRECATION_MAX_VARIABLE_NODES, maxElements = DEFAULT_DEPRECATION_MAX_ELEMENTS, }) {
44
+ const collected = new Map();
45
+ const walk = { maxVariableDepth, remaining: maxVariableNodes, truncated: false };
46
+ const collect = (usage) => {
47
+ const key = `${usage.kind}:${usage.name}`;
48
+ // Already recorded is deduplication, not truncation — only a rejected *new* element is a loss.
49
+ if (collected.has(key))
50
+ return;
51
+ if (collected.size >= maxElements) {
52
+ walk.truncated = true;
53
+ return;
54
+ }
55
+ collected.set(key, usage);
56
+ };
57
+ const resolvedOperation = operation ?? getOperationAST(document, operationName ?? undefined);
58
+ collectFromDocument(schema, scopeToOperation(document, resolvedOperation), collect);
59
+ collectFromVariables(schema, resolvedOperation?.variableDefinitions, variables, walk, collect);
60
+ return {
61
+ elements: [...collected.values()].sort((a, b) => a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name)),
62
+ truncated: walk.truncated,
63
+ };
64
+ }
65
+ /**
66
+ * A document may carry several operations but only one runs, so anything the others select would be
67
+ * reported as usage that never happened. `separateOperations` keeps each operation with only the
68
+ * fragments it can reach, which is exactly the scope wanted here.
69
+ */
70
+ function scopeToOperation(document, operation) {
71
+ if (!operation)
72
+ return document;
73
+ let operationCount = 0;
74
+ for (const definition of document.definitions) {
75
+ if (definition.kind === Kind.OPERATION_DEFINITION)
76
+ operationCount++;
77
+ if (operationCount > 1)
78
+ return separateOperations(document)[operation.name?.value ?? ''] ?? document;
79
+ }
80
+ return document;
81
+ }
82
+ function collectFromDocument(schema, document, collect) {
83
+ const typeInfo = new TypeInfo(schema);
84
+ visit(document, visitWithTypeInfo(typeInfo, {
85
+ Field(_node, _key, _parent, _path, ancestors) {
86
+ const field = typeInfo.getFieldDef();
87
+ if (field?.deprecationReason == null)
88
+ return;
89
+ collect({
90
+ kind: 'output-field',
91
+ name: `${typeInfo.getParentType()?.name ?? UNKNOWN}.${field.name}`,
92
+ path: getPath(ancestors, field.name),
93
+ });
94
+ },
95
+ Argument(_node, _key, _parent, _path, ancestors) {
96
+ const argument = typeInfo.getArgument();
97
+ if (argument?.deprecationReason == null)
98
+ return;
99
+ // TypeInfo resolves an argument against the enclosing directive when there is one, so
100
+ // without this branch a directive's argument is reported as an argument of the field the
101
+ // directive is attached to — a field argument that does not exist.
102
+ const directive = typeInfo.getDirective();
103
+ collect({
104
+ kind: directive ? 'directive-argument' : 'argument',
105
+ name: directive
106
+ ? `@${directive.name}(${argument.name})`
107
+ : `${typeInfo.getParentType()?.name ?? UNKNOWN}.${typeInfo.getFieldDef()?.name ?? UNKNOWN}(${argument.name})`,
108
+ path: getPath(ancestors),
109
+ });
110
+ },
111
+ // Input-object fields written as literals in the document, including variable definition
112
+ // default values, which TypeInfo also tracks as input positions.
113
+ ObjectField(node, _key, _parent, _path, ancestors) {
114
+ const parentType = getNamedType(typeInfo.getParentInputType());
115
+ if (!isInputObjectType(parentType))
116
+ return;
117
+ const inputField = parentType.getFields()[node.name.value];
118
+ if (inputField?.deprecationReason == null)
119
+ return;
120
+ collect({
121
+ kind: 'input-field',
122
+ name: `${parentType.name}.${inputField.name}`,
123
+ path: getPath(ancestors, inputField.name),
124
+ });
125
+ },
126
+ EnumValue(node, _key, _parent, _path, ancestors) {
127
+ const enumType = getNamedType(typeInfo.getInputType());
128
+ if (!isEnumType(enumType))
129
+ return;
130
+ const enumValue = enumType.getValue(node.value);
131
+ if (enumValue?.deprecationReason == null)
132
+ return;
133
+ collect({
134
+ kind: 'enum-value',
135
+ name: `${enumType.name}.${enumValue.name}`,
136
+ path: getPath(ancestors),
137
+ });
138
+ },
139
+ }));
140
+ }
141
+ function isPathNode(node) {
142
+ return node.kind === Kind.FIELD || node.kind === Kind.OBJECT_FIELD;
143
+ }
144
+ function getPath(ancestors, leaf) {
145
+ const segments = ancestors
146
+ .filter((x) => !Array.isArray(x))
147
+ .filter(isPathNode)
148
+ .map((x) => x.name.value);
149
+ if (leaf)
150
+ segments.push(leaf);
151
+ return segments.join('.');
152
+ }
153
+ /**
154
+ * Walks each supplied variable value against its declared type, reporting every deprecated input
155
+ * field and enum value the client actually sent.
156
+ *
157
+ * Takes the definitions of the *resolved* operation rather than scanning the whole document: the
158
+ * same variable name may be declared with a different type in each operation, so only the executed
159
+ * one's declarations describe the supplied values.
160
+ */
161
+ function collectFromVariables(schema, variableDefinitions, variables, state, collect) {
162
+ if (variables == null)
163
+ return;
164
+ for (const variableDefinition of variableDefinitions ?? []) {
165
+ const variableName = variableDefinition.variable.name.value;
166
+ if (!Object.hasOwn(variables, variableName))
167
+ continue;
168
+ const type = typeFromAST(schema, variableDefinition.type);
169
+ if (!isInputType(type))
170
+ continue;
171
+ walkInputValue(type, variables[variableName], `$${variableName}`, 0, state, collect);
172
+ }
173
+ }
174
+ function walkInputValue(type, value, path, depth, state, collect) {
175
+ // A limit stopping the descent is a loss of telemetry, so it is recorded; a null value is simply
176
+ // nothing to walk, and must not be reported as one.
177
+ if (depth > state.maxVariableDepth || state.remaining <= 0) {
178
+ state.truncated = true;
179
+ return;
180
+ }
181
+ if (value == null)
182
+ return;
183
+ state.remaining--;
184
+ // Unwrapped inline rather than by recursing, so a nullability wrapper doesn't spend a node from
185
+ // the budget for a value that has not been descended into yet.
186
+ const unwrapped = isNonNullType(type) ? type.ofType : type;
187
+ if (isListType(unwrapped)) {
188
+ // GraphQL accepts a bare value in a list position, so handle both shapes.
189
+ const items = Array.isArray(value) ? value : [value];
190
+ for (const [index, item] of items.entries()) {
191
+ // Checked here rather than relying on the recursive call returning early: the loop itself is
192
+ // the unbounded part, since list length comes from the payload and each step builds a path.
193
+ if (state.remaining <= 0) {
194
+ state.truncated = true;
195
+ return;
196
+ }
197
+ walkInputValue(unwrapped.ofType, item, Array.isArray(value) ? `${path}.${index}` : path, depth + 1, state, collect);
198
+ }
199
+ return;
200
+ }
201
+ if (isEnumType(unwrapped)) {
202
+ // An unknown name is a coercion error graphql-js raises moments later; nothing to record here.
203
+ if (typeof value !== 'string')
204
+ return;
205
+ const enumValue = unwrapped.getValue(value);
206
+ if (enumValue?.deprecationReason != null) {
207
+ collect({ kind: 'enum-value', name: `${unwrapped.name}.${enumValue.name}`, path });
208
+ }
209
+ return;
210
+ }
211
+ // Scalars have no members that could carry a deprecation.
212
+ if (!isInputObjectType(unwrapped) || !isRecord(value))
213
+ return;
214
+ for (const field of Object.values(unwrapped.getFields())) {
215
+ if (state.remaining <= 0) {
216
+ state.truncated = true;
217
+ return;
218
+ }
219
+ if (!Object.hasOwn(value, field.name))
220
+ continue;
221
+ const fieldPath = `${path}.${field.name}`;
222
+ // Presence is the signal, explicit `null` included: a client still sending the field would
223
+ // break if it were removed from the schema.
224
+ if (field.deprecationReason != null) {
225
+ collect({ kind: 'input-field', name: `${unwrapped.name}.${field.name}`, path: fieldPath });
226
+ }
227
+ walkInputValue(field.type, value[field.name], fieldPath, depth + 1, state, collect);
228
+ }
229
+ }
230
+ function isRecord(value) {
231
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
232
+ }
233
+
234
+ export { DEFAULT_DEPRECATION_MAX_ELEMENTS, DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH, DEFAULT_DEPRECATION_MAX_VARIABLE_NODES, collectDeprecatedElementUsage };
235
+ //# sourceMappingURL=deprecation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deprecation.mjs","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":[],"mappings":"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"}
package/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './context';
2
+ export * from './deprecation';
2
3
  export * from './logging';
3
4
  export * from './operation';
4
5
  export * from './request-info';
package/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './context.mjs';
2
+ export * from './deprecation.mjs';
2
3
  export * from './logging.mjs';
3
4
  export * from './operation.mjs';
4
5
  export * from './request-info.mjs';
package/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './context';
2
+ export * from './deprecation';
2
3
  export * from './logging';
3
4
  export * from './operation';
4
5
  export * from './request-info';
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var context = require('./context.js');
4
+ var deprecation = require('./deprecation.js');
4
5
  var logging = require('./logging.js');
5
6
  var operation = require('./operation.js');
6
7
  var requestInfo = require('./request-info.js');
@@ -13,6 +14,10 @@ var utils = require('./utils.js');
13
14
 
14
15
  exports.createContextFactory = context.createContextFactory;
15
16
  exports.defaultCreateUser = context.defaultCreateUser;
17
+ exports.DEFAULT_DEPRECATION_MAX_ELEMENTS = deprecation.DEFAULT_DEPRECATION_MAX_ELEMENTS;
18
+ exports.DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = deprecation.DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH;
19
+ exports.DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = deprecation.DEFAULT_DEPRECATION_MAX_VARIABLE_NODES;
20
+ exports.collectDeprecatedElementUsage = deprecation.collectDeprecatedElementUsage;
16
21
  exports.logGraphQLOperation = logging.logGraphQLOperation;
17
22
  exports.logSubscriptionOperation = logging.logSubscriptionOperation;
18
23
  exports.extractAnonymousOperationName = operation.extractAnonymousOperationName;
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  export { createContextFactory, defaultCreateUser } from './context.mjs';
2
+ export { DEFAULT_DEPRECATION_MAX_ELEMENTS, DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH, DEFAULT_DEPRECATION_MAX_VARIABLE_NODES, collectDeprecatedElementUsage } from './deprecation.mjs';
2
3
  export { logGraphQLOperation, logSubscriptionOperation } from './logging.mjs';
3
4
  export { extractAnonymousOperationName, isIntrospectionQuery } from './operation.mjs';
4
5
  export { buildBaseRequestInfo, buildConnectRequestInfo, connectRequestBaseUrl, requestBaseUrl } from './request-info.mjs';
package/index.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
package/logging.d.cts CHANGED
@@ -3,6 +3,7 @@ import type { ExecutionArgs, GraphQLFormattedError } from 'graphql';
3
3
  import { OperationTypeNode } from 'graphql';
4
4
  import type { ExecutionResult } from 'graphql-ws';
5
5
  import type { GraphQLContext } from './context';
6
+ import type { DeprecatedElementUsage } from './deprecation';
6
7
  type LogFunction = Logger['info'];
7
8
  export type LoggerLogFunctions<T extends Logger> = {
8
9
  [Property in keyof T]: LogFunction;
@@ -57,6 +58,16 @@ export interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extend
57
58
  * Whether the operation is a subsequent payload of an incremental response.
58
59
  */
59
60
  isSubsequentPayload?: boolean;
61
+ /**
62
+ * Deprecated schema elements the operation used, as returned by `collectDeprecatedElementUsage`.
63
+ * Omitted from the log entry when empty.
64
+ */
65
+ deprecatedElements?: DeprecatedElementUsage[];
66
+ /**
67
+ * Whether a limit stopped deprecated element collection, meaning `deprecatedElements` may be
68
+ * incomplete. Omitted from the log entry when false.
69
+ */
70
+ deprecatedElementsTruncated?: boolean;
60
71
  /**
61
72
  * The logger to use.
62
73
  */
@@ -70,13 +81,17 @@ export interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extend
70
81
  * Logs a GraphQL operation in a consistent format with the option of including any additional data.
71
82
  * Top level and result entries with null or undefined values will be omitted.
72
83
  */
73
- export declare const logGraphQLOperation: <TLogger extends Logger = Logger>({ message, started, type, operationName, query, variables, result, logger, logLevel, ...rest }: GraphQLLogOperationInfo<TLogger>) => void;
74
- export declare const logSubscriptionOperation: <TLogger extends Logger = Logger>({ id, args, result, message, logLevel, resolveLogger, }: {
84
+ export declare const logGraphQLOperation: <TLogger extends Logger = Logger>({ message, started, type, operationName, query, variables, result, deprecatedElements, deprecatedElementsTruncated, logger, logLevel, ...rest }: GraphQLLogOperationInfo<TLogger>) => void;
85
+ export declare const logSubscriptionOperation: <TLogger extends Logger = Logger>({ id, args, result, message, logLevel, resolveLogger, includeDeprecatedElements, }: {
75
86
  id?: string;
76
87
  args: ExecutionArgs;
77
88
  result?: ExecutionResult;
78
89
  message?: string;
79
90
  logLevel?: keyof LoggerLogFunctions<TLogger>;
80
91
  resolveLogger?: (context: GraphQLContext) => TLogger;
92
+ /**
93
+ * If true, deprecated schema elements the operation uses are collected and logged.
94
+ */
95
+ includeDeprecatedElements?: boolean;
81
96
  }) => void;
82
97
  export {};
package/logging.d.mts CHANGED
@@ -3,6 +3,7 @@ import type { ExecutionArgs, GraphQLFormattedError } from 'graphql';
3
3
  import { OperationTypeNode } from 'graphql';
4
4
  import type { ExecutionResult } from 'graphql-ws';
5
5
  import type { GraphQLContext } from './context.mjs';
6
+ import type { DeprecatedElementUsage } from './deprecation.mjs';
6
7
  type LogFunction = Logger['info'];
7
8
  export type LoggerLogFunctions<T extends Logger> = {
8
9
  [Property in keyof T]: LogFunction;
@@ -57,6 +58,16 @@ export interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extend
57
58
  * Whether the operation is a subsequent payload of an incremental response.
58
59
  */
59
60
  isSubsequentPayload?: boolean;
61
+ /**
62
+ * Deprecated schema elements the operation used, as returned by `collectDeprecatedElementUsage`.
63
+ * Omitted from the log entry when empty.
64
+ */
65
+ deprecatedElements?: DeprecatedElementUsage[];
66
+ /**
67
+ * Whether a limit stopped deprecated element collection, meaning `deprecatedElements` may be
68
+ * incomplete. Omitted from the log entry when false.
69
+ */
70
+ deprecatedElementsTruncated?: boolean;
60
71
  /**
61
72
  * The logger to use.
62
73
  */
@@ -70,13 +81,17 @@ export interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extend
70
81
  * Logs a GraphQL operation in a consistent format with the option of including any additional data.
71
82
  * Top level and result entries with null or undefined values will be omitted.
72
83
  */
73
- export declare const logGraphQLOperation: <TLogger extends Logger = Logger>({ message, started, type, operationName, query, variables, result, logger, logLevel, ...rest }: GraphQLLogOperationInfo<TLogger>) => void;
74
- export declare const logSubscriptionOperation: <TLogger extends Logger = Logger>({ id, args, result, message, logLevel, resolveLogger, }: {
84
+ export declare const logGraphQLOperation: <TLogger extends Logger = Logger>({ message, started, type, operationName, query, variables, result, deprecatedElements, deprecatedElementsTruncated, logger, logLevel, ...rest }: GraphQLLogOperationInfo<TLogger>) => void;
85
+ export declare const logSubscriptionOperation: <TLogger extends Logger = Logger>({ id, args, result, message, logLevel, resolveLogger, includeDeprecatedElements, }: {
75
86
  id?: string;
76
87
  args: ExecutionArgs;
77
88
  result?: ExecutionResult;
78
89
  message?: string;
79
90
  logLevel?: keyof LoggerLogFunctions<TLogger>;
80
91
  resolveLogger?: (context: GraphQLContext) => TLogger;
92
+ /**
93
+ * If true, deprecated schema elements the operation uses are collected and logged.
94
+ */
95
+ includeDeprecatedElements?: boolean;
81
96
  }) => void;
82
97
  export {};
package/logging.d.ts CHANGED
@@ -3,6 +3,7 @@ import type { ExecutionArgs, GraphQLFormattedError } from 'graphql';
3
3
  import { OperationTypeNode } from 'graphql';
4
4
  import type { ExecutionResult } from 'graphql-ws';
5
5
  import type { GraphQLContext } from './context';
6
+ import type { DeprecatedElementUsage } from './deprecation';
6
7
  type LogFunction = Logger['info'];
7
8
  export type LoggerLogFunctions<T extends Logger> = {
8
9
  [Property in keyof T]: LogFunction;
@@ -57,6 +58,16 @@ export interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extend
57
58
  * Whether the operation is a subsequent payload of an incremental response.
58
59
  */
59
60
  isSubsequentPayload?: boolean;
61
+ /**
62
+ * Deprecated schema elements the operation used, as returned by `collectDeprecatedElementUsage`.
63
+ * Omitted from the log entry when empty.
64
+ */
65
+ deprecatedElements?: DeprecatedElementUsage[];
66
+ /**
67
+ * Whether a limit stopped deprecated element collection, meaning `deprecatedElements` may be
68
+ * incomplete. Omitted from the log entry when false.
69
+ */
70
+ deprecatedElementsTruncated?: boolean;
60
71
  /**
61
72
  * The logger to use.
62
73
  */
@@ -70,13 +81,17 @@ export interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extend
70
81
  * Logs a GraphQL operation in a consistent format with the option of including any additional data.
71
82
  * Top level and result entries with null or undefined values will be omitted.
72
83
  */
73
- export declare const logGraphQLOperation: <TLogger extends Logger = Logger>({ message, started, type, operationName, query, variables, result, logger, logLevel, ...rest }: GraphQLLogOperationInfo<TLogger>) => void;
74
- export declare const logSubscriptionOperation: <TLogger extends Logger = Logger>({ id, args, result, message, logLevel, resolveLogger, }: {
84
+ export declare const logGraphQLOperation: <TLogger extends Logger = Logger>({ message, started, type, operationName, query, variables, result, deprecatedElements, deprecatedElementsTruncated, logger, logLevel, ...rest }: GraphQLLogOperationInfo<TLogger>) => void;
85
+ export declare const logSubscriptionOperation: <TLogger extends Logger = Logger>({ id, args, result, message, logLevel, resolveLogger, includeDeprecatedElements, }: {
75
86
  id?: string;
76
87
  args: ExecutionArgs;
77
88
  result?: ExecutionResult;
78
89
  message?: string;
79
90
  logLevel?: keyof LoggerLogFunctions<TLogger>;
80
91
  resolveLogger?: (context: GraphQLContext) => TLogger;
92
+ /**
93
+ * If true, deprecated schema elements the operation uses are collected and logged.
94
+ */
95
+ includeDeprecatedElements?: boolean;
81
96
  }) => void;
82
97
  export {};
package/logging.js CHANGED
@@ -3,6 +3,7 @@
3
3
  var nodeCommon = require('@makerx/node-common');
4
4
  var compat = require('es-toolkit/compat');
5
5
  var graphql = require('graphql');
6
+ var deprecation = require('./deprecation.js');
6
7
  var operation = require('./operation.js');
7
8
  var utils = require('./utils.js');
8
9
 
@@ -10,7 +11,7 @@ var utils = require('./utils.js');
10
11
  * Logs a GraphQL operation in a consistent format with the option of including any additional data.
11
12
  * Top level and result entries with null or undefined values will be omitted.
12
13
  */
13
- const logGraphQLOperation = ({ message = 'GraphQL operation', started, type, operationName, query, variables, result, logger, logLevel = 'info', ...rest }) => {
14
+ const logGraphQLOperation = ({ message = 'GraphQL operation', started, type, operationName, query, variables, result, deprecatedElements, deprecatedElementsTruncated, logger, logLevel = 'info', ...rest }) => {
14
15
  const isIntrospection = query && operation.isIntrospectionQuery(query);
15
16
  if (nodeCommon.isLocalDev && isIntrospection)
16
17
  return;
@@ -22,17 +23,32 @@ const logGraphQLOperation = ({ message = 'GraphQL operation', started, type, ope
22
23
  duration: started ? Date.now() - started : undefined,
23
24
  result: result ? compat.omitBy(result, utils.isNil) : undefined,
24
25
  isIntrospectionQuery: isIntrospection || undefined,
26
+ deprecatedElements: deprecatedElements?.length ? deprecatedElements : undefined,
27
+ deprecatedElementsTruncated: deprecatedElementsTruncated || undefined,
25
28
  ...compat.omitBy(rest, utils.isNil),
26
29
  }, utils.isNil));
27
30
  };
28
- const logSubscriptionOperation = ({ id, args, result, message, logLevel, resolveLogger, }) => {
31
+ const logSubscriptionOperation = ({ id, args, result, message, logLevel, resolveLogger, includeDeprecatedElements, }) => {
29
32
  const logger = resolveLogger
30
33
  ? resolveLogger(args.contextValue)
31
34
  : args.contextValue.logger;
32
35
  if (!logger)
33
36
  return;
34
- const { operationName, variableValues, document } = args;
37
+ const { operationName, variableValues, document, schema } = args;
35
38
  const { data, ...resultWithoutData } = result ?? {};
39
+ let deprecatedElements;
40
+ let deprecatedElementsTruncated;
41
+ if (includeDeprecatedElements) {
42
+ try {
43
+ const usage = deprecation.collectDeprecatedElementUsage({ schema, document, operationName, variables: variableValues });
44
+ deprecatedElements = usage.elements;
45
+ deprecatedElementsTruncated = usage.truncated;
46
+ }
47
+ catch (error) {
48
+ // Telemetry must never take down the subscription that produced it.
49
+ logger.warn('Failed to collect deprecated schema element usage', { error, operationName });
50
+ }
51
+ }
36
52
  logGraphQLOperation({
37
53
  message,
38
54
  type: graphql.OperationTypeNode.SUBSCRIPTION,
@@ -41,6 +57,8 @@ const logSubscriptionOperation = ({ id, args, result, message, logLevel, resolve
41
57
  query: graphql.print(document),
42
58
  variables: variableValues,
43
59
  result: resultWithoutData,
60
+ deprecatedElements,
61
+ deprecatedElementsTruncated,
44
62
  logger,
45
63
  logLevel,
46
64
  });
package/logging.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"logging.js","sources":["../src/logging.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { isLocalDev } from '@makerx/node-common'\nimport { omitBy } from 'es-toolkit/compat'\nimport type { ExecutionArgs, GraphQLFormattedError } from 'graphql'\nimport { OperationTypeNode, print } from 'graphql'\nimport type { ExecutionResult } from 'graphql-ws'\nimport type { GraphQLContext } from './context'\nimport { isIntrospectionQuery } from './operation'\nimport { isNil } from './utils'\n\ntype LogFunction = Logger['info']\nexport type LoggerLogFunctions<T extends Logger> = {\n [Property in keyof T]: LogFunction\n}\n\n/**\n * Info for logging a GraphQL operation in a consistent format with the option of including any additional data.\n */\nexport interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extends Record<string, unknown> {\n /**\n * The message to log, defaults to 'GraphQL operation'.\n */\n message?: string\n /**\n * The timestamp when the operation started, if supplied, the duration will be logged.\n */\n started?: number\n /**\n * The type of GraphQL operation.\n */\n type?: OperationTypeNode | null\n /**\n * The name of the GraphQL operation.\n */\n operationName?: string | null\n /**\n * The formatted GraphQL query.\n */\n query?: string | null\n /**\n * The GraphQL variables.\n */\n variables?: Record<string, unknown> | null\n /**\n * The result of the GraphQL operation.\n * Generally, we don't log the data or extensions, just errors.\n */\n result?: {\n data?: Record<string, unknown> | null\n errors?: readonly GraphQLFormattedError[] | null\n extensions?: Record<string, unknown> | null\n hasNext?: boolean\n }\n /**\n * Whether the operation is an introspection query.\n */\n isIntrospectionQuery?: boolean\n /**\n * Whether the operation is part of an incremental response.\n */\n isIncrementalResponse?: boolean\n /**\n * Whether the operation is a subsequent payload of an incremental response.\n */\n isSubsequentPayload?: boolean\n /**\n * The logger to use.\n */\n logger: TLogger\n /**\n * The logger function to use, defaults to 'info'.\n */\n logLevel?: keyof LoggerLogFunctions<TLogger>\n}\n\n/**\n * Logs a GraphQL operation in a consistent format with the option of including any additional data.\n * Top level and result entries with null or undefined values will be omitted.\n */\nexport const logGraphQLOperation = <TLogger extends Logger = Logger>({\n message = 'GraphQL operation',\n started,\n type,\n operationName,\n query,\n variables,\n result,\n logger,\n logLevel = 'info',\n ...rest\n}: GraphQLLogOperationInfo<TLogger>) => {\n const isIntrospection = query && isIntrospectionQuery(query)\n if (isLocalDev && isIntrospection) return\n logger[logLevel as keyof Logger](\n message,\n omitBy(\n {\n type,\n operationName,\n query,\n variables: variables && Object.keys(variables).length > 0 ? variables : undefined,\n duration: started ? Date.now() - started : undefined,\n result: result ? omitBy(result, isNil) : undefined,\n isIntrospectionQuery: isIntrospection || undefined,\n ...omitBy(rest, isNil),\n },\n isNil,\n ),\n )\n}\n\nexport const logSubscriptionOperation = <TLogger extends Logger = Logger>({\n id,\n args,\n result,\n message,\n logLevel,\n resolveLogger,\n}: {\n id?: string\n args: ExecutionArgs\n result?: ExecutionResult\n message?: string\n logLevel?: keyof LoggerLogFunctions<TLogger>\n resolveLogger?: (context: GraphQLContext) => TLogger\n}) => {\n const logger = resolveLogger\n ? resolveLogger(args.contextValue as GraphQLContext)\n : ((args.contextValue as GraphQLContext).logger as TLogger)\n if (!logger) return\n\n const { operationName, variableValues, document } = args\n const { data, ...resultWithoutData } = result ?? {}\n\n logGraphQLOperation({\n message,\n type: OperationTypeNode.SUBSCRIPTION,\n subscriptionId: id,\n operationName,\n query: print(document),\n variables: variableValues,\n result: resultWithoutData,\n logger,\n logLevel,\n })\n}\n"],"names":["isIntrospectionQuery","isLocalDev","omitBy","isNil","OperationTypeNode","print"],"mappings":";;;;;;;;AA2EA;;;AAGG;AACI,MAAM,mBAAmB,GAAG,CAAkC,EACnE,OAAO,GAAG,mBAAmB,EAC7B,OAAO,EACP,IAAI,EACJ,aAAa,EACb,KAAK,EACL,SAAS,EACT,MAAM,EACN,MAAM,EACN,QAAQ,GAAG,MAAM,EACjB,GAAG,IAAI,EAC0B,KAAI;IACrC,MAAM,eAAe,GAAG,KAAK,IAAIA,8BAAoB,CAAC,KAAK,CAAC;IAC5D,IAAIC,qBAAU,IAAI,eAAe;QAAE;AACnC,IAAA,MAAM,CAAC,QAAwB,CAAC,CAC9B,OAAO,EACPC,aAAM,CACJ;QACE,IAAI;QACJ,aAAa;QACb,KAAK;QACL,SAAS,EAAE,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,SAAS,GAAG,SAAS;AACjF,QAAA,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,SAAS;AACpD,QAAA,MAAM,EAAE,MAAM,GAAGA,aAAM,CAAC,MAAM,EAAEC,WAAK,CAAC,GAAG,SAAS;QAClD,oBAAoB,EAAE,eAAe,IAAI,SAAS;AAClD,QAAA,GAAGD,aAAM,CAAC,IAAI,EAAEC,WAAK,CAAC;KACvB,EACDA,WAAK,CACN,CACF;AACH;AAEO,MAAM,wBAAwB,GAAG,CAAkC,EACxE,EAAE,EACF,IAAI,EACJ,MAAM,EACN,OAAO,EACP,QAAQ,EACR,aAAa,GAQd,KAAI;IACH,MAAM,MAAM,GAAG;AACb,UAAE,aAAa,CAAC,IAAI,CAAC,YAA8B;AACnD,UAAI,IAAI,CAAC,YAA+B,CAAC,MAAkB;AAC7D,IAAA,IAAI,CAAC,MAAM;QAAE;IAEb,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,IAAI;IACxD,MAAM,EAAE,IAAI,EAAE,GAAG,iBAAiB,EAAE,GAAG,MAAM,IAAI,EAAE;AAEnD,IAAA,mBAAmB,CAAC;QAClB,OAAO;QACP,IAAI,EAAEC,yBAAiB,CAAC,YAAY;AACpC,QAAA,cAAc,EAAE,EAAE;QAClB,aAAa;AACb,QAAA,KAAK,EAAEC,aAAK,CAAC,QAAQ,CAAC;AACtB,QAAA,SAAS,EAAE,cAAc;AACzB,QAAA,MAAM,EAAE,iBAAiB;QACzB,MAAM;QACN,QAAQ;AACT,KAAA,CAAC;AACJ;;;;;"}
1
+ {"version":3,"file":"logging.js","sources":["../src/logging.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { isLocalDev } from '@makerx/node-common'\nimport { omitBy } from 'es-toolkit/compat'\nimport type { ExecutionArgs, GraphQLFormattedError } from 'graphql'\nimport { OperationTypeNode, print } from 'graphql'\nimport type { ExecutionResult } from 'graphql-ws'\nimport type { GraphQLContext } from './context'\nimport type { DeprecatedElementUsage } from './deprecation'\nimport { collectDeprecatedElementUsage } from './deprecation'\nimport { isIntrospectionQuery } from './operation'\nimport { isNil } from './utils'\n\ntype LogFunction = Logger['info']\nexport type LoggerLogFunctions<T extends Logger> = {\n [Property in keyof T]: LogFunction\n}\n\n/**\n * Info for logging a GraphQL operation in a consistent format with the option of including any additional data.\n */\nexport interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extends Record<string, unknown> {\n /**\n * The message to log, defaults to 'GraphQL operation'.\n */\n message?: string\n /**\n * The timestamp when the operation started, if supplied, the duration will be logged.\n */\n started?: number\n /**\n * The type of GraphQL operation.\n */\n type?: OperationTypeNode | null\n /**\n * The name of the GraphQL operation.\n */\n operationName?: string | null\n /**\n * The formatted GraphQL query.\n */\n query?: string | null\n /**\n * The GraphQL variables.\n */\n variables?: Record<string, unknown> | null\n /**\n * The result of the GraphQL operation.\n * Generally, we don't log the data or extensions, just errors.\n */\n result?: {\n data?: Record<string, unknown> | null\n errors?: readonly GraphQLFormattedError[] | null\n extensions?: Record<string, unknown> | null\n hasNext?: boolean\n }\n /**\n * Whether the operation is an introspection query.\n */\n isIntrospectionQuery?: boolean\n /**\n * Whether the operation is part of an incremental response.\n */\n isIncrementalResponse?: boolean\n /**\n * Whether the operation is a subsequent payload of an incremental response.\n */\n isSubsequentPayload?: boolean\n /**\n * Deprecated schema elements the operation used, as returned by `collectDeprecatedElementUsage`.\n * Omitted from the log entry when empty.\n */\n deprecatedElements?: DeprecatedElementUsage[]\n /**\n * Whether a limit stopped deprecated element collection, meaning `deprecatedElements` may be\n * incomplete. Omitted from the log entry when false.\n */\n deprecatedElementsTruncated?: boolean\n /**\n * The logger to use.\n */\n logger: TLogger\n /**\n * The logger function to use, defaults to 'info'.\n */\n logLevel?: keyof LoggerLogFunctions<TLogger>\n}\n\n/**\n * Logs a GraphQL operation in a consistent format with the option of including any additional data.\n * Top level and result entries with null or undefined values will be omitted.\n */\nexport const logGraphQLOperation = <TLogger extends Logger = Logger>({\n message = 'GraphQL operation',\n started,\n type,\n operationName,\n query,\n variables,\n result,\n deprecatedElements,\n deprecatedElementsTruncated,\n logger,\n logLevel = 'info',\n ...rest\n}: GraphQLLogOperationInfo<TLogger>) => {\n const isIntrospection = query && isIntrospectionQuery(query)\n if (isLocalDev && isIntrospection) return\n logger[logLevel as keyof Logger](\n message,\n omitBy(\n {\n type,\n operationName,\n query,\n variables: variables && Object.keys(variables).length > 0 ? variables : undefined,\n duration: started ? Date.now() - started : undefined,\n result: result ? omitBy(result, isNil) : undefined,\n isIntrospectionQuery: isIntrospection || undefined,\n deprecatedElements: deprecatedElements?.length ? deprecatedElements : undefined,\n deprecatedElementsTruncated: deprecatedElementsTruncated || undefined,\n ...omitBy(rest, isNil),\n },\n isNil,\n ),\n )\n}\n\nexport const logSubscriptionOperation = <TLogger extends Logger = Logger>({\n id,\n args,\n result,\n message,\n logLevel,\n resolveLogger,\n includeDeprecatedElements,\n}: {\n id?: string\n args: ExecutionArgs\n result?: ExecutionResult\n message?: string\n logLevel?: keyof LoggerLogFunctions<TLogger>\n resolveLogger?: (context: GraphQLContext) => TLogger\n /**\n * If true, deprecated schema elements the operation uses are collected and logged.\n */\n includeDeprecatedElements?: boolean\n}) => {\n const logger = resolveLogger\n ? resolveLogger(args.contextValue as GraphQLContext)\n : ((args.contextValue as GraphQLContext).logger as TLogger)\n if (!logger) return\n\n const { operationName, variableValues, document, schema } = args\n const { data, ...resultWithoutData } = result ?? {}\n\n let deprecatedElements: DeprecatedElementUsage[] | undefined\n let deprecatedElementsTruncated: boolean | undefined\n if (includeDeprecatedElements) {\n try {\n const usage = collectDeprecatedElementUsage({ schema, document, operationName, variables: variableValues })\n deprecatedElements = usage.elements\n deprecatedElementsTruncated = usage.truncated\n } catch (error) {\n // Telemetry must never take down the subscription that produced it.\n logger.warn('Failed to collect deprecated schema element usage', { error, operationName })\n }\n }\n\n logGraphQLOperation({\n message,\n type: OperationTypeNode.SUBSCRIPTION,\n subscriptionId: id,\n operationName,\n query: print(document),\n variables: variableValues,\n result: resultWithoutData,\n deprecatedElements,\n deprecatedElementsTruncated,\n logger,\n logLevel,\n })\n}\n"],"names":["isIntrospectionQuery","isLocalDev","omitBy","isNil","collectDeprecatedElementUsage","OperationTypeNode","print"],"mappings":";;;;;;;;;AAuFA;;;AAGG;AACI,MAAM,mBAAmB,GAAG,CAAkC,EACnE,OAAO,GAAG,mBAAmB,EAC7B,OAAO,EACP,IAAI,EACJ,aAAa,EACb,KAAK,EACL,SAAS,EACT,MAAM,EACN,kBAAkB,EAClB,2BAA2B,EAC3B,MAAM,EACN,QAAQ,GAAG,MAAM,EACjB,GAAG,IAAI,EAC0B,KAAI;IACrC,MAAM,eAAe,GAAG,KAAK,IAAIA,8BAAoB,CAAC,KAAK,CAAC;IAC5D,IAAIC,qBAAU,IAAI,eAAe;QAAE;AACnC,IAAA,MAAM,CAAC,QAAwB,CAAC,CAC9B,OAAO,EACPC,aAAM,CACJ;QACE,IAAI;QACJ,aAAa;QACb,KAAK;QACL,SAAS,EAAE,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,SAAS,GAAG,SAAS;AACjF,QAAA,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,SAAS;AACpD,QAAA,MAAM,EAAE,MAAM,GAAGA,aAAM,CAAC,MAAM,EAAEC,WAAK,CAAC,GAAG,SAAS;QAClD,oBAAoB,EAAE,eAAe,IAAI,SAAS;QAClD,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;QAC/E,2BAA2B,EAAE,2BAA2B,IAAI,SAAS;AACrE,QAAA,GAAGD,aAAM,CAAC,IAAI,EAAEC,WAAK,CAAC;KACvB,EACDA,WAAK,CACN,CACF;AACH;MAEa,wBAAwB,GAAG,CAAkC,EACxE,EAAE,EACF,IAAI,EACJ,MAAM,EACN,OAAO,EACP,QAAQ,EACR,aAAa,EACb,yBAAyB,GAY1B,KAAI;IACH,MAAM,MAAM,GAAG;AACb,UAAE,aAAa,CAAC,IAAI,CAAC,YAA8B;AACnD,UAAI,IAAI,CAAC,YAA+B,CAAC,MAAkB;AAC7D,IAAA,IAAI,CAAC,MAAM;QAAE;IAEb,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI;IAChE,MAAM,EAAE,IAAI,EAAE,GAAG,iBAAiB,EAAE,GAAG,MAAM,IAAI,EAAE;AAEnD,IAAA,IAAI,kBAAwD;AAC5D,IAAA,IAAI,2BAAgD;IACpD,IAAI,yBAAyB,EAAE;AAC7B,QAAA,IAAI;AACF,YAAA,MAAM,KAAK,GAAGC,yCAA6B,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;AAC3G,YAAA,kBAAkB,GAAG,KAAK,CAAC,QAAQ;AACnC,YAAA,2BAA2B,GAAG,KAAK,CAAC,SAAS;QAC/C;QAAE,OAAO,KAAK,EAAE;;YAEd,MAAM,CAAC,IAAI,CAAC,mDAAmD,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC;QAC5F;IACF;AAEA,IAAA,mBAAmB,CAAC;QAClB,OAAO;QACP,IAAI,EAAEC,yBAAiB,CAAC,YAAY;AACpC,QAAA,cAAc,EAAE,EAAE;QAClB,aAAa;AACb,QAAA,KAAK,EAAEC,aAAK,CAAC,QAAQ,CAAC;AACtB,QAAA,SAAS,EAAE,cAAc;AACzB,QAAA,MAAM,EAAE,iBAAiB;QACzB,kBAAkB;QAClB,2BAA2B;QAC3B,MAAM;QACN,QAAQ;AACT,KAAA,CAAC;AACJ;;;;;"}
package/logging.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { isLocalDev } from '@makerx/node-common';
2
2
  import { omitBy } from 'es-toolkit/compat';
3
3
  import { print, OperationTypeNode } from 'graphql';
4
+ import { collectDeprecatedElementUsage } from './deprecation.mjs';
4
5
  import { isIntrospectionQuery } from './operation.mjs';
5
6
  import { isNil } from './utils.mjs';
6
7
 
@@ -8,7 +9,7 @@ import { isNil } from './utils.mjs';
8
9
  * Logs a GraphQL operation in a consistent format with the option of including any additional data.
9
10
  * Top level and result entries with null or undefined values will be omitted.
10
11
  */
11
- const logGraphQLOperation = ({ message = 'GraphQL operation', started, type, operationName, query, variables, result, logger, logLevel = 'info', ...rest }) => {
12
+ const logGraphQLOperation = ({ message = 'GraphQL operation', started, type, operationName, query, variables, result, deprecatedElements, deprecatedElementsTruncated, logger, logLevel = 'info', ...rest }) => {
12
13
  const isIntrospection = query && isIntrospectionQuery(query);
13
14
  if (isLocalDev && isIntrospection)
14
15
  return;
@@ -20,17 +21,32 @@ const logGraphQLOperation = ({ message = 'GraphQL operation', started, type, ope
20
21
  duration: started ? Date.now() - started : undefined,
21
22
  result: result ? omitBy(result, isNil) : undefined,
22
23
  isIntrospectionQuery: isIntrospection || undefined,
24
+ deprecatedElements: deprecatedElements?.length ? deprecatedElements : undefined,
25
+ deprecatedElementsTruncated: deprecatedElementsTruncated || undefined,
23
26
  ...omitBy(rest, isNil),
24
27
  }, isNil));
25
28
  };
26
- const logSubscriptionOperation = ({ id, args, result, message, logLevel, resolveLogger, }) => {
29
+ const logSubscriptionOperation = ({ id, args, result, message, logLevel, resolveLogger, includeDeprecatedElements, }) => {
27
30
  const logger = resolveLogger
28
31
  ? resolveLogger(args.contextValue)
29
32
  : args.contextValue.logger;
30
33
  if (!logger)
31
34
  return;
32
- const { operationName, variableValues, document } = args;
35
+ const { operationName, variableValues, document, schema } = args;
33
36
  const { data, ...resultWithoutData } = result ?? {};
37
+ let deprecatedElements;
38
+ let deprecatedElementsTruncated;
39
+ if (includeDeprecatedElements) {
40
+ try {
41
+ const usage = collectDeprecatedElementUsage({ schema, document, operationName, variables: variableValues });
42
+ deprecatedElements = usage.elements;
43
+ deprecatedElementsTruncated = usage.truncated;
44
+ }
45
+ catch (error) {
46
+ // Telemetry must never take down the subscription that produced it.
47
+ logger.warn('Failed to collect deprecated schema element usage', { error, operationName });
48
+ }
49
+ }
34
50
  logGraphQLOperation({
35
51
  message,
36
52
  type: OperationTypeNode.SUBSCRIPTION,
@@ -39,6 +55,8 @@ const logSubscriptionOperation = ({ id, args, result, message, logLevel, resolve
39
55
  query: print(document),
40
56
  variables: variableValues,
41
57
  result: resultWithoutData,
58
+ deprecatedElements,
59
+ deprecatedElementsTruncated,
42
60
  logger,
43
61
  logLevel,
44
62
  });