@makerx/graphql-core 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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.
@@ -363,12 +376,30 @@ This library includes a `subscriptions` module to provide simple setup using the
363
376
  1. Create a subscriptions server, using the ws-server cleanup function in your server lifecycle.
364
377
 
365
378
  The `useSubscriptionsServer` function sets up:
366
- - 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)
367
380
  - GraphQL context creation
368
381
  - Logging from the server `onConnect`, `onDisconnect`, `onOperation`, `onNext` and `onError` callbacks
369
382
 
370
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.
371
384
 
385
+ By default a connection's token is verified when the connection is established, and again each time the client subscribes on it. A token that no longer verifies then closes the connection with `4403 Forbidden`, and the operation is not run. Subscriptions already running continue until that happens.
386
+
387
+ To give connections a set lifetime instead, pass `connectionExpiry`. It receives the claims `verifyToken` returned and returns the time the connection ends, in milliseconds since the epoch (or `undefined` for the default behaviour). At that time the connection is closed with `4403`, which ends every subscription running on it, and a time already past refuses the connection. Until then the client may subscribe again without the token being verified again: a connection's token cannot change.
388
+
389
+ ```ts
390
+ // End each connection when its token expires
391
+ connectionExpiry: (claims) => (claims.exp === undefined ? undefined : claims.exp * 1000)
392
+
393
+ // A short-lived token (such as a proxy's assertion) that proves a longer session: connections last an hour from when the token was issued
394
+ connectionExpiry: (claims) => (claims.iat === undefined ? undefined : (claims.iat + 3600) * 1000)
395
+ ```
396
+
397
+ With a lifetime longer than the token's, a revoked session is noticed only when the connection ends.
398
+
399
+ graphql-ws clients retry `4403` by default, but a retry succeeds only with a fresh token: pass `connectionParams` to `createClient` as a function that returns a current token, or authenticate the upgrade request afresh (by a cookie and a proxy, for example). A client that sends the same expired token fails again until its `retryAttempts` run out, and its subscriptions then end with an error.
400
+
401
+ `keepAlive` sets the milliseconds between the server's websocket pings (default 12 seconds, graphql-ws's default; `0` turns them off). A connection that has not answered a ping by the time the next one is due is terminated. The pings also stop proxies and load balancers from closing an idle connection, so keep the interval below their idle timeouts.
402
+
372
403
  Example for Apollo Server (`wsServerCleanup` called in the `drainServer` plugin callback):
373
404
 
