@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.
- package/README.md +68 -10
- package/context.d.cts +7 -0
- package/context.d.mts +7 -0
- package/context.d.ts +7 -0
- package/context.js +6 -5
- package/context.js.map +1 -1
- package/context.mjs +6 -5
- package/context.mjs.map +1 -1
- package/deprecation.d.cts +81 -0
- package/deprecation.d.mts +81 -0
- package/deprecation.d.ts +81 -0
- package/deprecation.js +240 -0
- package/deprecation.js.map +1 -0
- package/deprecation.mjs +235 -0
- package/deprecation.mjs.map +1 -0
- package/index.d.cts +1 -0
- package/index.d.mts +1 -0
- package/index.d.ts +1 -0
- package/index.js +5 -0
- package/index.js.map +1 -1
- package/index.mjs +1 -0
- package/index.mjs.map +1 -1
- package/logging.d.cts +17 -2
- package/logging.d.mts +17 -2
- package/logging.d.ts +17 -2
- package/logging.js +21 -3
- package/logging.js.map +1 -1
- package/logging.mjs +21 -3
- package/logging.mjs.map +1 -1
- package/package.json +1 -1
- package/subscriptions/context.d.cts +7 -0
- package/subscriptions/context.d.mts +7 -0
- package/subscriptions/context.d.ts +7 -0
- package/subscriptions/context.js +5 -5
- package/subscriptions/context.js.map +1 -1
- package/subscriptions/context.mjs +6 -6
- package/subscriptions/context.mjs.map +1 -1
- package/subscriptions/server.d.cts +13 -2
- package/subscriptions/server.d.mts +13 -2
- package/subscriptions/server.d.ts +13 -2
- package/subscriptions/server.js +12 -6
- package/subscriptions/server.js.map +1 -1
- package/subscriptions/server.mjs +13 -7
- package/subscriptions/server.mjs.map +1 -1
- package/subscriptions/utils.d.cts +2 -0
- package/subscriptions/utils.d.mts +2 -0
- package/subscriptions/utils.d.ts +2 -0
- package/subscriptions/utils.js +2 -0
- package/subscriptions/utils.js.map +1 -1
- package/subscriptions/utils.mjs +2 -1
- 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
|
|
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 ??
|
|
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
|
|
61
|
-
|
|
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
|
-
|
|
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 ??
|
|
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 ??
|
|
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
|
|
59
|
-
|
|
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
|
-
|
|
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 ??
|
|
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;
|
package/deprecation.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { DocumentNode, GraphQLSchema, OperationDefinitionNode } from 'graphql';
|
|
2
|
+
/**
|
|
3
|
+
* Safety valve for the variable walk. Recursion follows the schema's input types, which may be
|
|
4
|
+
* mutually recursive, and variables are walked before graphql-js coerces them — so nothing else
|
|
5
|
+
* bounds how deep a hand-crafted payload can go. Far above any real operation's nesting.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_DEPTH = 25;
|
|
8
|
+
/**
|
|
9
|
+
* Bounds the *breadth* of the variable walk, which the depth cap does not: a single large array of
|
|
10
|
+
* small input objects is shallow but arbitrarily wide.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_DEPRECATION_MAX_VARIABLE_NODES = 10000;
|
|
13
|
+
/**
|
|
14
|
+
* Caps the collected result so one hostile document cannot blow up a log entry.
|
|
15
|
+
*/
|
|
16
|
+
export declare const DEFAULT_DEPRECATION_MAX_ELEMENTS = 50;
|
|
17
|
+
export type DeprecatedElementKind = 'output-field' | 'argument' | 'directive-argument' | 'input-field' | 'enum-value';
|
|
18
|
+
export interface DeprecatedElementUsage {
|
|
19
|
+
kind: DeprecatedElementKind;
|
|
20
|
+
/**
|
|
21
|
+
* Stable key for aggregating usage, formatted per kind:
|
|
22
|
+
* `Type.field`, `Type.field(arg)`, `@directive(arg)`, `InputType.field`, `EnumType.VALUE`.
|
|
23
|
+
*/
|
|
24
|
+
name: string;
|
|
25
|
+
/**
|
|
26
|
+
* Best-effort location, for debugging a single record — not an aggregation key. A field selected
|
|
27
|
+
* inside a fragment definition has no enclosing field in its ancestry, so its path is relative to
|
|
28
|
+
* the fragment rather than the operation.
|
|
29
|
+
*/
|
|
30
|
+
path?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface CollectDeprecatedElementUsageOptions {
|
|
33
|
+
schema: GraphQLSchema;
|
|
34
|
+
document: DocumentNode;
|
|
35
|
+
/**
|
|
36
|
+
* The executed operation. Resolved from `document` and `operationName` when omitted.
|
|
37
|
+
*/
|
|
38
|
+
operation?: OperationDefinitionNode | null;
|
|
39
|
+
operationName?: string | null;
|
|
40
|
+
/**
|
|
41
|
+
* Raw request variables, as supplied by the client and before graphql-js coerces them.
|
|
42
|
+
*/
|
|
43
|
+
variables?: Record<string, unknown> | null;
|
|
44
|
+
maxVariableDepth?: number;
|
|
45
|
+
maxVariableNodes?: number;
|
|
46
|
+
maxElements?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface CollectDeprecatedElementUsageResult {
|
|
49
|
+
elements: DeprecatedElementUsage[];
|
|
50
|
+
/**
|
|
51
|
+
* True when a limit — `maxElements`, `maxVariableDepth` or `maxVariableNodes` — stopped
|
|
52
|
+
* collection before it finished, so `elements` may be missing elements the operation really used.
|
|
53
|
+
* Absence from a truncated result is not evidence that an element is unused.
|
|
54
|
+
*/
|
|
55
|
+
truncated: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Reports every `@deprecated` schema element an operation uses, across both the document and the
|
|
59
|
+
* supplied variables.
|
|
60
|
+
*
|
|
61
|
+
* Output fields and arguments are named in the document, so an AST walk finds them. Input fields
|
|
62
|
+
* and enum values are data the caller *supplies* rather than selects: they can arrive as a document
|
|
63
|
+
* literal, or inside a variable where the name appears nowhere in the AST, so both routes are
|
|
64
|
+
* walked and the results deduplicated.
|
|
65
|
+
*
|
|
66
|
+
* Results are deduplicated on kind and name — at most one entry per distinct element per request,
|
|
67
|
+
* bounded by the size of the schema rather than the size of the request — and sorted, so records
|
|
68
|
+
* are stable across requests.
|
|
69
|
+
*
|
|
70
|
+
* Returns `truncated` when any limit stopped collection. Absence from `elements` is only evidence
|
|
71
|
+
* that an element went unused when `truncated` is false — which matters, because concluding
|
|
72
|
+
* "nothing uses this" from a truncated result is how a still-used element gets deleted.
|
|
73
|
+
*
|
|
74
|
+
* The variable walk reads untrusted input before coercion, so a payload whose shape contradicts its
|
|
75
|
+
* declared type reaches it. Every shape it depends on is guarded: malformed variables yield no
|
|
76
|
+
* records rather than an error.
|
|
77
|
+
*
|
|
78
|
+
* This is request-side telemetry — it observes values a caller *sends*, never values the server
|
|
79
|
+
* returns. A deprecated element that only ever appears in responses reads as unused.
|
|
80
|
+
*/
|
|
81
|
+
export declare function collectDeprecatedElementUsage({ schema, document, operation, operationName, variables, maxVariableDepth, maxVariableNodes, maxElements, }: CollectDeprecatedElementUsageOptions): CollectDeprecatedElementUsageResult;
|