@makerx/graphql-core 3.2.0 → 3.4.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.
Files changed (51) hide show
  1. package/README.md +68 -10
  2. package/context.d.cts +7 -0
  3. package/context.d.mts +7 -0
  4. package/context.d.ts +7 -0
  5. package/context.js +6 -5
  6. package/context.js.map +1 -1
  7. package/context.mjs +6 -5
  8. package/context.mjs.map +1 -1
  9. package/deprecation.d.cts +81 -0
  10. package/deprecation.d.mts +81 -0
  11. package/deprecation.d.ts +81 -0
  12. package/deprecation.js +240 -0
  13. package/deprecation.js.map +1 -0
  14. package/deprecation.mjs +235 -0
  15. package/deprecation.mjs.map +1 -0
  16. package/index.d.cts +1 -0
  17. package/index.d.mts +1 -0
  18. package/index.d.ts +1 -0
  19. package/index.js +5 -0
  20. package/index.js.map +1 -1
  21. package/index.mjs +1 -0
  22. package/index.mjs.map +1 -1
  23. package/logging.d.cts +17 -2
  24. package/logging.d.mts +17 -2
  25. package/logging.d.ts +17 -2
  26. package/logging.js +21 -3
  27. package/logging.js.map +1 -1
  28. package/logging.mjs +21 -3
  29. package/logging.mjs.map +1 -1
  30. package/package.json +1 -1
  31. package/subscriptions/context.d.cts +7 -0
  32. package/subscriptions/context.d.mts +7 -0
  33. package/subscriptions/context.d.ts +7 -0
  34. package/subscriptions/context.js +5 -5
  35. package/subscriptions/context.js.map +1 -1
  36. package/subscriptions/context.mjs +6 -6
  37. package/subscriptions/context.mjs.map +1 -1
  38. package/subscriptions/server.d.cts +13 -2
  39. package/subscriptions/server.d.mts +13 -2
  40. package/subscriptions/server.d.ts +13 -2
  41. package/subscriptions/server.js +12 -6
  42. package/subscriptions/server.js.map +1 -1
  43. package/subscriptions/server.mjs +13 -7
  44. package/subscriptions/server.mjs.map +1 -1
  45. package/subscriptions/utils.d.cts +2 -0
  46. package/subscriptions/utils.d.mts +2 -0
  47. package/subscriptions/utils.d.ts +2 -0
  48. package/subscriptions/utils.js +2 -0
  49. package/subscriptions/utils.js.map +1 -1
  50. package/subscriptions/utils.mjs +2 -1
  51. package/subscriptions/utils.mjs.map +1 -1
package/README.md CHANGED
@@ -137,15 +137,15 @@ By default, if `claims` (decoded token `JwtPayload`) are available, the `GraphQL
137
137
 
138
138
  The User class adds some handy getters over raw claims (decodedJWT payload) and provides access to the JWT (access token) for on-behalf-of downstream authentication flows. Note this may represent a user or service principal (system) identity.
139
139
 
140
- | Property | Description |
141
- | -------- | ------------------------------------------------------------------------------------------------------------------- |
142
- | claims | The decoded JWT payload, set via the RequestInput.user field. |
143
- | token | The bearer token from the request authorization header. |
144
- | email | The user's email via coalesced claim values: email, emails, preferred_username, unique_name, upn. |
145
- | name | The user's name (via the name or given_name and family_name claims). |
146
- | id | The user's unique and immutable ID, useful for contextual differentiation e.g. session keys (the oid or sub claim). |
147
- | scopes | The user's scopes, via the scp claim split into an array of scopes. |
148
- | roles | The user's roles (via the roles claim). |
140
+ | Property | Description |
141
+ | -------- | --------------------------------------------------------------------------------------------------------------------------- |
142
+ | claims | The decoded JWT payload, set via the RequestInput.user field. |
143
+ | token | The access token. By default, the bearer token from the `Authorization` header (see [Token extraction](#token-extraction)). |
144
+ | email | The user's email via coalesced claim values: email, emails, preferred_username, unique_name, upn. |
145
+ | name | The user's name (via the name or given_name and family_name claims). |
146
+ | id | The user's unique and immutable ID, useful for contextual differentiation e.g. session keys (the oid or sub claim). |
147
+ | scopes | The user's scopes, via the scp claim split into an array of scopes. |
148
+ | roles | The user's roles (via the roles claim). |
149
149
 
150
150
  ### Custom user
151
151
 
@@ -163,6 +163,19 @@ const graphqlServer = createServer({
163
163
  })
164
164
  ```
165
165
 