374
405
  ```ts
@@ -400,6 +431,17 @@ This library includes a `subscriptions` module to provide simple setup using the
400
431
 
401
432
  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.
402
433
 
434
+ 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):
435
+
436
+ ```ts
437
+ const extractToken: ExtractSubscriptionToken = ({ connectRequest }) =>
438
+ connectRequest.headers['x-goog-iap-jwt-assertion'] as string | undefined
439
+
440
+ const createSubscriptionContext = createSubscriptionContextFactory({ requestLogger, extractToken })
441
+
442
+ useSubscriptionsServer({ schema, httpServer, logger, createSubscriptionContext, extractToken, requireAuth: true, verifyToken })
443
+ ```
444
+
403
445
  ### wrapSubscriptionIterator
404
446
 
405
447
  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;;;;"}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "**"
5
5
  ],
6
6
  "name": "@makerx/graphql-core",
7
- "version": "3.3.0",
7
+ "version": "3.5.0",
8
8
  "private": false,
9
9
  "description": "A set of core GraphQL utilities that MakerX uses to build GraphQL APIs",
10
10
  "author": "MakerX",
@@ -42,7 +42,8 @@
42
42
  }
43
43
  },
44
44
  "overrides": {
45
- "esbuild": "^0.28.1"
45
+ "esbuild": "^0.28.1",
46
+ "@graphql-tools/utils": "12.0.1"
46
47
  },
47
48
  "main": "./index.js",
48
49
  "module": "./index.mjs",
@@ -10,12 +10,19 @@ export interface SubscriptionContextInput {
10
10
  export type CreateSubscriptionUser<T = User | undefined> = (input: SubscriptionContextInput) => Promise<T> | T;
11
11
  export type CreateSubscriptionContext<TContext = GraphQLContext> = (input: SubscriptionContextInput) => Promise<TContext>;
12
12
  export type AugmentSubscriptionRequestInfo = (input: SubscriptionContextInput) => Record<string, unknown>;
13
+ export type ExtractSubscriptionToken = (input: Omit<SubscriptionContextInput, 'claims'>) => string | undefined;
13
14
  export type CreateSubscriptionContextConfig<TUser = User | undefined, TAugment extends Record<string, unknown> = Record<string, never>, TLogger extends Logger = Logger> = {
14
15
  requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger;
15
16
  augmentRequestInfo?: AugmentSubscriptionRequestInfo;
16
17
  claimsToLog?: string[];
17
18
  requestInfoToLog?: Array<RequestInfoLogKey>;
18
19
  augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>;
20
+ /**
21
+ * Returns the token that the default `createUser` sets as `User.token`. Use the same function as
22
+ * the `useSubscriptionsServer` `extractToken` option. Not called when you supply `createUser`.
23
+ * Defaults to the bearer token in the `authorization` or `Authorization` connection parameter.
24
+ */
25
+ extractToken?: ExtractSubscriptionToken;
19
26
  } & ([User | undefined] extends [TUser] ? {
20
27
  createUser?: CreateSubscriptionUser<TUser>;
21
28
  } : {
@@ -10,12 +10,19 @@ export interface SubscriptionContextInput {
10
10
  export type CreateSubscriptionUser<T = User | undefined> = (input: SubscriptionContextInput) => Promise<T> | T;
11
11
  export type CreateSubscriptionContext<TContext = GraphQLContext> = (input: SubscriptionContextInput) => Promise<TContext>;
12
12
  export type AugmentSubscriptionRequestInfo = (input: SubscriptionContextInput) => Record<string, unknown>;
13
+ export type ExtractSubscriptionToken = (input: Omit<SubscriptionContextInput, 'claims'>) => string | undefined;
13
14
  export type CreateSubscriptionContextConfig<TUser = User | undefined, TAugment extends Record<string, unknown> = Record<string, never>, TLogger extends Logger = Logger> = {
14
15
  requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger;
15
16
  augmentRequestInfo?: AugmentSubscriptionRequestInfo;
16
17
  claimsToLog?: string[];
17
18
  requestInfoToLog?: Array<RequestInfoLogKey>;
18
19
  augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>;
20
+ /**
21
+ * Returns the token that the default `createUser` sets as `User.token`. Use the same function as
22
+ * the `useSubscriptionsServer` `extractToken` option. Not called when you supply `createUser`.
23
+ * Defaults to the bearer token in the `authorization` or `Authorization` connection parameter.
24
+ */
25
+ extractToken?: ExtractSubscriptionToken;
19
26
  } & ([User | undefined] extends [TUser] ? {
20
27
  createUser?: CreateSubscriptionUser<TUser>;
21
28
  } : {
@@ -10,12 +10,19 @@ export interface SubscriptionContextInput {
10
10
  export type CreateSubscriptionUser<T = User | undefined> = (input: SubscriptionContextInput) => Promise<T> | T;
11
11
  export type CreateSubscriptionContext<TContext = GraphQLContext> = (input: SubscriptionContextInput) => Promise<TContext>;
12
12
  export type AugmentSubscriptionRequestInfo = (input: SubscriptionContextInput) => Record<string, unknown>;
13
+ export type ExtractSubscriptionToken = (input: Omit<SubscriptionContextInput, 'claims'>) => string | undefined;
13
14
  export type CreateSubscriptionContextConfig<TUser = User | undefined, TAugment extends Record<string, unknown> = Record<string, never>, TLogger extends Logger = Logger> = {
14
15
  requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger;
15
16
  augmentRequestInfo?: AugmentSubscriptionRequestInfo;
16
17
  claimsToLog?: string[];
17
18
  requestInfoToLog?: Array<RequestInfoLogKey>;
18
19
  augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>;
20
+ /**
21
+ * Returns the token that the default `createUser` sets as `User.token`. Use the same function as
22
+ * the `useSubscriptionsServer` `extractToken` option. Not called when you supply `createUser`.
23
+ * Defaults to the bearer token in the `authorization` or `Authorization` connection parameter.
24
+ */
25
+ extractToken?: ExtractSubscriptionToken;
19
26
  } & ([User | undefined] extends [TUser] ? {
20
27
  createUser?: CreateSubscriptionUser<TUser>;
21
28
  } : {
@@ -9,7 +9,8 @@ const createSubscriptionContextFactory = (config) => {
9
9
  const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config;
10
10
  // The conditional type on CreateSubscriptionContextConfig guarantees `createUser` is provided
11
11
  // when TUser is narrower than `User | undefined`, so defaulting is sound here.
12
- const createUser = (config.createUser ?? defaultCreateUser);
12
+ const createUser = (config.createUser ??
13
+ buildDefaultCreateUser(config.extractToken ?? utils.defaultExtractSubscriptionToken));
13
14
  return async (input) => {
14
15
  const { connectRequest: req, claims } = input;
15
16
  // build request info from the connect request and socket
@@ -44,11 +45,10 @@ const createSubscriptionContextFactory = (config) => {
44
45
  return augmentedGraphQLContext;
45
46
  };
46
47
  };
47
- const defaultCreateUser = ({ claims, connectionParams }) => {
48
- if (!claims)
48
+ const buildDefaultCreateUser = (extractToken) => (input) => {
49
+ if (!input.claims)
49
50
  return Promise.resolve(undefined);
50
- const accessToken = utils.extractTokenFromConnectionParams(connectionParams);
51
- return Promise.resolve(new User.User(claims, accessToken ?? ''));
51
+ return Promise.resolve(new User.User(input.claims, extractToken(input) ?? ''));
52
52
  };
53
53
 
54
54
  exports.createSubscriptionContextFactory = createSubscriptionContextFactory;
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sources":["../../src/subscriptions/context.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { pick } from 'es-toolkit/compat'\nimport type { IncomingMessage } from 'http'\nimport { User } from '../User'\nimport type { CreateRequestLogger, GraphQLContext, JwtPayload, RequestInfo, RequestInfoLogKey } from '../context'\nimport { buildConnectRequestInfo } from '../request-info'\nimport { extractTokenFromConnectionParams } from './utils'\n\nexport interface SubscriptionContextInput {\n connectRequest: IncomingMessage\n connectionParams?: Readonly<Record<string, unknown>>\n claims?: JwtPayload\n}\n\nexport type CreateSubscriptionUser<T = User | undefined> = (input: SubscriptionContextInput) => Promise<T> | T\nexport type CreateSubscriptionContext<TContext = GraphQLContext> = (input: SubscriptionContextInput) => Promise<TContext>\nexport type AugmentSubscriptionRequestInfo = (input: SubscriptionContextInput) => 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 CreateSubscriptionContextConfig<\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?: AugmentSubscriptionRequestInfo\n claimsToLog?: string[]\n requestInfoToLog?: Array<RequestInfoLogKey>\n augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>\n} & ([User | undefined] extends [TUser] ? { createUser?: CreateSubscriptionUser<TUser> } : { createUser: CreateSubscriptionUser<TUser> })\n\nexport const createSubscriptionContextFactory = <\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n>(\n config: CreateSubscriptionContextConfig<TUser, TAugment, TLogger>,\n): CreateSubscriptionContext<GraphQLContext<TLogger, RequestInfo, TUser> & TAugment> => {\n const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config\n // The conditional type on CreateSubscriptionContextConfig guarantees `createUser` is provided\n // when TUser is narrower than `User | undefined`, so defaulting is sound here.\n const createUser = (config.createUser ?? defaultCreateUser) as CreateSubscriptionUser<TUser>\n\n return async (input: SubscriptionContextInput) => {\n const { connectRequest: req, claims } = input\n\n // build request info from the connect request and socket\n const requestInfo: RequestInfo = {\n ...buildConnectRequestInfo(req),\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 defaultCreateUser: CreateSubscriptionUser<User | undefined> = ({ claims, connectionParams }) => {\n if (!claims) return Promise.resolve(undefined)\n const accessToken = extractTokenFromConnectionParams(connectionParams)\n return Promise.resolve(new User(claims, accessToken ?? ''))\n}\n"],"names":["requestInfo","buildConnectRequestInfo","pick","extractTokenFromConnectionParams","User"],"mappings":";;;;;;;AAgCO,MAAM,gCAAgC,GAAG,CAK9C,MAAiE,KACoB;AACrF,IAAA,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,GAAG,MAAM;;;IAGnG,MAAM,UAAU,IAAI,MAAM,CAAC,UAAU,IAAI,iBAAiB,CAAkC;AAE5F,IAAA,OAAO,OAAO,KAA+B,KAAI;QAC/C,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK;;AAG7C,QAAA,MAAMA,aAAW,GAAgB;YAC/B,GAAGC,mCAAuB,CAAC,GAAG,CAAC;AAC/B,YAAA,GAAG,kBAAkB,GAAG,KAAK,CAAC;SAC/B;AAED,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,CAACF,aAAW,EAAE,gBAAgB,CAAC;;AAEjG,YAAA,IAAI,MAAM,IAAI,WAAW,EAAE,MAAM;gBAAE,qBAAqB,CAAC,IAAI,GAAGE,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;yBAClEF,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,iBAAiB,GAA6C,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAI;AACnG,IAAA,IAAI,CAAC,MAAM;AAAE,QAAA,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;AAC9C,IAAA,MAAM,WAAW,GAAGG,sCAAgC,CAAC,gBAAgB,CAAC;AACtE,IAAA,OAAO,OAAO,CAAC,OAAO,CAAC,IAAIC,SAAI,CAAC,MAAM,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC;AAC7D,CAAC;;;;"}
1
+ {"version":3,"file":"context.js","sources":["../../src/subscriptions/context.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { pick } from 'es-toolkit/compat'\nimport type { IncomingMessage } from 'http'\nimport { User } from '../User'\nimport type { CreateRequestLogger, GraphQLContext, JwtPayload, RequestInfo, RequestInfoLogKey } from '../context'\nimport { buildConnectRequestInfo } from '../request-info'\nimport { defaultExtractSubscriptionToken } from './utils'\n\nexport interface SubscriptionContextInput {\n connectRequest: IncomingMessage\n connectionParams?: Readonly<Record<string, unknown>>\n claims?: JwtPayload\n}\n\nexport type CreateSubscriptionUser<T = User | undefined> = (input: SubscriptionContextInput) => Promise<T> | T\nexport type CreateSubscriptionContext<TContext = GraphQLContext> = (input: SubscriptionContextInput) => Promise<TContext>\nexport type AugmentSubscriptionRequestInfo = (input: SubscriptionContextInput) => Record<string, unknown>\n// The token is extracted before the connection is authenticated, so the input has no claims.\nexport type ExtractSubscriptionToken = (input: Omit<SubscriptionContextInput, 'claims'>) => 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 CreateSubscriptionContextConfig<\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?: AugmentSubscriptionRequestInfo\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`. Use the same function as\n * the `useSubscriptionsServer` `extractToken` option. Not called when you supply `createUser`.\n * Defaults to the bearer token in the `authorization` or `Authorization` connection parameter.\n */\n extractToken?: ExtractSubscriptionToken\n} & ([User | undefined] extends [TUser] ? { createUser?: CreateSubscriptionUser<TUser> } : { createUser: CreateSubscriptionUser<TUser> })\n\nexport const createSubscriptionContextFactory = <\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n>(\n config: CreateSubscriptionContextConfig<TUser, TAugment, TLogger>,\n): CreateSubscriptionContext<GraphQLContext<TLogger, RequestInfo, TUser> & TAugment> => {\n const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config\n // The conditional type on CreateSubscriptionContextConfig guarantees `createUser` is provided\n // when TUser is narrower than `User | undefined`, so defaulting is sound here.\n const createUser = (config.createUser ??\n buildDefaultCreateUser(config.extractToken ?? defaultExtractSubscriptionToken)) as CreateSubscriptionUser<TUser>\n\n return async (input: SubscriptionContextInput) => {\n const { connectRequest: req, claims } = input\n\n // build request info from the connect request and socket\n const requestInfo: RequestInfo = {\n ...buildConnectRequestInfo(req),\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 buildDefaultCreateUser =\n (extractToken: ExtractSubscriptionToken): CreateSubscriptionUser<User | undefined> =>\n (input) => {\n if (!input.claims) return Promise.resolve(undefined)\n return Promise.resolve(new User(input.claims, extractToken(input) ?? ''))\n }\n"],"names":["defaultExtractSubscriptionToken","requestInfo","buildConnectRequestInfo","pick","User"],"mappings":";;;;;;;AAwCO,MAAM,gCAAgC,GAAG,CAK9C,MAAiE,KACoB;AACrF,IAAA,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,GAAG,MAAM;;;AAGnG,IAAA,MAAM,UAAU,IAAI,MAAM,CAAC,UAAU;QACnC,sBAAsB,CAAC,MAAM,CAAC,YAAY,IAAIA,qCAA+B,CAAC,CAAkC;AAElH,IAAA,OAAO,OAAO,KAA+B,KAAI;QAC/C,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK;;AAG7C,QAAA,MAAMC,aAAW,GAAgB;YAC/B,GAAGC,mCAAuB,CAAC,GAAG,CAAC;AAC/B,YAAA,GAAG,kBAAkB,GAAG,KAAK,CAAC;SAC/B;AAED,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,CAACF,aAAW,EAAE,gBAAgB,CAAC;;AAEjG,YAAA,IAAI,MAAM,IAAI,WAAW,EAAE,MAAM;gBAAE,qBAAqB,CAAC,IAAI,GAAGE,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;yBAClEF,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,sBAAsB,GAC1B,CAAC,YAAsC,KACvC,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,IAAIG,SAAI,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AAC3E,CAAC;;;;"}
@@ -1,13 +1,14 @@
1
1
  import { pick } from 'es-toolkit/compat';
2
2
  import { User } from '../User.mjs';
3
3
  import { buildConnectRequestInfo } from '../request-info.mjs';
4
- import { extractTokenFromConnectionParams } from './utils.mjs';
4
+ import { defaultExtractSubscriptionToken } from './utils.mjs';
5
5
 
6
6
  const createSubscriptionContextFactory = (config) => {
7
7
  const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config;
8
8
  // The conditional type on CreateSubscriptionContextConfig guarantees `createUser` is provided
9
9
  // when TUser is narrower than `User | undefined`, so defaulting is sound here.
10
- const createUser = (config.createUser ?? defaultCreateUser);
10
+ const createUser = (config.createUser ??
11
+ buildDefaultCreateUser(config.extractToken ?? defaultExtractSubscriptionToken));
11
12
  return async (input) => {
12
13
  const { connectRequest: req, claims } = input;
13
14
  // build request info from the connect request and socket
@@ -42,11 +43,10 @@ const createSubscriptionContextFactory = (config) => {
42
43
  return augmentedGraphQLContext;
43
44
  };
44
45
  };
45
- const defaultCreateUser = ({ claims, connectionParams }) => {
46
- if (!claims)
46
+ const buildDefaultCreateUser = (extractToken) => (input) => {
47
+ if (!input.claims)
47
48
  return Promise.resolve(undefined);
48
- const accessToken = extractTokenFromConnectionParams(connectionParams);
49
- return Promise.resolve(new User(claims, accessToken ?? ''));
49
+ return Promise.resolve(new User(input.claims, extractToken(input) ?? ''));
50
50
  };
51
51
 
52
52
  export { createSubscriptionContextFactory };
@@ -1 +1 @@
1
- {"version":3,"file":"context.mjs","sources":["../../src/subscriptions/context.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { pick } from 'es-toolkit/compat'\nimport type { IncomingMessage } from 'http'\nimport { User } from '../User'\nimport type { CreateRequestLogger, GraphQLContext, JwtPayload, RequestInfo, RequestInfoLogKey } from '../context'\nimport { buildConnectRequestInfo } from '../request-info'\nimport { extractTokenFromConnectionParams } from './utils'\n\nexport interface SubscriptionContextInput {\n connectRequest: IncomingMessage\n connectionParams?: Readonly<Record<string, unknown>>\n claims?: JwtPayload\n}\n\nexport type CreateSubscriptionUser<T = User | undefined> = (input: SubscriptionContextInput) => Promise<T> | T\nexport type CreateSubscriptionContext<TContext = GraphQLContext> = (input: SubscriptionContextInput) => Promise<TContext>\nexport type AugmentSubscriptionRequestInfo = (input: SubscriptionContextInput) => 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 CreateSubscriptionContextConfig<\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?: AugmentSubscriptionRequestInfo\n claimsToLog?: string[]\n requestInfoToLog?: Array<RequestInfoLogKey>\n augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>\n} & ([User | undefined] extends [TUser] ? { createUser?: CreateSubscriptionUser<TUser> } : { createUser: CreateSubscriptionUser<TUser> })\n\nexport const createSubscriptionContextFactory = <\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n>(\n config: CreateSubscriptionContextConfig<TUser, TAugment, TLogger>,\n): CreateSubscriptionContext<GraphQLContext<TLogger, RequestInfo, TUser> & TAugment> => {\n const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config\n // The conditional type on CreateSubscriptionContextConfig guarantees `createUser` is provided\n // when TUser is narrower than `User | undefined`, so defaulting is sound here.\n const createUser = (config.createUser ?? defaultCreateUser) as CreateSubscriptionUser<TUser>\n\n return async (input: SubscriptionContextInput) => {\n const { connectRequest: req, claims } = input\n\n // build request info from the connect request and socket\n const requestInfo: RequestInfo = {\n ...buildConnectRequestInfo(req),\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 defaultCreateUser: CreateSubscriptionUser<User | undefined> = ({ claims, connectionParams }) => {\n if (!claims) return Promise.resolve(undefined)\n const accessToken = extractTokenFromConnectionParams(connectionParams)\n return Promise.resolve(new User(claims, accessToken ?? ''))\n}\n"],"names":[],"mappings":";;;;;AAgCO,MAAM,gCAAgC,GAAG,CAK9C,MAAiE,KACoB;AACrF,IAAA,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,GAAG,MAAM;;;IAGnG,MAAM,UAAU,IAAI,MAAM,CAAC,UAAU,IAAI,iBAAiB,CAAkC;AAE5F,IAAA,OAAO,OAAO,KAA+B,KAAI;QAC/C,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK;;AAG7C,QAAA,MAAM,WAAW,GAAgB;YAC/B,GAAG,uBAAuB,CAAC,GAAG,CAAC;AAC/B,YAAA,GAAG,kBAAkB,GAAG,KAAK,CAAC;SAC/B;AAED,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,iBAAiB,GAA6C,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAI;AACnG,IAAA,IAAI,CAAC,MAAM;AAAE,QAAA,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;AAC9C,IAAA,MAAM,WAAW,GAAG,gCAAgC,CAAC,gBAAgB,CAAC;AACtE,IAAA,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC;AAC7D,CAAC;;;;"}
1
+ {"version":3,"file":"context.mjs","sources":["../../src/subscriptions/context.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { pick } from 'es-toolkit/compat'\nimport type { IncomingMessage } from 'http'\nimport { User } from '../User'\nimport type { CreateRequestLogger, GraphQLContext, JwtPayload, RequestInfo, RequestInfoLogKey } from '../context'\nimport { buildConnectRequestInfo } from '../request-info'\nimport { defaultExtractSubscriptionToken } from './utils'\n\nexport interface SubscriptionContextInput {\n connectRequest: IncomingMessage\n connectionParams?: Readonly<Record<string, unknown>>\n claims?: JwtPayload\n}\n\nexport type CreateSubscriptionUser<T = User | undefined> = (input: SubscriptionContextInput) => Promise<T> | T\nexport type CreateSubscriptionContext<TContext = GraphQLContext> = (input: SubscriptionContextInput) => Promise<TContext>\nexport type AugmentSubscriptionRequestInfo = (input: SubscriptionContextInput) => Record<string, unknown>\n// The token is extracted before the connection is authenticated, so the input has no claims.\nexport type ExtractSubscriptionToken = (input: Omit<SubscriptionContextInput, 'claims'>) => 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 CreateSubscriptionContextConfig<\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?: AugmentSubscriptionRequestInfo\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`. Use the same function as\n * the `useSubscriptionsServer` `extractToken` option. Not called when you supply `createUser`.\n * Defaults to the bearer token in the `authorization` or `Authorization` connection parameter.\n */\n extractToken?: ExtractSubscriptionToken\n} & ([User | undefined] extends [TUser] ? { createUser?: CreateSubscriptionUser<TUser> } : { createUser: CreateSubscriptionUser<TUser> })\n\nexport const createSubscriptionContextFactory = <\n TUser = User | undefined,\n TAugment extends Record<string, unknown> = Record<string, never>,\n TLogger extends Logger = Logger,\n>(\n config: CreateSubscriptionContextConfig<TUser, TAugment, TLogger>,\n): CreateSubscriptionContext<GraphQLContext<TLogger, RequestInfo, TUser> & TAugment> => {\n const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config\n // The conditional type on CreateSubscriptionContextConfig guarantees `createUser` is provided\n // when TUser is narrower than `User | undefined`, so defaulting is sound here.\n const createUser = (config.createUser ??\n buildDefaultCreateUser(config.extractToken ?? defaultExtractSubscriptionToken)) as CreateSubscriptionUser<TUser>\n\n return async (input: SubscriptionContextInput) => {\n const { connectRequest: req, claims } = input\n\n // build request info from the connect request and socket\n const requestInfo: RequestInfo = {\n ...buildConnectRequestInfo(req),\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 buildDefaultCreateUser =\n (extractToken: ExtractSubscriptionToken): CreateSubscriptionUser<User | undefined> =>\n (input) => {\n if (!input.claims) return Promise.resolve(undefined)\n return Promise.resolve(new User(input.claims, extractToken(input) ?? ''))\n }\n"],"names":[],"mappings":";;;;;AAwCO,MAAM,gCAAgC,GAAG,CAK9C,MAAiE,KACoB;AACrF,IAAA,MAAM,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,GAAG,MAAM;;;AAGnG,IAAA,MAAM,UAAU,IAAI,MAAM,CAAC,UAAU;QACnC,sBAAsB,CAAC,MAAM,CAAC,YAAY,IAAI,+BAA+B,CAAC,CAAkC;AAElH,IAAA,OAAO,OAAO,KAA+B,KAAI;QAC/C,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK;;AAG7C,QAAA,MAAM,WAAW,GAAgB;YAC/B,GAAG,uBAAuB,CAAC,GAAG,CAAC;AAC/B,YAAA,GAAG,kBAAkB,GAAG,KAAK,CAAC;SAC/B;AAED,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,sBAAsB,GAC1B,CAAC,YAAsC,KACvC,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;;;;"}
@@ -1,9 +1,9 @@
1
1
  import type { Logger } from '@makerx/node-common';
2
- import type { GraphQLSchema } from 'graphql';
2
+ import { type GraphQLSchema } from 'graphql';
3
3
  import type { Server } from 'http';
4
4
  import type { GraphQLContext, JwtPayload } from '../context';
5
- import type { CreateSubscriptionContext } from './context';
6
- export declare function useSubscriptionsServer<TLogger extends Logger = Logger>({ schema, httpServer, createSubscriptionContext, logger, operationLogLevel, path, verifyToken, requireAuth, jwtClaimsToLog, resolveSubscriptionOperationLogger, includeDeprecatedElements, }: {
5
+ import type { CreateSubscriptionContext, ExtractSubscriptionToken } from './context';
6
+ export declare function useSubscriptionsServer<TLogger extends Logger = Logger>({ schema, httpServer, createSubscriptionContext, logger, operationLogLevel, path, verifyToken, extractToken, requireAuth, jwtClaimsToLog, resolveSubscriptionOperationLogger, includeDeprecatedElements, connectionExpiry, keepAlive, }: {
7
7
  schema: GraphQLSchema;
8
8
  httpServer: Server;
9
9
  createSubscriptionContext: CreateSubscriptionContext;
@@ -11,6 +11,12 @@ export declare function useSubscriptionsServer<TLogger extends Logger = Logger>(
11
11
  operationLogLevel?: keyof TLogger;
12
12
  path?: string;
13
13
  verifyToken?: (host: string, token: string) => Promise<JwtPayload>;
14
+ /**
15
+ * Returns the token to pass to `verifyToken`, e.g. from a JWT assertion header that a proxy adds
16
+ * to the connect request. Defaults to the bearer token in the `authorization` or `Authorization`
17
+ * connection parameter. Pass the same function to `createSubscriptionContextFactory`.
18
+ */
19
+ extractToken?: ExtractSubscriptionToken;
14
20
  requireAuth?: boolean;
15
21
  jwtClaimsToLog?: string[];
16
22
  resolveSubscriptionOperationLogger?: (context: GraphQLContext) => TLogger;
@@ -19,4 +25,39 @@ export declare function useSubscriptionsServer<TLogger extends Logger = Logger>(
19
25
  * established. Not collected per emitted payload: the usage is a property of the operation.
20
26
  */
21
27
  includeDeprecatedElements?: boolean;
28
+ /**
29
+ * Returns the time a connection ends, in milliseconds since the epoch, from the claims `verifyToken`
30
+ * returned when it was established. `undefined` keeps the default behaviour below.
31
+ *
32
+ * When it returns a time:
33
+ * - A time already past refuses the connection with `4403 Forbidden`, as an invalid token does.
34
+ * - Otherwise at that time the connection is closed with `4403`, which ends every subscription
35
+ * running on it.
36
+ * - Until then the client may subscribe again on the connection, and the token is not verified
37
+ * again: it was verified when the connection was established, and it cannot change for the life
38
+ * of the connection.
39
+ *
40
+ * To end a connection when its token expires: `(claims) => (claims.exp === undefined ? undefined : claims.exp * 1000)`.
41
+ * When the token is short-lived proof of a longer session, such as an assertion that a proxy adds to
42
+ * the upgrade request, a later time keeps subscriptions from being cut off every few minutes:
43
+ * `(claims) => (claims.iat === undefined ? undefined : (claims.iat + 3600) * 1000)`. A connection then
44
+ * outlives its token, so a revoked session is noticed only when the connection ends.
45
+ *
46
+ * By default a connection has no set end. Its token is verified again each time the client
47
+ * subscribes, and a token that no longer verifies closes the connection with `4403` then.
48
+ * Subscriptions already running continue until that happens.
49
+ *
50
+ * graphql-ws clients retry `4403` by default, but a retry succeeds only with a fresh token: the
51
+ * client's `connectionParams` must be a function that returns a current token, or the upgrade
52
+ * request must be authenticated afresh (by a cookie and a proxy, for example). A client that sends
53
+ * the same expired token fails again until its `retryAttempts` run out, and its subscriptions then
54
+ * end with an error.
55
+ */
56
+ connectionExpiry?: (claims: JwtPayload) => number | undefined;
57
+ /**
58
+ * Milliseconds between the server's websocket pings. A connection that has not answered a ping by
59
+ * the time the next one is due is terminated. `0` turns the pings off. Defaults to graphql-ws's 12
60
+ * seconds.
61
+ */
62
+ keepAlive?: number;
22
63
  }): import("graphql-ws").Disposable;