166
+ ### Token extraction
167
+
168
+ The default `createUser` sets `User.token` to the bearer token from the `Authorization` header. If clients authenticate another way, for example with a JWT assertion header that a proxy adds to the request, supply `extractToken` to return the token:
169
+
170
+ ```ts
171
+ const createContext = createContextFactory({
172
+ requestLogger,
173
+ extractToken: ({ req }) => req.headers['x-goog-iap-jwt-assertion'] as string | undefined,
174
+ })
175
+ ```
176
+
177
+ `extractToken` is not called when you supply `createUser`. For subscriptions, see [GraphQL subscriptions](#graphql-subscriptions).
178
+
166
179
  ## Shield (authorization)
167
180
 
168
181
  The `shield` module provides typed helpers around [graphql-shield](https://the-guild.dev/graphql/shield) for defining authorization middleware against your generated `Resolvers` type.
@@ -281,6 +294,38 @@ Refer to the `GraphQLLogOperationInfo` type for the definition of input.
281
294
 
282
295
  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
296
 
297
+ ## Deprecation usage
298
+
299
+ ### collectDeprecatedElementUsage
300
+
301
+ Reports every `@deprecated` schema element an operation uses, so you can tell whether a deprecated element is safe to remove.
302
+
303
+ ```ts
304
+ const { elements, truncated } = collectDeprecatedElementUsage({ schema, document, operation, operationName, variables })
305
+ // elements: [{ kind: 'input-field', name: 'WidgetFilterInput.legacyId', path: '$input.legacyId' }]
306
+ // truncated: false
307
+ ```
308
+
309
+ Five kinds are detected: `output-field`, `argument`, `directive-argument`, `input-field` and `enum-value`.
310
+
311
+ 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.
312
+
313
+ 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`.
314
+
315
+ Behaviour worth knowing:
316
+
317
+ - **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.
318
+ - **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.
319
+ - **Presence is the signal, explicit `null` included.** A client still sending a field would break if it were removed.
320
+ - **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.
321
+ - **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`.
322
+ - **`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.
323
+
324
+ Two limitations to design around:
325
+
326
+ - **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.
327
+ - **`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.
328
+
284
329
  ## GraphQL subscriptions
285
330
 
286
331
  This library includes a `subscriptions` module to provide simple setup using the [GraphQL WS](https://the-guild.dev/graphql/ws) package.
@@ -331,10 +376,12 @@ This library includes a `subscriptions` module to provide simple setup using the
331
376
  1. Create a subscriptions server, using the ws-server cleanup function in your server lifecycle.
332
377
 
333
378
  The `useSubscriptionsServer` function sets up:
334
- - Auth token validation as part of establishing (or rejecting) the connection (behaviour defined by `verifyToken` and `requireAuth` args)
379
+ - Auth token validation as part of establishing (or rejecting) the connection (behaviour defined by `verifyToken`, `extractToken` and `requireAuth` args)
335
380
  - GraphQL context creation
336
381
  - Logging from the server `onConnect`, `onDisconnect`, `onOperation`, `onNext` and `onError` callbacks
337
382
 
383
+ 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.
384
+
338
385
  Example for Apollo Server (`wsServerCleanup` called in the `drainServer` plugin callback):
339
386
 
340
387
  ```ts
@@ -366,6 +413,17 @@ This library includes a `subscriptions` module to provide simple setup using the
366
413
 
367
414
  1. For authorisation, clients can include a connection parameter named `authorization` or `Authorization` using the HTTP header format `Bearer <token>`. Note: [Apollo Sandbox](https://studio.apollographql.com/sandbox/explorer) will include an `Authorization` connection parameter when you specify an HTTP `Authorization` header via the UI.
368
415
 
416
+ If the token arrives another way, supply an `extractToken` function to both `useSubscriptionsServer` (which passes the token to `verifyToken`) and `createSubscriptionContextFactory` (which sets it as `User.token`). It receives the `connectRequest` (the HTTP upgrade request) and the `connectionParams`, and returns the token or `undefined`. For example, to read a JWT assertion header that a proxy adds to the upgrade request (browsers cannot set custom headers on a websocket request):
417
+
418
+ ```ts
419
+ const extractToken: ExtractSubscriptionToken = ({ connectRequest }) =>
420
+ connectRequest.headers['x-goog-iap-jwt-assertion'] as string | undefined
421
+
422
+ const createSubscriptionContext = createSubscriptionContextFactory({ requestLogger, extractToken })
423
+
424
+ useSubscriptionsServer({ schema, httpServer, logger, createSubscriptionContext, extractToken, requireAuth: true, verifyToken })
425
+ ```
426
+
369
427
  ### wrapSubscriptionIterator
370
428
 
371
429
  Wraps an `AsyncIterableIterator` (e.g. one returned by a pubsub `asyncIterableIterator(...)` call) with two optional behaviours:
package/context.d.cts CHANGED
@@ -51,12 +51,19 @@ export interface ContextInput {
51
51
  export type CreateContext<TContext = GraphQLContext> = (input: ContextInput) => Promise<TContext>;
52
52
  export type CreateRequestLogger<TUser = User | undefined, TLogger extends Logger = Logger> = (requestMetadata: Record<string, unknown>, user: TUser) => TLogger;
53
53
  export type AugmentRequestInfo = (input: ContextInput) => Record<string, unknown>;
54
+ export type ExtractToken = (input: ContextInput) => string | undefined;
54
55
  export type CreateContextConfig<TUser = User | undefined, TAugment extends Record<string, unknown> = Record<string, never>, TLogger extends Logger = Logger> = {
55
56
  requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger;
56
57
  augmentRequestInfo?: AugmentRequestInfo;
57
58
  claimsToLog?: string[];
58
59
  requestInfoToLog?: Array<RequestInfoLogKey>;
59
60
  augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>;
61
+ /**
62
+ * Returns the token that the default `createUser` sets as `User.token`, e.g. from a JWT assertion
63
+ * header. Not called when you supply `createUser`. Defaults to the bearer token in the
64
+ * `Authorization` header.
65
+ */
66
+ extractToken?: ExtractToken;
60
67
  } & ([User | undefined] extends [TUser] ? {
61
68
  createUser?: CreateUser<TUser>;
62
69
  } : {
package/context.d.mts CHANGED
@@ -51,12 +51,19 @@ export interface ContextInput {
51
51
  export type CreateContext<TContext = GraphQLContext> = (input: ContextInput) => Promise<TContext>;
52
52
  export type CreateRequestLogger<TUser = User | undefined, TLogger extends Logger = Logger> = (requestMetadata: Record<string, unknown>, user: TUser) => TLogger;
53
53
  export type AugmentRequestInfo = (input: ContextInput) => Record<string, unknown>;
54
+ export type ExtractToken = (input: ContextInput) => string | undefined;
54
55
  export type CreateContextConfig<TUser = User | undefined, TAugment extends Record<string, unknown> = Record<string, never>, TLogger extends Logger = Logger> = {
55
56
  requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger;
56
57
  augmentRequestInfo?: AugmentRequestInfo;
57
58
  claimsToLog?: string[];
58
59
  requestInfoToLog?: Array<RequestInfoLogKey>;
59
60
  augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>;
61
+ /**
62
+ * Returns the token that the default `createUser` sets as `User.token`, e.g. from a JWT assertion
63
+ * header. Not called when you supply `createUser`. Defaults to the bearer token in the
64
+ * `Authorization` header.
65
+ */
66
+ extractToken?: ExtractToken;
60
67
  } & ([User | undefined] extends [TUser] ? {
61
68
  createUser?: CreateUser<TUser>;
62
69
  } : {
package/context.d.ts CHANGED
@@ -51,12 +51,19 @@ export interface ContextInput {
51
51
  export type CreateContext<TContext = GraphQLContext> = (input: ContextInput) => Promise<TContext>;
52
52
  export type CreateRequestLogger<TUser = User | undefined, TLogger extends Logger = Logger> = (requestMetadata: Record<string, unknown>, user: TUser) => TLogger;
53
53
  export type AugmentRequestInfo = (input: ContextInput) => Record<string, unknown>;
54
+ export type ExtractToken = (input: ContextInput) => string | undefined;
54
55
  export type CreateContextConfig<TUser = User | undefined, TAugment extends Record<string, unknown> = Record<string, never>, TLogger extends Logger = Logger> = {
55
56
  requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger;
56
57
  augmentRequestInfo?: AugmentRequestInfo;
57
58
  claimsToLog?: string[];
58
59
  requestInfoToLog?: Array<RequestInfoLogKey>;
59
60
  augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>;
61
+ /**
62
+ * Returns the token that the default `createUser` sets as `User.token`, e.g. from a JWT assertion
63
+ * header. Not called when you supply `createUser`. Defaults to the bearer token in the
64
+ * `Authorization` header.
65
+ */
66
+ extractToken?: ExtractToken;
60
67
  } & ([User | undefined] extends [TUser] ? {
61
68
  createUser?: CreateUser<TUser>;
62
69
  } : {
package/context.js CHANGED
@@ -8,7 +8,7 @@ const createContextFactory = (config) => {
8
8
  const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config;
9
9
  // The conditional type on CreateContextConfig guarantees `createUser` is provided when TUser is
10
10
  // narrower than `User | undefined`, so defaulting to defaultCreateUser is sound here.
11
- const createUser = (config.createUser ?? defaultCreateUser);
11
+ const createUser = (config.createUser ?? buildDefaultCreateUser(config.extractToken ?? defaultExtractToken));
12
12
  return async (input) => {
13
13
  const { req, claims, context } = input;
14
14
  // build base request info from the request
@@ -57,12 +57,13 @@ const createContextFactory = (config) => {
57
57
  return augmentedGraphQLContext;
58
58
  };
59
59
  };
60
- const defaultCreateUser = ({ req, claims }) => {
61
- if (!claims)
60
+ const defaultExtractToken = ({ req }) => req.headers.authorization?.startsWith('Bearer') ? req.headers.authorization.substring(7) : undefined;
61
+ const buildDefaultCreateUser = (extractToken) => (input) => {
62
+ if (!input.claims)
62
63
  return Promise.resolve(undefined);
63
- const accessToken = req.headers.authorization?.startsWith('Bearer') ? (req.headers.authorization?.substring(7) ?? '') : '';
64
- return Promise.resolve(new User.User(claims, accessToken));
64
+ return Promise.resolve(new User.User(input.claims, extractToken(input) ?? ''));
65
65
  };
66
+ const defaultCreateUser = buildDefaultCreateUser(defaultExtractToken);
66
67
 
67
68
  exports.createContextFactory = createContextFactory;
68
69
  exports.defaultCreateUser = defaultCreateUser;
package/context.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sources":["../src/context.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { pick } from 'es-toolkit/compat'\nimport type { Request } from 'express'\nimport { buildBaseRequestInfo, type BaseRequestInfo } from './request-info'\nimport { User } from './User'\n\nexport interface GraphQLContext<\n TLogger extends Logger = Logger,\n TRequestInfo extends BaseRequestInfo = RequestInfo,\n TUser = User | undefined,\n> {\n logger: TLogger\n requestInfo: TRequestInfo\n user: TUser\n started: number\n}\n\nexport type AnyGraphqlContext = GraphQLContext<any, any, any>\n\nexport interface LambdaContext {\n functionName?: string\n awsRequestId?: string\n}\nexport type LambdaEvent = never\nexport type LambdaRequestInfo = BaseRequestInfo & LambdaContext\nexport type RequestInfo = BaseRequestInfo | LambdaRequestInfo\n\n// Strips the `[key: string]: unknown` index signature so `keyof` returns only the declared keys.\ntype KnownKeys<T> = keyof {\n [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]\n}\n// Known keys for autocomplete, plus `(string & {})` to keep arbitrary augmented fields assignable.\nexport type RequestInfoLogKey = KnownKeys<BaseRequestInfo> | keyof LambdaContext | (string & {})\n\n// standard claims https://datatracker.ietf.org/doc/html/rfc7519#section-4.1\nexport interface JwtPayload {\n [key: string]: unknown\n name?: string | undefined\n email?: string | undefined\n given_name?: string | undefined\n family_name?: string | undefined\n preferred_username?: string | undefined\n upn?: string | undefined\n unique_name?: string | undefined\n oid?: string | undefined\n iss?: string | undefined\n sub?: string | undefined\n aud?: string | string[] | undefined\n roles?: string[] | undefined\n scp?: string | undefined\n exp?: number | undefined\n nbf?: number | undefined\n iat?: number | undefined\n jti?: string | undefined\n}\n\nexport type InferUserFromContext<TContext extends AnyGraphqlContext> =\n TContext extends GraphQLContext<any, any, infer TUser> ? TUser : never\nexport type CreateUser<T = User | undefined> = (input: Omit<ContextInput, 'createUser'>) => Promise<T> | T\nexport interface ContextInput {\n req: Request\n claims?: JwtPayload\n context?: LambdaContext\n event?: LambdaEvent\n}\nexport type CreateContext<TContext = GraphQLContext> = (input: ContextInput) => Promise<TContext>\nexport type CreateRequestLogger<TUser = User | undefined, TLogger extends Logger = Logger> = (\n requestMetadata: Record<string, unknown>,\n user: TUser,\n) => TLogger\nexport type AugmentRequestInfo = (input: ContextInput) => Record<string, unknown>\n\n// `createUser` is optional when TUser is compatible with the default `User | undefined`\n// (i.e. `defaultCreateUser` can satisfy it), and required when TUser is narrower.\nexport type CreateContextConfig<\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n> = {\n requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger\n augmentRequestInfo?: AugmentRequestInfo\n claimsToLog?: string[]\n requestInfoToLog?: Array<RequestInfoLogKey>\n augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>\n} & ([User | undefined] extends [TUser] ? { createUser?: CreateUser<TUser> } : { createUser: CreateUser<TUser> })\n\nexport const createContextFactory = <\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n>(\n config: CreateContextConfig<TUser, TAugment, TLogger>,\n): CreateContext<GraphQLContext<TLogger, RequestInfo, TUser> & TAugment> => {\n const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config\n // The conditional type on CreateContextConfig guarantees `createUser` is provided when TUser is\n // narrower than `User | undefined`, so defaulting to defaultCreateUser is sound here.\n const createUser = (config.createUser ?? defaultCreateUser) as CreateUser<TUser>\n\n return async (input: ContextInput) => {\n const { req, claims, context } = input\n\n // build base request info from the request\n const baseRequestInfo: BaseRequestInfo = buildBaseRequestInfo(req)\n\n // add lambda info from the context, if present\n let lambdaRequestInfo: LambdaRequestInfo | undefined\n if (context) {\n const { awsRequestId, functionName } = context\n lambdaRequestInfo = {\n awsRequestId,\n functionName,\n ...baseRequestInfo,\n }\n }\n\n // augment request info with the supplied function, if present\n let requestInfo: RequestInfo = lambdaRequestInfo ?? baseRequestInfo\n if (augmentRequestInfo)\n requestInfo = {\n ...requestInfo,\n ...augmentRequestInfo(input),\n }\n\n const user = await createUser(input)\n\n // create request logger\n let logger: TLogger\n if (typeof requestLogger === 'function') {\n // build request logger metadata\n const requestLoggerMetadata: Record<string, unknown> = {}\n // add request info to log\n if (requestInfoToLog?.length) requestLoggerMetadata.request = pick(requestInfo, requestInfoToLog)\n // add user claims to log\n if (claims && claimsToLog?.length) requestLoggerMetadata.user = pick(claims, claimsToLog)\n // build the request logger\n logger = requestLogger(requestLoggerMetadata, user)\n } else logger = requestLogger\n\n const graphqlContext: GraphQLContext<TLogger, RequestInfo, TUser> = {\n requestInfo,\n logger,\n user,\n started: Date.now(),\n }\n\n const augmentedGraphQLContext = augmentContext ? { ...graphqlContext, ...(await augmentContext(graphqlContext)) } : graphqlContext\n\n return augmentedGraphQLContext as GraphQLContext<TLogger, RequestInfo, TUser> & TAugment\n }\n}\n\nexport const defaultCreateUser: CreateUser<User | undefined> = ({ req, claims }) => {\n if (!claims) return Promise.resolve(undefined)\n const accessToken = req.headers.authorization?.startsWith('Bearer') ? (req.headers.authorization?.substring(7) ?? '') : ''\n return Promise.resolve(new User(claims, accessToken))\n}\n"],"names":["buildBaseRequestInfo","requestInfo","pick","User"],"mappings":";;;;;;AAsFO,MAAM,oBAAoB,GAAG,CAKlC,MAAqD,KACoB;AACzE,IAAA,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,GAAG,MAAM;;;IAGnG,MAAM,UAAU,IAAI,MAAM,CAAC,UAAU,IAAI,iBAAiB,CAAsB;AAEhF,IAAA,OAAO,OAAO,KAAmB,KAAI;QACnC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK;;AAGtC,QAAA,MAAM,eAAe,GAAoBA,gCAAoB,CAAC,GAAG,CAAC;;AAGlE,QAAA,IAAI,iBAAgD;QACpD,IAAI,OAAO,EAAE;AACX,YAAA,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO;AAC9C,YAAA,iBAAiB,GAAG;gBAClB,YAAY;gBACZ,YAAY;AACZ,gBAAA,GAAG,eAAe;aACnB;QACH;;AAGA,QAAA,IAAIC,aAAW,GAAgB,iBAAiB,IAAI,eAAe;AACnE,QAAA,IAAI,kBAAkB;AACpB,YAAAA,aAAW,GAAG;AACZ,gBAAA,GAAGA,aAAW;gBACd,GAAG,kBAAkB,CAAC,KAAK,CAAC;aAC7B;AAEH,QAAA,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC;;AAGpC,QAAA,IAAI,MAAe;AACnB,QAAA,IAAI,OAAO,aAAa,KAAK,UAAU,EAAE;;YAEvC,MAAM,qBAAqB,GAA4B,EAAE;;YAEzD,IAAI,gBAAgB,EAAE,MAAM;gBAAE,qBAAqB,CAAC,OAAO,GAAGC,WAAI,CAACD,aAAW,EAAE,gBAAgB,CAAC;;AAEjG,YAAA,IAAI,MAAM,IAAI,WAAW,EAAE,MAAM;gBAAE,qBAAqB,CAAC,IAAI,GAAGC,WAAI,CAAC,MAAM,EAAE,WAAW,CAAC;;AAEzF,YAAA,MAAM,GAAG,aAAa,CAAC,qBAAqB,EAAE,IAAI,CAAC;QACrD;;YAAO,MAAM,GAAG,aAAa;AAE7B,QAAA,MAAM,cAAc,GAAgD;yBAClED,aAAW;YACX,MAAM;YACN,IAAI;AACJ,YAAA,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE;SACpB;QAED,MAAM,uBAAuB,GAAG,cAAc,GAAG,EAAE,GAAG,cAAc,EAAE,IAAI,MAAM,cAAc,CAAC,cAAc,CAAC,CAAC,EAAE,GAAG,cAAc;AAElI,QAAA,OAAO,uBAAiF;AAC1F,IAAA,CAAC;AACH;AAEO,MAAM,iBAAiB,GAAiC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAI;AACjF,IAAA,IAAI,CAAC,MAAM;AAAE,QAAA,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;AAC9C,IAAA,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,UAAU,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE;AAC1H,IAAA,OAAO,OAAO,CAAC,OAAO,CAAC,IAAIE,SAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AACvD;;;;;"}
1
+ {"version":3,"file":"context.js","sources":["../src/context.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { pick } from 'es-toolkit/compat'\nimport type { Request } from 'express'\nimport { buildBaseRequestInfo, type BaseRequestInfo } from './request-info'\nimport { User } from './User'\n\nexport interface GraphQLContext<\n TLogger extends Logger = Logger,\n TRequestInfo extends BaseRequestInfo = RequestInfo,\n TUser = User | undefined,\n> {\n logger: TLogger\n requestInfo: TRequestInfo\n user: TUser\n started: number\n}\n\nexport type AnyGraphqlContext = GraphQLContext<any, any, any>\n\nexport interface LambdaContext {\n functionName?: string\n awsRequestId?: string\n}\nexport type LambdaEvent = never\nexport type LambdaRequestInfo = BaseRequestInfo & LambdaContext\nexport type RequestInfo = BaseRequestInfo | LambdaRequestInfo\n\n// Strips the `[key: string]: unknown` index signature so `keyof` returns only the declared keys.\ntype KnownKeys<T> = keyof {\n [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]\n}\n// Known keys for autocomplete, plus `(string & {})` to keep arbitrary augmented fields assignable.\nexport type RequestInfoLogKey = KnownKeys<BaseRequestInfo> | keyof LambdaContext | (string & {})\n\n// standard claims https://datatracker.ietf.org/doc/html/rfc7519#section-4.1\nexport interface JwtPayload {\n [key: string]: unknown\n name?: string | undefined\n email?: string | undefined\n given_name?: string | undefined\n family_name?: string | undefined\n preferred_username?: string | undefined\n upn?: string | undefined\n unique_name?: string | undefined\n oid?: string | undefined\n iss?: string | undefined\n sub?: string | undefined\n aud?: string | string[] | undefined\n roles?: string[] | undefined\n scp?: string | undefined\n exp?: number | undefined\n nbf?: number | undefined\n iat?: number | undefined\n jti?: string | undefined\n}\n\nexport type InferUserFromContext<TContext extends AnyGraphqlContext> =\n TContext extends GraphQLContext<any, any, infer TUser> ? TUser : never\nexport type CreateUser<T = User | undefined> = (input: Omit<ContextInput, 'createUser'>) => Promise<T> | T\nexport interface ContextInput {\n req: Request\n claims?: JwtPayload\n context?: LambdaContext\n event?: LambdaEvent\n}\nexport type CreateContext<TContext = GraphQLContext> = (input: ContextInput) => Promise<TContext>\nexport type CreateRequestLogger<TUser = User | undefined, TLogger extends Logger = Logger> = (\n requestMetadata: Record<string, unknown>,\n user: TUser,\n) => TLogger\nexport type AugmentRequestInfo = (input: ContextInput) => Record<string, unknown>\nexport type ExtractToken = (input: ContextInput) => string | undefined\n\n// `createUser` is optional when TUser is compatible with the default `User | undefined`\n// (i.e. `defaultCreateUser` can satisfy it), and required when TUser is narrower.\nexport type CreateContextConfig<\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n> = {\n requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger\n augmentRequestInfo?: AugmentRequestInfo\n claimsToLog?: string[]\n requestInfoToLog?: Array<RequestInfoLogKey>\n augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>\n /**\n * Returns the token that the default `createUser` sets as `User.token`, e.g. from a JWT assertion\n * header. Not called when you supply `createUser`. Defaults to the bearer token in the\n * `Authorization` header.\n */\n extractToken?: ExtractToken\n} & ([User | undefined] extends [TUser] ? { createUser?: CreateUser<TUser> } : { createUser: CreateUser<TUser> })\n\nexport const createContextFactory = <\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n>(\n config: CreateContextConfig<TUser, TAugment, TLogger>,\n): CreateContext<GraphQLContext<TLogger, RequestInfo, TUser> & TAugment> => {\n const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config\n // The conditional type on CreateContextConfig guarantees `createUser` is provided when TUser is\n // narrower than `User | undefined`, so defaulting to defaultCreateUser is sound here.\n const createUser = (config.createUser ?? buildDefaultCreateUser(config.extractToken ?? defaultExtractToken)) as CreateUser<TUser>\n\n return async (input: ContextInput) => {\n const { req, claims, context } = input\n\n // build base request info from the request\n const baseRequestInfo: BaseRequestInfo = buildBaseRequestInfo(req)\n\n // add lambda info from the context, if present\n let lambdaRequestInfo: LambdaRequestInfo | undefined\n if (context) {\n const { awsRequestId, functionName } = context\n lambdaRequestInfo = {\n awsRequestId,\n functionName,\n ...baseRequestInfo,\n }\n }\n\n // augment request info with the supplied function, if present\n let requestInfo: RequestInfo = lambdaRequestInfo ?? baseRequestInfo\n if (augmentRequestInfo)\n requestInfo = {\n ...requestInfo,\n ...augmentRequestInfo(input),\n }\n\n const user = await createUser(input)\n\n // create request logger\n let logger: TLogger\n if (typeof requestLogger === 'function') {\n // build request logger metadata\n const requestLoggerMetadata: Record<string, unknown> = {}\n // add request info to log\n if (requestInfoToLog?.length) requestLoggerMetadata.request = pick(requestInfo, requestInfoToLog)\n // add user claims to log\n if (claims && claimsToLog?.length) requestLoggerMetadata.user = pick(claims, claimsToLog)\n // build the request logger\n logger = requestLogger(requestLoggerMetadata, user)\n } else logger = requestLogger\n\n const graphqlContext: GraphQLContext<TLogger, RequestInfo, TUser> = {\n requestInfo,\n logger,\n user,\n started: Date.now(),\n }\n\n const augmentedGraphQLContext = augmentContext ? { ...graphqlContext, ...(await augmentContext(graphqlContext)) } : graphqlContext\n\n return augmentedGraphQLContext as GraphQLContext<TLogger, RequestInfo, TUser> & TAugment\n }\n}\n\nconst defaultExtractToken: ExtractToken = ({ req }) =>\n req.headers.authorization?.startsWith('Bearer') ? req.headers.authorization.substring(7) : undefined\n\nconst buildDefaultCreateUser =\n (extractToken: ExtractToken): CreateUser<User | undefined> =>\n (input) => {\n if (!input.claims) return Promise.resolve(undefined)\n return Promise.resolve(new User(input.claims, extractToken(input) ?? ''))\n }\n\nexport const defaultCreateUser = buildDefaultCreateUser(defaultExtractToken)\n"],"names":["buildBaseRequestInfo","requestInfo","pick","User"],"mappings":";;;;;;AA6FO,MAAM,oBAAoB,GAAG,CAKlC,MAAqD,KACoB;AACzE,IAAA,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,GAAG,MAAM;;;AAGnG,IAAA,MAAM,UAAU,IAAI,MAAM,CAAC,UAAU,IAAI,sBAAsB,CAAC,MAAM,CAAC,YAAY,IAAI,mBAAmB,CAAC,CAAsB;AAEjI,IAAA,OAAO,OAAO,KAAmB,KAAI;QACnC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK;;AAGtC,QAAA,MAAM,eAAe,GAAoBA,gCAAoB,CAAC,GAAG,CAAC;;AAGlE,QAAA,IAAI,iBAAgD;QACpD,IAAI,OAAO,EAAE;AACX,YAAA,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO;AAC9C,YAAA,iBAAiB,GAAG;gBAClB,YAAY;gBACZ,YAAY;AACZ,gBAAA,GAAG,eAAe;aACnB;QACH;;AAGA,QAAA,IAAIC,aAAW,GAAgB,iBAAiB,IAAI,eAAe;AACnE,QAAA,IAAI,kBAAkB;AACpB,YAAAA,aAAW,GAAG;AACZ,gBAAA,GAAGA,aAAW;gBACd,GAAG,kBAAkB,CAAC,KAAK,CAAC;aAC7B;AAEH,QAAA,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC;;AAGpC,QAAA,IAAI,MAAe;AACnB,QAAA,IAAI,OAAO,aAAa,KAAK,UAAU,EAAE;;YAEvC,MAAM,qBAAqB,GAA4B,EAAE;;YAEzD,IAAI,gBAAgB,EAAE,MAAM;gBAAE,qBAAqB,CAAC,OAAO,GAAGC,WAAI,CAACD,aAAW,EAAE,gBAAgB,CAAC;;AAEjG,YAAA,IAAI,MAAM,IAAI,WAAW,EAAE,MAAM;gBAAE,qBAAqB,CAAC,IAAI,GAAGC,WAAI,CAAC,MAAM,EAAE,WAAW,CAAC;;AAEzF,YAAA,MAAM,GAAG,aAAa,CAAC,qBAAqB,EAAE,IAAI,CAAC;QACrD;;YAAO,MAAM,GAAG,aAAa;AAE7B,QAAA,MAAM,cAAc,GAAgD;yBAClED,aAAW;YACX,MAAM;YACN,IAAI;AACJ,YAAA,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE;SACpB;QAED,MAAM,uBAAuB,GAAG,cAAc,GAAG,EAAE,GAAG,cAAc,EAAE,IAAI,MAAM,cAAc,CAAC,cAAc,CAAC,CAAC,EAAE,GAAG,cAAc;AAElI,QAAA,OAAO,uBAAiF;AAC1F,IAAA,CAAC;AACH;AAEA,MAAM,mBAAmB,GAAiB,CAAC,EAAE,GAAG,EAAE,KAChD,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS;AAEtG,MAAM,sBAAsB,GAC1B,CAAC,YAA0B,KAC3B,CAAC,KAAK,KAAI;IACR,IAAI,CAAC,KAAK,CAAC,MAAM;AAAE,QAAA,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;AACpD,IAAA,OAAO,OAAO,CAAC,OAAO,CAAC,IAAIE,SAAI,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AAC3E,CAAC;MAEU,iBAAiB,GAAG,sBAAsB,CAAC,mBAAmB;;;;;"}
package/context.mjs CHANGED
@@ -6,7 +6,7 @@ const createContextFactory = (config) => {
6
6
  const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config;
7
7
  // The conditional type on CreateContextConfig guarantees `createUser` is provided when TUser is
8
8
  // narrower than `User | undefined`, so defaulting to defaultCreateUser is sound here.
9
- const createUser = (config.createUser ?? defaultCreateUser);
9
+ const createUser = (config.createUser ?? buildDefaultCreateUser(config.extractToken ?? defaultExtractToken));
10
10
  return async (input) => {
11
11
  const { req, claims, context } = input;
12
12
  // build base request info from the request
@@ -55,12 +55,13 @@ const createContextFactory = (config) => {
55
55
  return augmentedGraphQLContext;
56
56
  };
57
57
  };
58
- const defaultCreateUser = ({ req, claims }) => {
59
- if (!claims)
58
+ const defaultExtractToken = ({ req }) => req.headers.authorization?.startsWith('Bearer') ? req.headers.authorization.substring(7) : undefined;
59
+ const buildDefaultCreateUser = (extractToken) => (input) => {
60
+ if (!input.claims)
60
61
  return Promise.resolve(undefined);
61
- const accessToken = req.headers.authorization?.startsWith('Bearer') ? (req.headers.authorization?.substring(7) ?? '') : '';
62
- return Promise.resolve(new User(claims, accessToken));
62
+ return Promise.resolve(new User(input.claims, extractToken(input) ?? ''));
63
63
  };
64
+ const defaultCreateUser = buildDefaultCreateUser(defaultExtractToken);
64
65
 
65
66
  export { createContextFactory, defaultCreateUser };
66
67
  //# sourceMappingURL=context.mjs.map
package/context.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"context.mjs","sources":["../src/context.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { pick } from 'es-toolkit/compat'\nimport type { Request } from 'express'\nimport { buildBaseRequestInfo, type BaseRequestInfo } from './request-info'\nimport { User } from './User'\n\nexport interface GraphQLContext<\n TLogger extends Logger = Logger,\n TRequestInfo extends BaseRequestInfo = RequestInfo,\n TUser = User | undefined,\n> {\n logger: TLogger\n requestInfo: TRequestInfo\n user: TUser\n started: number\n}\n\nexport type AnyGraphqlContext = GraphQLContext<any, any, any>\n\nexport interface LambdaContext {\n functionName?: string\n awsRequestId?: string\n}\nexport type LambdaEvent = never\nexport type LambdaRequestInfo = BaseRequestInfo & LambdaContext\nexport type RequestInfo = BaseRequestInfo | LambdaRequestInfo\n\n// Strips the `[key: string]: unknown` index signature so `keyof` returns only the declared keys.\ntype KnownKeys<T> = keyof {\n [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]\n}\n// Known keys for autocomplete, plus `(string & {})` to keep arbitrary augmented fields assignable.\nexport type RequestInfoLogKey = KnownKeys<BaseRequestInfo> | keyof LambdaContext | (string & {})\n\n// standard claims https://datatracker.ietf.org/doc/html/rfc7519#section-4.1\nexport interface JwtPayload {\n [key: string]: unknown\n name?: string | undefined\n email?: string | undefined\n given_name?: string | undefined\n family_name?: string | undefined\n preferred_username?: string | undefined\n upn?: string | undefined\n unique_name?: string | undefined\n oid?: string | undefined\n iss?: string | undefined\n sub?: string | undefined\n aud?: string | string[] | undefined\n roles?: string[] | undefined\n scp?: string | undefined\n exp?: number | undefined\n nbf?: number | undefined\n iat?: number | undefined\n jti?: string | undefined\n}\n\nexport type InferUserFromContext<TContext extends AnyGraphqlContext> =\n TContext extends GraphQLContext<any, any, infer TUser> ? TUser : never\nexport type CreateUser<T = User | undefined> = (input: Omit<ContextInput, 'createUser'>) => Promise<T> | T\nexport interface ContextInput {\n req: Request\n claims?: JwtPayload\n context?: LambdaContext\n event?: LambdaEvent\n}\nexport type CreateContext<TContext = GraphQLContext> = (input: ContextInput) => Promise<TContext>\nexport type CreateRequestLogger<TUser = User | undefined, TLogger extends Logger = Logger> = (\n requestMetadata: Record<string, unknown>,\n user: TUser,\n) => TLogger\nexport type AugmentRequestInfo = (input: ContextInput) => Record<string, unknown>\n\n// `createUser` is optional when TUser is compatible with the default `User | undefined`\n// (i.e. `defaultCreateUser` can satisfy it), and required when TUser is narrower.\nexport type CreateContextConfig<\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n> = {\n requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger\n augmentRequestInfo?: AugmentRequestInfo\n claimsToLog?: string[]\n requestInfoToLog?: Array<RequestInfoLogKey>\n augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>\n} & ([User | undefined] extends [TUser] ? { createUser?: CreateUser<TUser> } : { createUser: CreateUser<TUser> })\n\nexport const createContextFactory = <\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n>(\n config: CreateContextConfig<TUser, TAugment, TLogger>,\n): CreateContext<GraphQLContext<TLogger, RequestInfo, TUser> & TAugment> => {\n const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config\n // The conditional type on CreateContextConfig guarantees `createUser` is provided when TUser is\n // narrower than `User | undefined`, so defaulting to defaultCreateUser is sound here.\n const createUser = (config.createUser ?? defaultCreateUser) as CreateUser<TUser>\n\n return async (input: ContextInput) => {\n const { req, claims, context } = input\n\n // build base request info from the request\n const baseRequestInfo: BaseRequestInfo = buildBaseRequestInfo(req)\n\n // add lambda info from the context, if present\n let lambdaRequestInfo: LambdaRequestInfo | undefined\n if (context) {\n const { awsRequestId, functionName } = context\n lambdaRequestInfo = {\n awsRequestId,\n functionName,\n ...baseRequestInfo,\n }\n }\n\n // augment request info with the supplied function, if present\n let requestInfo: RequestInfo = lambdaRequestInfo ?? baseRequestInfo\n if (augmentRequestInfo)\n requestInfo = {\n ...requestInfo,\n ...augmentRequestInfo(input),\n }\n\n const user = await createUser(input)\n\n // create request logger\n let logger: TLogger\n if (typeof requestLogger === 'function') {\n // build request logger metadata\n const requestLoggerMetadata: Record<string, unknown> = {}\n // add request info to log\n if (requestInfoToLog?.length) requestLoggerMetadata.request = pick(requestInfo, requestInfoToLog)\n // add user claims to log\n if (claims && claimsToLog?.length) requestLoggerMetadata.user = pick(claims, claimsToLog)\n // build the request logger\n logger = requestLogger(requestLoggerMetadata, user)\n } else logger = requestLogger\n\n const graphqlContext: GraphQLContext<TLogger, RequestInfo, TUser> = {\n requestInfo,\n logger,\n user,\n started: Date.now(),\n }\n\n const augmentedGraphQLContext = augmentContext ? { ...graphqlContext, ...(await augmentContext(graphqlContext)) } : graphqlContext\n\n return augmentedGraphQLContext as GraphQLContext<TLogger, RequestInfo, TUser> & TAugment\n }\n}\n\nexport const defaultCreateUser: CreateUser<User | undefined> = ({ req, claims }) => {\n if (!claims) return Promise.resolve(undefined)\n const accessToken = req.headers.authorization?.startsWith('Bearer') ? (req.headers.authorization?.substring(7) ?? '') : ''\n return Promise.resolve(new User(claims, accessToken))\n}\n"],"names":[],"mappings":";;;;AAsFO,MAAM,oBAAoB,GAAG,CAKlC,MAAqD,KACoB;AACzE,IAAA,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,GAAG,MAAM;;;IAGnG,MAAM,UAAU,IAAI,MAAM,CAAC,UAAU,IAAI,iBAAiB,CAAsB;AAEhF,IAAA,OAAO,OAAO,KAAmB,KAAI;QACnC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK;;AAGtC,QAAA,MAAM,eAAe,GAAoB,oBAAoB,CAAC,GAAG,CAAC;;AAGlE,QAAA,IAAI,iBAAgD;QACpD,IAAI,OAAO,EAAE;AACX,YAAA,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO;AAC9C,YAAA,iBAAiB,GAAG;gBAClB,YAAY;gBACZ,YAAY;AACZ,gBAAA,GAAG,eAAe;aACnB;QACH;;AAGA,QAAA,IAAI,WAAW,GAAgB,iBAAiB,IAAI,eAAe;AACnE,QAAA,IAAI,kBAAkB;AACpB,YAAA,WAAW,GAAG;AACZ,gBAAA,GAAG,WAAW;gBACd,GAAG,kBAAkB,CAAC,KAAK,CAAC;aAC7B;AAEH,QAAA,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC;;AAGpC,QAAA,IAAI,MAAe;AACnB,QAAA,IAAI,OAAO,aAAa,KAAK,UAAU,EAAE;;YAEvC,MAAM,qBAAqB,GAA4B,EAAE;;YAEzD,IAAI,gBAAgB,EAAE,MAAM;gBAAE,qBAAqB,CAAC,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC;;AAEjG,YAAA,IAAI,MAAM,IAAI,WAAW,EAAE,MAAM;gBAAE,qBAAqB,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC;;AAEzF,YAAA,MAAM,GAAG,aAAa,CAAC,qBAAqB,EAAE,IAAI,CAAC;QACrD;;YAAO,MAAM,GAAG,aAAa;AAE7B,QAAA,MAAM,cAAc,GAAgD;YAClE,WAAW;YACX,MAAM;YACN,IAAI;AACJ,YAAA,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE;SACpB;QAED,MAAM,uBAAuB,GAAG,cAAc,GAAG,EAAE,GAAG,cAAc,EAAE,IAAI,MAAM,cAAc,CAAC,cAAc,CAAC,CAAC,EAAE,GAAG,cAAc;AAElI,QAAA,OAAO,uBAAiF;AAC1F,IAAA,CAAC;AACH;AAEO,MAAM,iBAAiB,GAAiC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAI;AACjF,IAAA,IAAI,CAAC,MAAM;AAAE,QAAA,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;AAC9C,IAAA,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,UAAU,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE;AAC1H,IAAA,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AACvD;;;;"}
1
+ {"version":3,"file":"context.mjs","sources":["../src/context.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { pick } from 'es-toolkit/compat'\nimport type { Request } from 'express'\nimport { buildBaseRequestInfo, type BaseRequestInfo } from './request-info'\nimport { User } from './User'\n\nexport interface GraphQLContext<\n TLogger extends Logger = Logger,\n TRequestInfo extends BaseRequestInfo = RequestInfo,\n TUser = User | undefined,\n> {\n logger: TLogger\n requestInfo: TRequestInfo\n user: TUser\n started: number\n}\n\nexport type AnyGraphqlContext = GraphQLContext<any, any, any>\n\nexport interface LambdaContext {\n functionName?: string\n awsRequestId?: string\n}\nexport type LambdaEvent = never\nexport type LambdaRequestInfo = BaseRequestInfo & LambdaContext\nexport type RequestInfo = BaseRequestInfo | LambdaRequestInfo\n\n// Strips the `[key: string]: unknown` index signature so `keyof` returns only the declared keys.\ntype KnownKeys<T> = keyof {\n [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]\n}\n// Known keys for autocomplete, plus `(string & {})` to keep arbitrary augmented fields assignable.\nexport type RequestInfoLogKey = KnownKeys<BaseRequestInfo> | keyof LambdaContext | (string & {})\n\n// standard claims https://datatracker.ietf.org/doc/html/rfc7519#section-4.1\nexport interface JwtPayload {\n [key: string]: unknown\n name?: string | undefined\n email?: string | undefined\n given_name?: string | undefined\n family_name?: string | undefined\n preferred_username?: string | undefined\n upn?: string | undefined\n unique_name?: string | undefined\n oid?: string | undefined\n iss?: string | undefined\n sub?: string | undefined\n aud?: string | string[] | undefined\n roles?: string[] | undefined\n scp?: string | undefined\n exp?: number | undefined\n nbf?: number | undefined\n iat?: number | undefined\n jti?: string | undefined\n}\n\nexport type InferUserFromContext<TContext extends AnyGraphqlContext> =\n TContext extends GraphQLContext<any, any, infer TUser> ? TUser : never\nexport type CreateUser<T = User | undefined> = (input: Omit<ContextInput, 'createUser'>) => Promise<T> | T\nexport interface ContextInput {\n req: Request\n claims?: JwtPayload\n context?: LambdaContext\n event?: LambdaEvent\n}\nexport type CreateContext<TContext = GraphQLContext> = (input: ContextInput) => Promise<TContext>\nexport type CreateRequestLogger<TUser = User | undefined, TLogger extends Logger = Logger> = (\n requestMetadata: Record<string, unknown>,\n user: TUser,\n) => TLogger\nexport type AugmentRequestInfo = (input: ContextInput) => Record<string, unknown>\nexport type ExtractToken = (input: ContextInput) => string | undefined\n\n// `createUser` is optional when TUser is compatible with the default `User | undefined`\n// (i.e. `defaultCreateUser` can satisfy it), and required when TUser is narrower.\nexport type CreateContextConfig<\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n> = {\n requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger\n augmentRequestInfo?: AugmentRequestInfo\n claimsToLog?: string[]\n requestInfoToLog?: Array<RequestInfoLogKey>\n augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>\n /**\n * Returns the token that the default `createUser` sets as `User.token`, e.g. from a JWT assertion\n * header. Not called when you supply `createUser`. Defaults to the bearer token in the\n * `Authorization` header.\n */\n extractToken?: ExtractToken\n} & ([User | undefined] extends [TUser] ? { createUser?: CreateUser<TUser> } : { createUser: CreateUser<TUser> })\n\nexport const createContextFactory = <\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n>(\n config: CreateContextConfig<TUser, TAugment, TLogger>,\n): CreateContext<GraphQLContext<TLogger, RequestInfo, TUser> & TAugment> => {\n const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config\n // The conditional type on CreateContextConfig guarantees `createUser` is provided when TUser is\n // narrower than `User | undefined`, so defaulting to defaultCreateUser is sound here.\n const createUser = (config.createUser ?? buildDefaultCreateUser(config.extractToken ?? defaultExtractToken)) as CreateUser<TUser>\n\n return async (input: ContextInput) => {\n const { req, claims, context } = input\n\n // build base request info from the request\n const baseRequestInfo: BaseRequestInfo = buildBaseRequestInfo(req)\n\n // add lambda info from the context, if present\n let lambdaRequestInfo: LambdaRequestInfo | undefined\n if (context) {\n const { awsRequestId, functionName } = context\n lambdaRequestInfo = {\n awsRequestId,\n functionName,\n ...baseRequestInfo,\n }\n }\n\n // augment request info with the supplied function, if present\n let requestInfo: RequestInfo = lambdaRequestInfo ?? baseRequestInfo\n if (augmentRequestInfo)\n requestInfo = {\n ...requestInfo,\n ...augmentRequestInfo(input),\n }\n\n const user = await createUser(input)\n\n // create request logger\n let logger: TLogger\n if (typeof requestLogger === 'function') {\n // build request logger metadata\n const requestLoggerMetadata: Record<string, unknown> = {}\n // add request info to log\n if (requestInfoToLog?.length) requestLoggerMetadata.request = pick(requestInfo, requestInfoToLog)\n // add user claims to log\n if (claims && claimsToLog?.length) requestLoggerMetadata.user = pick(claims, claimsToLog)\n // build the request logger\n logger = requestLogger(requestLoggerMetadata, user)\n } else logger = requestLogger\n\n const graphqlContext: GraphQLContext<TLogger, RequestInfo, TUser> = {\n requestInfo,\n logger,\n user,\n started: Date.now(),\n }\n\n const augmentedGraphQLContext = augmentContext ? { ...graphqlContext, ...(await augmentContext(graphqlContext)) } : graphqlContext\n\n return augmentedGraphQLContext as GraphQLContext<TLogger, RequestInfo, TUser> & TAugment\n }\n}\n\nconst defaultExtractToken: ExtractToken = ({ req }) =>\n req.headers.authorization?.startsWith('Bearer') ? req.headers.authorization.substring(7) : undefined\n\nconst buildDefaultCreateUser =\n (extractToken: ExtractToken): CreateUser<User | undefined> =>\n (input) => {\n if (!input.claims) return Promise.resolve(undefined)\n return Promise.resolve(new User(input.claims, extractToken(input) ?? ''))\n }\n\nexport const defaultCreateUser = buildDefaultCreateUser(defaultExtractToken)\n"],"names":[],"mappings":";;;;AA6FO,MAAM,oBAAoB,GAAG,CAKlC,MAAqD,KACoB;AACzE,IAAA,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,GAAG,MAAM;;;AAGnG,IAAA,MAAM,UAAU,IAAI,MAAM,CAAC,UAAU,IAAI,sBAAsB,CAAC,MAAM,CAAC,YAAY,IAAI,mBAAmB,CAAC,CAAsB;AAEjI,IAAA,OAAO,OAAO,KAAmB,KAAI;QACnC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK;;AAGtC,QAAA,MAAM,eAAe,GAAoB,oBAAoB,CAAC,GAAG,CAAC;;AAGlE,QAAA,IAAI,iBAAgD;QACpD,IAAI,OAAO,EAAE;AACX,YAAA,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO;AAC9C,YAAA,iBAAiB,GAAG;gBAClB,YAAY;gBACZ,YAAY;AACZ,gBAAA,GAAG,eAAe;aACnB;QACH;;AAGA,QAAA,IAAI,WAAW,GAAgB,iBAAiB,IAAI,eAAe;AACnE,QAAA,IAAI,kBAAkB;AACpB,YAAA,WAAW,GAAG;AACZ,gBAAA,GAAG,WAAW;gBACd,GAAG,kBAAkB,CAAC,KAAK,CAAC;aAC7B;AAEH,QAAA,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC;;AAGpC,QAAA,IAAI,MAAe;AACnB,QAAA,IAAI,OAAO,aAAa,KAAK,UAAU,EAAE;;YAEvC,MAAM,qBAAqB,GAA4B,EAAE;;YAEzD,IAAI,gBAAgB,EAAE,MAAM;gBAAE,qBAAqB,CAAC,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC;;AAEjG,YAAA,IAAI,MAAM,IAAI,WAAW,EAAE,MAAM;gBAAE,qBAAqB,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC;;AAEzF,YAAA,MAAM,GAAG,aAAa,CAAC,qBAAqB,EAAE,IAAI,CAAC;QACrD;;YAAO,MAAM,GAAG,aAAa;AAE7B,QAAA,MAAM,cAAc,GAAgD;YAClE,WAAW;YACX,MAAM;YACN,IAAI;AACJ,YAAA,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE;SACpB;QAED,MAAM,uBAAuB,GAAG,cAAc,GAAG,EAAE,GAAG,cAAc,EAAE,IAAI,MAAM,cAAc,CAAC,cAAc,CAAC,CAAC,EAAE,GAAG,cAAc;AAElI,QAAA,OAAO,uBAAiF;AAC1F,IAAA,CAAC;AACH;AAEA,MAAM,mBAAmB,GAAiB,CAAC,EAAE,GAAG,EAAE,KAChD,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS;AAEtG,MAAM,sBAAsB,GAC1B,CAAC,YAA0B,KAC3B,CAAC,KAAK,KAAI;IACR,IAAI,CAAC,KAAK,CAAC,MAAM;AAAE,QAAA,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;AACpD,IAAA,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AAC3E,CAAC;MAEU,iBAAiB,GAAG,sBAAsB,CAAC,mBAAmB;;;;"}
@@ -0,0 +1,81 @@
1
+ import type { DocumentNode, GraphQLSchema, OperationDefinitionNode } from 'graphql';
2
+ /**
3
+ * Safety valve for the variable walk. Recursion follows the schema's input types, which may be
4
+ * mutually recursive, and variables are walked before graphql-js coerces them — so nothing else
5
+ * bounds how deep a hand-crafted payload can go. Far above any real operation's nesting.
6
+ */
7
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = 25;
8
+ /**
9
+ * Bounds the *breadth* of the variable walk, which the depth cap does not: a single large array of
10
+ * small input objects is shallow but arbitrarily wide.
11
+ */
12
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = 10000;
13
+ /**
14
+ * Caps the collected result so one hostile document cannot blow up a log entry.
15
+ */
16
+ export declare const DEFAULT_DEPRECATION_MAX_ELEMENTS = 50;
17
+ export type DeprecatedElementKind = 'output-field' | 'argument' | 'directive-argument' | 'input-field' | 'enum-value';
18
+ export interface DeprecatedElementUsage {
19
+ kind: DeprecatedElementKind;
20
+ /**
21
+ * Stable key for aggregating usage, formatted per kind:
22
+ * `Type.field`, `Type.field(arg)`, `@directive(arg)`, `InputType.field`, `EnumType.VALUE`.
23
+ */
24
+ name: string;
25
+ /**
26
+ * Best-effort location, for debugging a single record — not an aggregation key. A field selected
27
+ * inside a fragment definition has no enclosing field in its ancestry, so its path is relative to
28
+ * the fragment rather than the operation.
29
+ */
30
+ path?: string;
31
+ }
32
+ export interface CollectDeprecatedElementUsageOptions {
33
+ schema: GraphQLSchema;
34
+ document: DocumentNode;
35
+ /**
36
+ * The executed operation. Resolved from `document` and `operationName` when omitted.
37
+ */
38
+ operation?: OperationDefinitionNode | null;
39
+ operationName?: string | null;
40
+ /**
41
+ * Raw request variables, as supplied by the client and before graphql-js coerces them.
42
+ */
43
+ variables?: Record<string, unknown> | null;
44
+ maxVariableDepth?: number;
45
+ maxVariableNodes?: number;
46
+ maxElements?: number;
47
+ }
48
+ export interface CollectDeprecatedElementUsageResult {
49
+ elements: DeprecatedElementUsage[];
50
+ /**
51
+ * True when a limit — `maxElements`, `maxVariableDepth` or `maxVariableNodes` — stopped
52
+ * collection before it finished, so `elements` may be missing elements the operation really used.
53
+ * Absence from a truncated result is not evidence that an element is unused.
54
+ */
55
+ truncated: boolean;
56
+ }
57
+ /**
58
+ * Reports every `@deprecated` schema element an operation uses, across both the document and the
59
+ * supplied variables.
60
+ *
61
+ * Output fields and arguments are named in the document, so an AST walk finds them. Input fields
62
+ * and enum values are data the caller *supplies* rather than selects: they can arrive as a document
63
+ * literal, or inside a variable where the name appears nowhere in the AST, so both routes are
64
+ * walked and the results deduplicated.
65
+ *
66
+ * Results are deduplicated on kind and name — at most one entry per distinct element per request,
67
+ * bounded by the size of the schema rather than the size of the request — and sorted, so records
68
+ * are stable across requests.
69
+ *
70
+ * Returns `truncated` when any limit stopped collection. Absence from `elements` is only evidence
71
+ * that an element went unused when `truncated` is false — which matters, because concluding
72
+ * "nothing uses this" from a truncated result is how a still-used element gets deleted.
73
+ *
74
+ * The variable walk reads untrusted input before coercion, so a payload whose shape contradicts its
75
+ * declared type reaches it. Every shape it depends on is guarded: malformed variables yield no
76
+ * records rather than an error.
77
+ *
78
+ * This is request-side telemetry — it observes values a caller *sends*, never values the server
79
+ * returns. A deprecated element that only ever appears in responses reads as unused.
80
+ */
81
+ export declare function collectDeprecatedElementUsage({ schema, document, operation, operationName, variables, maxVariableDepth, maxVariableNodes, maxElements, }: CollectDeprecatedElementUsageOptions): CollectDeprecatedElementUsageResult;
@@ -0,0 +1,81 @@
1
+ import type { DocumentNode, GraphQLSchema, OperationDefinitionNode } from 'graphql';
2
+ /**
3
+ * Safety valve for the variable walk. Recursion follows the schema's input types, which may be
4
+ * mutually recursive, and variables are walked before graphql-js coerces them — so nothing else
5
+ * bounds how deep a hand-crafted payload can go. Far above any real operation's nesting.
6
+ */
7
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = 25;
8
+ /**
9
+ * Bounds the *breadth* of the variable walk, which the depth cap does not: a single large array of
10
+ * small input objects is shallow but arbitrarily wide.
11
+ */
12
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = 10000;
13
+ /**
14
+ * Caps the collected result so one hostile document cannot blow up a log entry.
15
+ */
16
+ export declare const DEFAULT_DEPRECATION_MAX_ELEMENTS = 50;
17
+ export type DeprecatedElementKind = 'output-field' | 'argument' | 'directive-argument' | 'input-field' | 'enum-value';
18
+ export interface DeprecatedElementUsage {
19
+ kind: DeprecatedElementKind;
20
+ /**
21
+ * Stable key for aggregating usage, formatted per kind:
22
+ * `Type.field`, `Type.field(arg)`, `@directive(arg)`, `InputType.field`, `EnumType.VALUE`.
23
+ */
24
+ name: string;
25
+ /**
26
+ * Best-effort location, for debugging a single record — not an aggregation key. A field selected
27
+ * inside a fragment definition has no enclosing field in its ancestry, so its path is relative to
28
+ * the fragment rather than the operation.
29
+ */
30
+ path?: string;
31
+ }
32
+ export interface CollectDeprecatedElementUsageOptions {
33
+ schema: GraphQLSchema;
34
+ document: DocumentNode;
35
+ /**
36
+ * The executed operation. Resolved from `document` and `operationName` when omitted.
37
+ */
38
+ operation?: OperationDefinitionNode | null;
39
+ operationName?: string | null;
40
+ /**
41
+ * Raw request variables, as supplied by the client and before graphql-js coerces them.
42
+ */
43
+ variables?: Record<string, unknown> | null;
44
+ maxVariableDepth?: number;
45
+ maxVariableNodes?: number;
46
+ maxElements?: number;
47
+ }
48
+ export interface CollectDeprecatedElementUsageResult {
49
+ elements: DeprecatedElementUsage[];
50
+ /**
51
+ * True when a limit — `maxElements`, `maxVariableDepth` or `maxVariableNodes` — stopped
52
+ * collection before it finished, so `elements` may be missing elements the operation really used.
53
+ * Absence from a truncated result is not evidence that an element is unused.
54
+ */
55
+ truncated: boolean;
56
+ }
57
+ /**
58
+ * Reports every `@deprecated` schema element an operation uses, across both the document and the
59
+ * supplied variables.
60
+ *
61
+ * Output fields and arguments are named in the document, so an AST walk finds them. Input fields
62
+ * and enum values are data the caller *supplies* rather than selects: they can arrive as a document
63
+ * literal, or inside a variable where the name appears nowhere in the AST, so both routes are
64
+ * walked and the results deduplicated.
65
+ *
66
+ * Results are deduplicated on kind and name — at most one entry per distinct element per request,
67
+ * bounded by the size of the schema rather than the size of the request — and sorted, so records
68
+ * are stable across requests.
69
+ *
70
+ * Returns `truncated` when any limit stopped collection. Absence from `elements` is only evidence
71
+ * that an element went unused when `truncated` is false — which matters, because concluding
72
+ * "nothing uses this" from a truncated result is how a still-used element gets deleted.
73
+ *
74
+ * The variable walk reads untrusted input before coercion, so a payload whose shape contradicts its
75
+ * declared type reaches it. Every shape it depends on is guarded: malformed variables yield no
76
+ * records rather than an error.
77
+ *
78
+ * This is request-side telemetry — it observes values a caller *sends*, never values the server
79
+ * returns. A deprecated element that only ever appears in responses reads as unused.
80
+ */
81
+ export declare function collectDeprecatedElementUsage({ schema, document, operation, operationName, variables, maxVariableDepth, maxVariableNodes, maxElements, }: CollectDeprecatedElementUsageOptions): CollectDeprecatedElementUsageResult;
@@ -0,0 +1,81 @@
1
+ import type { DocumentNode, GraphQLSchema, OperationDefinitionNode } from 'graphql';
2
+ /**
3
+ * Safety valve for the variable walk. Recursion follows the schema's input types, which may be
4
+ * mutually recursive, and variables are walked before graphql-js coerces them — so nothing else
5
+ * bounds how deep a hand-crafted payload can go. Far above any real operation's nesting.
6
+ */
7
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = 25;
8
+ /**
9
+ * Bounds the *breadth* of the variable walk, which the depth cap does not: a single large array of
10
+ * small input objects is shallow but arbitrarily wide.
11
+ */
12
+ export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = 10000;
13
+ /**
14
+ * Caps the collected result so one hostile document cannot blow up a log entry.
15
+ */
16
+ export declare const DEFAULT_DEPRECATION_MAX_ELEMENTS = 50;
17
+ export type DeprecatedElementKind = 'output-field' | 'argument' | 'directive-argument' | 'input-field' | 'enum-value';
18
+ export interface DeprecatedElementUsage {
19
+ kind: DeprecatedElementKind;
20
+ /**
21
+ * Stable key for aggregating usage, formatted per kind:
22
+ * `Type.field`, `Type.field(arg)`, `@directive(arg)`, `InputType.field`, `EnumType.VALUE`.
23
+ */
24
+ name: string;
25
+ /**
26
+ * Best-effort location, for debugging a single record — not an aggregation key. A field selected
27
+ * inside a fragment definition has no enclosing field in its ancestry, so its path is relative to
28
+ * the fragment rather than the operation.
29
+ */
30
+ path?: string;
31
+ }
32
+ export interface CollectDeprecatedElementUsageOptions {
33
+ schema: GraphQLSchema;
34
+ document: DocumentNode;
35
+ /**
36
+ * The executed operation. Resolved from `document` and `operationName` when omitted.
37
+ */
38
+ operation?: OperationDefinitionNode | null;
39
+ operationName?: string | null;
40
+ /**
41
+ * Raw request variables, as supplied by the client and before graphql-js coerces them.
42
+ */
43
+ variables?: Record<string, unknown> | null;
44
+ maxVariableDepth?: number;
45
+ maxVariableNodes?: number;
46
+ maxElements?: number;
47
+ }
48
+ export interface CollectDeprecatedElementUsageResult {
49
+ elements: DeprecatedElementUsage[];
50
+ /**
51
+ * True when a limit — `maxElements`, `maxVariableDepth` or `maxVariableNodes` — stopped
52
+ * collection before it finished, so `elements` may be missing elements the operation really used.
53
+ * Absence from a truncated result is not evidence that an element is unused.
54
+ */
55
+ truncated: boolean;
56
+ }
57
+ /**
58
+ * Reports every `@deprecated` schema element an operation uses, across both the document and the
59
+ * supplied variables.
60
+ *
61
+ * Output fields and arguments are named in the document, so an AST walk finds them. Input fields
62
+ * and enum values are data the caller *supplies* rather than selects: they can arrive as a document
63
+ * literal, or inside a variable where the name appears nowhere in the AST, so both routes are
64
+ * walked and the results deduplicated.
65
+ *
66
+ * Results are deduplicated on kind and name — at most one entry per distinct element per request,
67
+ * bounded by the size of the schema rather than the size of the request — and sorted, so records
68
+ * are stable across requests.
69
+ *
70
+ * Returns `truncated` when any limit stopped collection. Absence from `elements` is only evidence
71
+ * that an element went unused when `truncated` is false — which matters, because concluding
72
+ * "nothing uses this" from a truncated result is how a still-used element gets deleted.
73
+ *
74
+ * The variable walk reads untrusted input before coercion, so a payload whose shape contradicts its
75
+ * declared type reaches it. Every shape it depends on is guarded: malformed variables yield no
76
+ * records rather than an error.
77
+ *
78
+ * This is request-side telemetry — it observes values a caller *sends*, never values the server
79
+ * returns. A deprecated element that only ever appears in responses reads as unused.
80
+ */
81
+ export declare function collectDeprecatedElementUsage({ schema, document, operation, operationName, variables, maxVariableDepth, maxVariableNodes, maxElements, }: CollectDeprecatedElementUsageOptions): CollectDeprecatedElementUsageResult;