@makerx/graphql-core 2.3.0 → 3.0.0-beta.1

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
@@ -10,41 +10,45 @@ Note: See explanation on \*Express peer dependency below.
10
10
 
11
11
  `createContextFactory` returns a function that creates your GraphQL context using a standard (extensible) representation, including:
12
12
 
13
- - `logger`: a logger instance to use downstream of resolvers, usually logging some request metadata to assist correlating log entries (for example the X-Correlation-Id header value)
14
- - `requestInfo`: useful request info, for example to define per-request behaviour (multi-tenant apps), pass through correlation headers to downstream services etc
15
- - `user`: an object representing the user or system identity (see definition below, defaults to creating a `User` based on JWT claims)
16
- - anything else you wish to add to the context
13
+ - `logger`: a logger instance to use downstream of resolvers, built by your `requestLogger` factory, which receives both the resolved request metadata and the resolved `user` so you can enrich log output with user-derived fields (see [Request logger](#request-logger))
14
+ - `requestInfo`: useful request info — `source` (`http` or `subscription`), `protocol` (`http`/`https`/`ws`/`wss`), `host`, `baseUrl`, `url`, correlation/client headers, etc. Use it for per-request behaviour (multi-tenant apps), passing correlation headers downstream, etc. See [Request info](#request-info)
15
+ - `user`: an object representing the user or system identity (see [User](#user); defaults to a `User` built from JWT claims when `createUser` is omitted)
16
+ - anything else you wish to add to the context via `augmentContext`
17
17
 
18
18
  ### Step 1 - Define your context + creation
19
19
 
20
20
  context.ts
21
21
 
22
22
  ```ts
23
- // define the base context type, setting the logger type
24
- type BaseContext = GraphQLContextBase<Logger>
25
23
  // define the extra stuff added to our app's context
26
24
  type ExtraContext = {
27
25
  services: Services
28
26
  loaders: Loaders
29
27
  }
30
- // our app's context type, returned from the createContext function
31
- export type GraphQLContext = BaseContext & ExtraContext
32
28
 
33
29
  // configure the createContext function
34
- export const createContext = createContextFactory<GraphQLContext>({
35
- // set the keys of the user claims (JWT payload) we want added to the request metadata passed to the requestLogger factory
30
+ // TUser is inferred from `createUser`, TAugment is inferred from `augmentContext`'s return type
31
+ export const createContext = createContextFactory({
32
+ // keys of the user claims (JWT payload) to include in the request metadata passed to the requestLogger factory
36
33
  claimsToLog: ['oid', 'aud', 'tid', 'azp', 'iss', 'scp', 'roles'],
37
- // set the keys of the request info we want added to the request metadata passed to the requestLogger factory
34
+ // keys of the request info to include in the request metadata passed to the requestLogger factory
38
35
  requestInfoToLog: ['origin', 'requestId', 'correlationId'],
39
- // use a winston child logger to add metadata to log output
40
- requestLogger: (requestMetadata) => logger.child(requestMetadata),
41
- // build the rest of the app context
36
+ // build the per-request logger; receives the request metadata and the resolved user
37
+ // e.g. enrich log output with user-derived fields like multi-tenant `instance`
38
+ requestLogger: (requestMetadata, user) => logger.child({ ...requestMetadata, instance: user?.instance }),
39
+ // resolve the user for each request — optional; omit to use the default User-from-JWT behaviour
40
+ // (required when you supply a narrower TUser generic)
41
+ createUser: async ({ claims }) => new AppUser(claims),
42
+ // build the rest of the app context — annotate the return type to lock in inference
42
43
  augmentContext: (context): ExtraContext => {
43
44
  const services = createServices(context)
44
45
  const loaders = createLoaders(services)
45
46
  return { services, loaders }
46
47
  },
47
48
  })
49
+
50
+ // derive the full context type from the factory's return type
51
+ export type GraphQLContext = Awaited<ReturnType<typeof createContext>>
48
52
  ```
49
53
 
50
54
  ### Step 2 - Map the context creation to implementation
@@ -83,6 +87,50 @@ const graphqlServer = createServer({
83
87
  })
84
88
  ```
85
89
 
90
+ ## Request info
91
+
92
+ `context.requestInfo` is built for every request — both HTTP and websocket subscription connects — so downstream code can distinguish sources, rebuild URLs, pass through correlation headers, etc.
93
+
94
+ | Field | Description |
95
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96
+ | `requestId` | `x-request-id` header if present, otherwise a freshly generated UUID. |
97
+ | `source` | `'http'` for regular requests, `'subscription'` for websocket connects. |
98
+ | `protocol` | `'http'` / `'https'` for HTTP, `'ws'` / `'wss'` for subscriptions (resolved via `x-forwarded-proto` or TLS socket encryption). |
99
+ | `host` | Hostname only (no port). Prefers `x-forwarded-host`, falls back to the `Host` header, then `req.hostname` (Express only). |
100
+ | `port` | Port parsed from `x-forwarded-host` / `Host` header when present; `undefined` otherwise. |
101
+ | `baseUrl` | Fully-qualified origin (`scheme://host[:port]`) with default ports stripped. For subscriptions the scheme is normalised to `http(s)` so the value composes with relative URLs. |
102
+ | `url` | `req.originalUrl` for HTTP, `req.url` for subscription connects. |
103
+ | `origin` | `Origin` header. |
104
+ | `referer` | `Referer` header. |
105
+ | `correlationId` | `x-correlation-id` header. |
106
+ | `arrLogId` | `x-arr-log-id` header (Azure Front Door / ARR). |
107
+ | `clientIp` | First value from `x-forwarded-for`, falling back to `socket.remoteAddress`. |
108
+ | `userAgent` | `User-Agent` header. |
109
+
110
+ You can add more via `augmentRequestInfo(input)`. Lambda deployments also get `functionName` and `awsRequestId` when a `LambdaContext` is supplied.
111
+
112
+ Helpers are exported for custom wiring: `buildBaseRequestInfo(req)` (Express), `buildConnectRequestInfo(req)` (websocket `IncomingMessage`), and `requestBaseUrl` / `connectRequestBaseUrl`.
113
+
114
+ ## Request logger
115
+
116
+ The `requestLogger` config accepts either a pre-built `Logger` or a factory `(requestMetadata, user) => Logger`.
117
+
118
+ The factory form runs per request and receives:
119
+
120
+ - `requestMetadata` — an object containing `request` (the subset of `requestInfo` selected by `requestInfoToLog`) and `user` (the subset of claims selected by `claimsToLog`)
121
+ - `user` — the resolved `user` value returned by `createUser` (typed as your `TUser`)
122
+
123
+ This lets you enrich log output with fields derived from the resolved user, for example a multi-tenant instance id or an internal user id from your database, that aren't present on the raw JWT claims:
124
+
125
+ ```ts
126
+ requestLogger: (requestMetadata, user) =>
127
+ logger.child({
128
+ ...requestMetadata,
129
+ instance: user?.instance,
130
+ userId: user?.id,
131
+ }),
132
+ ```
133
+
86
134
  ## User
87
135
 
88
136
  By default, if `claims` (decoded token `JwtPayload`) are available, the `GraphQLContext.user` property will be set by constructing a `User` instance.
@@ -140,29 +188,36 @@ This library includes a `subscriptions` module to provide simple setup using the
140
188
  Example showing both normal context + subscription context creation:
141
189
 
142
190
  ```ts
143
- const augmentContext = (context: GraphQLContext) => {
191
+ type ExtraContext = { services: Services; dataSource: DataSource; dataLoaders: DataLoaders }
192
+
193
+ // the `context` arg is typed `GraphQLContext<Logger, RequestInfo, AppUser | undefined>` here —
194
+ // TUser flows through from `createUser`, so just annotate the return type and let inference do the rest
195
+ const augmentContext = (context: GraphQLContext<Logger, RequestInfo, AppUser | undefined>): ExtraContext => {
144
196
  const services = createServices(context)
145
197
  const dataLoaders = createDataLoaders()
146
198
  return { services, dataSource, dataLoaders }
147
199
  }
148
200
 
149
- // create a context using request based input
150
- const createContext = createContextFactory<GraphQLContext>({
201
+ // create a context using request based input — TUser / TAugment inferred from the config
202
+ const createContext = createContextFactory({
151
203
  claimsToLog,
152
204
  requestInfoToLog,
153
- requestLogger: (requestMetadata) => logger.child(requestMetadata),
205
+ requestLogger: (requestMetadata, user) => logger.child({ ...requestMetadata, instance: user?.instance }),
154
206
  createUser: ({ claims, req }) => findUpdateOrCreateUser(claims, req.headers.authorization?.substring(7)),
155
207
  augmentContext,
156
208
  })
157
209
 
158
210
  // create a context using graphql-ws Server#context callback input
159
- const createSubscriptionContext = createSubscriptionContextFactory<GraphQLContext>({
160
- claimsToLog
211
+ const createSubscriptionContext = createSubscriptionContextFactory({
212
+ claimsToLog,
161
213
  requestInfoToLog,
162
- requestLogger: (requestMetadata) => logger.child(requestMetadata),
214
+ requestLogger: (requestMetadata, user) => logger.child({ ...requestMetadata, instance: user?.instance }),
163
215
  createUser: ({ claims, connectionParams }) => findUpdateOrCreateUser(claims, extractTokenFromConnectionParams(connectionParams)),
164
216
  augmentContext,
165
217
  })
218
+
219
+ // share one context type between query and subscription paths
220
+ export type GraphQLContext = Awaited<ReturnType<typeof createContext>>
166
221
  ```
167
222
 
168
223
  1. Create a subscriptions server, using the ws-server cleanup function in your server lifecycle.
@@ -204,85 +259,6 @@ This library includes a `subscriptions` module to provide simple setup using the
204
259
 
205
260
  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.
206
261
 
207
- ## Testing
208
-
209
- The testing submodule exports utility functions for easily constructing ApolloClient instances for integration testing on NodeJS. The `errorPolicy` is set to `all` so that returned errors can be checked.
210
-
211
- ### Setup
212
-
213
- If you use this module, you need to install `@apollo/client`:
214
-
215
- ```
216
- npm install --save-dev @apollo/client
217
- ```
218
-
219
- ### Usage
220
-
221
- - `createTestClient` accepts a url and optional accessToken.
222
- - `createTestClientWithClientCredentials` accepts a url and client credentials config and will fetch and attach an access token to each request.
223
-
224
- testing.ts
225
-
226
- ```ts
227
- export const testClient = createTestClientWithClientCredentials(process.env.INTEGRATION_TEST_URL, clientCredentialsConfig)
228
-
229
- export const unauthenticatedClient = createTestClient(process.env.INTEGRATION_TEST_URL)
230
- ```
231
-
232
- tweets.spec.ts
233
-
234
- ```ts
235
- describe('tweets query', () => {
236
- const tweetsQuery = gql`
237
- query Tweets($input: TweetsWhere) {
238
- tweets(input: $input) {
239
- data {
240
- text
241
- createdAt
242
- }
243
- }
244
- }
245
- `
246
-
247
- it('returns tweets with sensible default limit', async () => {
248
- const {
249
- data: { tweets },
250
- errors,
251
- } = await testClient.query<TweetsQuery>({
252
- query: tweetsQuery,
253
- })
254
-
255
- expect(errors).toBeUndefined()
256
- expect(tweets).toBeDefined()
257
- expect(tweets?.data?.length).toBe(10)
258
- })
259
-
260
- it('guards against high limit', async () => {
261
- const tooHighLimit = 101
262
- await expect(async () => {
263
- await testClient.query<TweetsQuery, TweetsQueryVariables>({
264
- query: tweetsQuery,
265
- variables: {
266
- input: {
267
- maxResults: tooHighLimit,
268
- },
269
- },
270
- })
271
- }).rejects.toThrowErrorMatchingInlineSnapshot(`"Response not successful: Received status code 400"`)
272
- })
273
-
274
- it('requires authorisation', async () => {
275
- const { data, errors } = await unauthenticatedClient.query<TweetsQuery, TweetsQueryVariables>({
276
- query: tweetsQuery,
277
- })
278
-
279
- expect(data.tweets).toBeNull()
280
- expect(errors?.length).toBe(1)
281
- expect(errors?.[0].message).toMatchInlineSnapshot(`"User is not authorized to access Query.tweets"`)
282
- })
283
- })
284
- ```
285
-
286
262
  ## Utils
287
263
 
288
264
  - `isIntrospectionQuery`: indicates whether the query is an introspection query, based on the operation name or query content.
package/context.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Logger } from '@makerx/node-common';
2
2
  import type { Request } from 'express';
3
+ import { type BaseRequestInfo } from './request-info';
3
4
  import { User } from './User';
4
5
  export interface GraphQLContext<TLogger extends Logger = Logger, TRequestInfo extends BaseRequestInfo = RequestInfo, TUser = User | undefined> {
5
6
  logger: TLogger;
@@ -8,19 +9,6 @@ export interface GraphQLContext<TLogger extends Logger = Logger, TRequestInfo ex
8
9
  started: number;
9
10
  }
10
11
  export type AnyGraphqlContext = GraphQLContext<any, any, any>;
11
- export interface BaseRequestInfo extends Record<string, unknown> {
12
- requestId: string;
13
- protocol: 'http' | 'https' | 'ws';
14
- host: string;
15
- method: string;
16
- url: string;
17
- origin: string;
18
- referer?: string;
19
- correlationId?: string;
20
- arrLogId?: string;
21
- clientIp?: string;
22
- userAgent?: string;
23
- }
24
12
  export interface LambdaContext {
25
13
  functionName?: string;
26
14
  awsRequestId?: string;
@@ -28,6 +16,10 @@ export interface LambdaContext {
28
16
  export type LambdaEvent = never;
29
17
  export type LambdaRequestInfo = BaseRequestInfo & LambdaContext;
30
18
  export type RequestInfo = BaseRequestInfo | LambdaRequestInfo;
19
+ type KnownKeys<T> = keyof {
20
+ [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K];
21
+ };
22
+ export type RequestInfoLogKey = KnownKeys<BaseRequestInfo> | keyof LambdaContext | (string & {});
31
23
  export interface JwtPayload {
32
24
  [key: string]: unknown;
33
25
  name?: string | undefined;
@@ -57,16 +49,19 @@ export interface ContextInput {
57
49
  event?: LambdaEvent;
58
50
  }
59
51
  export type CreateContext<TContext = GraphQLContext> = (input: ContextInput) => Promise<TContext>;
60
- export type CreateRequestLogger = (requestMetadata: Record<string, unknown>) => Logger;
52
+ export type CreateRequestLogger<TUser = User | undefined, TLogger extends Logger = Logger> = (requestMetadata: Record<string, unknown>, user: TUser) => TLogger;
61
53
  export type AugmentRequestInfo = (input: ContextInput) => Record<string, unknown>;
62
- export interface CreateContextConfig<TContext extends AnyGraphqlContext = GraphQLContext> {
63
- requestLogger: CreateRequestLogger | Logger;
54
+ export type CreateContextConfig<TUser = User | undefined, TAugment extends Record<string, unknown> = Record<string, never>, TLogger extends Logger = Logger> = {
55
+ requestLogger: CreateRequestLogger<TUser, TLogger> | TLogger;
64
56
  augmentRequestInfo?: AugmentRequestInfo;
65
57
  claimsToLog?: string[];
66
- createUser: CreateUser<InferUserFromContext<TContext>>;
67
- requestInfoToLog?: Array<keyof RequestInfo>;
68
- augmentContext?: (context: TContext) => Record<string, unknown> | Promise<Record<string, unknown>>;
69
- }
70
- export declare const buildBaseRequestInfo: (req: Request) => BaseRequestInfo;
71
- export declare const createContextFactory: <TContext extends AnyGraphqlContext = GraphQLContext>({ requestLogger, augmentRequestInfo, claimsToLog, createUser, requestInfoToLog, augmentContext, }: CreateContextConfig<TContext>) => CreateContext<TContext>;
58
+ requestInfoToLog?: Array<RequestInfoLogKey>;
59
+ augmentContext?: (context: GraphQLContext<TLogger, RequestInfo, TUser>) => TAugment | Promise<TAugment>;
60
+ } & ([User | undefined] extends [TUser] ? {
61
+ createUser?: CreateUser<TUser>;
62
+ } : {
63
+ createUser: CreateUser<TUser>;
64
+ });
65
+ export declare const createContextFactory: <TUser = User | undefined, TAugment extends Record<string, unknown> = Record<string, never>, TLogger extends Logger = Logger>(config: CreateContextConfig<TUser, TAugment, TLogger>) => CreateContext<GraphQLContext<TLogger, RequestInfo, TUser> & TAugment>;
72
66
  export declare const defaultCreateUser: CreateUser<User | undefined>;
67
+ export {};
package/context.js CHANGED
@@ -1,28 +1,18 @@
1
1
  'use strict';
2
2
 
3
- var crypto = require('crypto');
4
- var lodash = require('lodash');
3
+ var compat = require('es-toolkit/compat');
4
+ var requestInfo = require('./request-info.js');
5
5
  var User = require('./User.js');
6
6
 
7
- const buildBaseRequestInfo = (req) => ({
8
- requestId: req.headers['x-request-id']?.toString() ?? crypto.randomUUID(),
9
- protocol: req.protocol,
10
- host: req.hostname ?? '',
11
- method: req.method ?? '',
12
- url: req.originalUrl,
13
- origin: req.get('Origin') ?? '',
14
- referer: req.headers.referer?.toString() ?? '',
15
- arrLogId: req.headers['x-arr-log-id']?.toString() ?? undefined,
16
- clientIp: req.headers['x-forwarded-for']?.toString() ?? req.socket.remoteAddress,
17
- correlationId: req.headers['x-correlation-id']?.toString() ?? undefined,
18
- userAgent: req.headers['user-agent']?.toString() ?? undefined,
19
- });
20
- const createContextFactory = ({ requestLogger, augmentRequestInfo, claimsToLog, createUser, requestInfoToLog, augmentContext, }) => {
21
- // the function that creates the GraphQL context
7
+ const createContextFactory = (config) => {
8
+ const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config;
9
+ // The conditional type on CreateContextConfig guarantees `createUser` is provided when TUser is
10
+ // narrower than `User | undefined`, so defaulting to defaultCreateUser is sound here.
11
+ const createUser = (config.createUser ?? defaultCreateUser);
22
12
  return async (input) => {
23
13
  const { req, claims, context } = input;
24
14
  // build base request info from the request
25
- const baseRequestInfo = buildBaseRequestInfo(req);
15
+ const baseRequestInfo = requestInfo.buildBaseRequestInfo(req);
26
16
  // add lambda info from the context, if present
27
17
  let lambdaRequestInfo;
28
18
  if (context) {
@@ -34,12 +24,13 @@ const createContextFactory = ({ requestLogger, augmentRequestInfo, claimsToLog,
34
24
  };
35
25
  }
36
26
  // augment request info with the supplied function, if present
37
- let requestInfo = lambdaRequestInfo ?? baseRequestInfo;
27
+ let requestInfo$1 = lambdaRequestInfo ?? baseRequestInfo;
38
28
  if (augmentRequestInfo)
39
- requestInfo = {
40
- ...requestInfo,
29
+ requestInfo$1 = {
30
+ ...requestInfo$1,
41
31
  ...augmentRequestInfo(input),
42
32
  };
33
+ const user = await createUser(input);
43
34
  // create request logger
44
35
  let logger;
45
36
  if (typeof requestLogger === 'function') {
@@ -47,24 +38,22 @@ const createContextFactory = ({ requestLogger, augmentRequestInfo, claimsToLog,
47
38
  const requestLoggerMetadata = {};
48
39
  // add request info to log
49
40
  if (requestInfoToLog?.length)
50
- requestLoggerMetadata.request = lodash.pick(requestInfo, requestInfoToLog);
41
+ requestLoggerMetadata.request = compat.pick(requestInfo$1, requestInfoToLog);
51
42
  // add user claims to log
52
43
  if (claims && claimsToLog?.length)
53
- requestLoggerMetadata.user = lodash.pick(claims, claimsToLog);
44
+ requestLoggerMetadata.user = compat.pick(claims, claimsToLog);
54
45
  // build the request logger
55
- logger = requestLogger(requestLoggerMetadata);
46
+ logger = requestLogger(requestLoggerMetadata, user);
56
47
  }
57
48
  else
58
49
  logger = requestLogger;
59
50
  const graphqlContext = {
60
- requestInfo,
51
+ requestInfo: requestInfo$1,
61
52
  logger,
62
- user: createUser ? await createUser(input) : undefined,
53
+ user,
63
54
  started: Date.now(),
64
55
  };
65
- const augmentedGraphQLContext = augmentContext
66
- ? { ...graphqlContext, ...(await augmentContext(graphqlContext)) }
67
- : graphqlContext;
56
+ const augmentedGraphQLContext = augmentContext ? { ...graphqlContext, ...(await augmentContext(graphqlContext)) } : graphqlContext;
68
57
  return augmentedGraphQLContext;
69
58
  };
70
59
  };
@@ -75,7 +64,6 @@ const defaultCreateUser = ({ req, claims }) => {
75
64
  return Promise.resolve(new User.User(claims, accessToken));
76
65
  };
77
66
 
78
- exports.buildBaseRequestInfo = buildBaseRequestInfo;
79
67
  exports.createContextFactory = createContextFactory;
80
68
  exports.defaultCreateUser = defaultCreateUser;
81
69
  //# sourceMappingURL=context.js.map
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 { randomUUID } from 'crypto'\nimport type { Request } from 'express'\nimport { pick } from 'lodash'\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 BaseRequestInfo extends Record<string, unknown> {\n requestId: string\n protocol: 'http' | 'https' | 'ws'\n host: string\n method: string\n url: string\n origin: string\n referer?: string\n correlationId?: string\n arrLogId?: string\n clientIp?: string\n userAgent?: string\n}\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// 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 = (requestMetadata: Record<string, unknown>) => Logger\nexport type AugmentRequestInfo = (input: ContextInput) => Record<string, unknown>\n\nexport interface CreateContextConfig<TContext extends AnyGraphqlContext = GraphQLContext> {\n requestLogger: CreateRequestLogger | Logger\n augmentRequestInfo?: AugmentRequestInfo\n claimsToLog?: string[]\n createUser: CreateUser<InferUserFromContext<TContext>>\n requestInfoToLog?: Array<keyof RequestInfo>\n augmentContext?: (context: TContext) => Record<string, unknown> | Promise<Record<string, unknown>>\n}\n\nexport const buildBaseRequestInfo = (req: Request): BaseRequestInfo => ({\n requestId: req.headers['x-request-id']?.toString() ?? randomUUID(),\n protocol: req.protocol as 'http' | 'https',\n host: req.hostname ?? '',\n method: req.method ?? '',\n url: req.originalUrl,\n origin: req.get('Origin') ?? '',\n referer: req.headers.referer?.toString() ?? '',\n arrLogId: req.headers['x-arr-log-id']?.toString() ?? undefined,\n clientIp: req.headers['x-forwarded-for']?.toString() ?? req.socket.remoteAddress,\n correlationId: req.headers['x-correlation-id']?.toString() ?? undefined,\n userAgent: req.headers['user-agent']?.toString() ?? undefined,\n})\n\nexport const createContextFactory = <TContext extends AnyGraphqlContext = GraphQLContext>({\n requestLogger,\n augmentRequestInfo,\n claimsToLog,\n createUser,\n requestInfoToLog,\n augmentContext,\n}: CreateContextConfig<TContext>): CreateContext<TContext> => {\n // the function that creates the GraphQL context\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 // create request logger\n let logger: Logger\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)\n } else logger = requestLogger\n\n const graphqlContext: GraphQLContext = {\n requestInfo,\n logger,\n user: createUser ? await createUser(input) : undefined,\n started: Date.now(),\n }\n\n const augmentedGraphQLContext = augmentContext\n ? { ...graphqlContext, ...(await augmentContext(graphqlContext as TContext)) }\n : graphqlContext\n\n return augmentedGraphQLContext as TContext\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":["randomUUID","pick","User"],"mappings":"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"}
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;;;;;"}
package/context.mjs CHANGED
@@ -1,22 +1,12 @@
1
- import { randomUUID } from 'crypto';
2
- import { pick } from 'lodash';
1
+ import { pick } from 'es-toolkit/compat';
2
+ import { buildBaseRequestInfo } from './request-info.mjs';
3
3
  import { User } from './User.mjs';
4
4
 
5
- const buildBaseRequestInfo = (req) => ({
6
- requestId: req.headers['x-request-id']?.toString() ?? randomUUID(),
7
- protocol: req.protocol,
8
- host: req.hostname ?? '',
9
- method: req.method ?? '',
10
- url: req.originalUrl,
11
- origin: req.get('Origin') ?? '',
12
- referer: req.headers.referer?.toString() ?? '',
13
- arrLogId: req.headers['x-arr-log-id']?.toString() ?? undefined,
14
- clientIp: req.headers['x-forwarded-for']?.toString() ?? req.socket.remoteAddress,
15
- correlationId: req.headers['x-correlation-id']?.toString() ?? undefined,
16
- userAgent: req.headers['user-agent']?.toString() ?? undefined,
17
- });
18
- const createContextFactory = ({ requestLogger, augmentRequestInfo, claimsToLog, createUser, requestInfoToLog, augmentContext, }) => {
19
- // the function that creates the GraphQL context
5
+ const createContextFactory = (config) => {
6
+ const { requestLogger, augmentRequestInfo, claimsToLog, requestInfoToLog, augmentContext } = config;
7
+ // The conditional type on CreateContextConfig guarantees `createUser` is provided when TUser is
8
+ // narrower than `User | undefined`, so defaulting to defaultCreateUser is sound here.
9
+ const createUser = (config.createUser ?? defaultCreateUser);
20
10
  return async (input) => {
21
11
  const { req, claims, context } = input;
22
12
  // build base request info from the request
@@ -38,6 +28,7 @@ const createContextFactory = ({ requestLogger, augmentRequestInfo, claimsToLog,
38
28
  ...requestInfo,
39
29
  ...augmentRequestInfo(input),
40
30
  };
31
+ const user = await createUser(input);
41
32
  // create request logger
42
33
  let logger;
43
34
  if (typeof requestLogger === 'function') {
@@ -50,19 +41,17 @@ const createContextFactory = ({ requestLogger, augmentRequestInfo, claimsToLog,
50
41
  if (claims && claimsToLog?.length)
51
42
  requestLoggerMetadata.user = pick(claims, claimsToLog);
52
43
  // build the request logger
53
- logger = requestLogger(requestLoggerMetadata);
44
+ logger = requestLogger(requestLoggerMetadata, user);
54
45
  }
55
46
  else
56
47
  logger = requestLogger;
57
48
  const graphqlContext = {
58
49
  requestInfo,
59
50
  logger,
60
- user: createUser ? await createUser(input) : undefined,
51
+ user,
61
52
  started: Date.now(),
62
53
  };
63
- const augmentedGraphQLContext = augmentContext
64
- ? { ...graphqlContext, ...(await augmentContext(graphqlContext)) }
65
- : graphqlContext;
54
+ const augmentedGraphQLContext = augmentContext ? { ...graphqlContext, ...(await augmentContext(graphqlContext)) } : graphqlContext;
66
55
  return augmentedGraphQLContext;
67
56
  };
68
57
  };
@@ -73,5 +62,5 @@ const defaultCreateUser = ({ req, claims }) => {
73
62
  return Promise.resolve(new User(claims, accessToken));
74
63
  };
75
64
 
76
- export { buildBaseRequestInfo, createContextFactory, defaultCreateUser };
65
+ export { createContextFactory, defaultCreateUser };
77
66
  //# 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 { randomUUID } from 'crypto'\nimport type { Request } from 'express'\nimport { pick } from 'lodash'\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 BaseRequestInfo extends Record<string, unknown> {\n requestId: string\n protocol: 'http' | 'https' | 'ws'\n host: string\n method: string\n url: string\n origin: string\n referer?: string\n correlationId?: string\n arrLogId?: string\n clientIp?: string\n userAgent?: string\n}\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// 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 = (requestMetadata: Record<string, unknown>) => Logger\nexport type AugmentRequestInfo = (input: ContextInput) => Record<string, unknown>\n\nexport interface CreateContextConfig<TContext extends AnyGraphqlContext = GraphQLContext> {\n requestLogger: CreateRequestLogger | Logger\n augmentRequestInfo?: AugmentRequestInfo\n claimsToLog?: string[]\n createUser: CreateUser<InferUserFromContext<TContext>>\n requestInfoToLog?: Array<keyof RequestInfo>\n augmentContext?: (context: TContext) => Record<string, unknown> | Promise<Record<string, unknown>>\n}\n\nexport const buildBaseRequestInfo = (req: Request): BaseRequestInfo => ({\n requestId: req.headers['x-request-id']?.toString() ?? randomUUID(),\n protocol: req.protocol as 'http' | 'https',\n host: req.hostname ?? '',\n method: req.method ?? '',\n url: req.originalUrl,\n origin: req.get('Origin') ?? '',\n referer: req.headers.referer?.toString() ?? '',\n arrLogId: req.headers['x-arr-log-id']?.toString() ?? undefined,\n clientIp: req.headers['x-forwarded-for']?.toString() ?? req.socket.remoteAddress,\n correlationId: req.headers['x-correlation-id']?.toString() ?? undefined,\n userAgent: req.headers['user-agent']?.toString() ?? undefined,\n})\n\nexport const createContextFactory = <TContext extends AnyGraphqlContext = GraphQLContext>({\n requestLogger,\n augmentRequestInfo,\n claimsToLog,\n createUser,\n requestInfoToLog,\n augmentContext,\n}: CreateContextConfig<TContext>): CreateContext<TContext> => {\n // the function that creates the GraphQL context\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 // create request logger\n let logger: Logger\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)\n } else logger = requestLogger\n\n const graphqlContext: GraphQLContext = {\n requestInfo,\n logger,\n user: createUser ? await createUser(input) : undefined,\n started: Date.now(),\n }\n\n const augmentedGraphQLContext = augmentContext\n ? { ...graphqlContext, ...(await augmentContext(graphqlContext as TContext)) }\n : graphqlContext\n\n return augmentedGraphQLContext as TContext\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":"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"}
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;;;;"}
package/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './context';
2
- export * from './User';
3
- export * from './utils';
4
2
  export * from './logging';
3
+ export * from './request-info';
5
4
  export * from './schema-util';
6
- export * from './shield';
7
- export * from './models';
5
+ export * from './type-utils';
6
+ export * from './User';
7
+ export * from './utils';
package/index.js CHANGED
@@ -1,25 +1,25 @@
1
1
  'use strict';
2
2
 
3
3
  var context = require('./context.js');
4
- var User = require('./User.js');
5
- var utils = require('./utils.js');
6
4
  var logging = require('./logging.js');
5
+ var requestInfo = require('./request-info.js');
7
6
  var schemaUtil = require('./schema-util.js');
8
- var shield = require('./shield.js');
7
+ var User = require('./User.js');
8
+ var utils = require('./utils.js');
9
9
 
10
10
 
11
11
 
12
- exports.buildBaseRequestInfo = context.buildBaseRequestInfo;
13
12
  exports.createContextFactory = context.createContextFactory;
14
13
  exports.defaultCreateUser = context.defaultCreateUser;
14
+ exports.logGraphQLOperation = logging.logGraphQLOperation;
15
+ exports.logSubscriptionOperation = logging.logSubscriptionOperation;
16
+ exports.buildBaseRequestInfo = requestInfo.buildBaseRequestInfo;
17
+ exports.buildConnectRequestInfo = requestInfo.buildConnectRequestInfo;
18
+ exports.connectRequestBaseUrl = requestInfo.connectRequestBaseUrl;
19
+ exports.requestBaseUrl = requestInfo.requestBaseUrl;
20
+ exports.requireExplicitResolversForScalars = schemaUtil.requireExplicitResolversForScalars;
15
21
  exports.User = User.User;
16
22
  exports.isIntrospectionQuery = utils.isIntrospectionQuery;
17
23
  exports.isNil = utils.isNil;
18
24
  exports.isNotNil = utils.isNotNil;
19
- exports.logGraphQLOperation = logging.logGraphQLOperation;
20
- exports.logSubscriptionOperation = logging.logSubscriptionOperation;
21
- exports.requireExplicitResolversForScalars = schemaUtil.requireExplicitResolversForScalars;
22
- exports.combineRuleWithAll = shield.combineRuleWithAll;
23
- exports.createRule = shield.createRule;
24
- exports.createShieldSchema = shield.createShieldSchema;
25
25
  //# sourceMappingURL=index.js.map
package/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- export { buildBaseRequestInfo, createContextFactory, defaultCreateUser } from './context.mjs';
2
- export { User } from './User.mjs';
3
- export { isIntrospectionQuery, isNil, isNotNil } from './utils.mjs';
1
+ export { createContextFactory, defaultCreateUser } from './context.mjs';
4
2
  export { logGraphQLOperation, logSubscriptionOperation } from './logging.mjs';
3
+ export { buildBaseRequestInfo, buildConnectRequestInfo, connectRequestBaseUrl, requestBaseUrl } from './request-info.mjs';
5
4
  export { requireExplicitResolversForScalars } from './schema-util.mjs';
6
- export { combineRuleWithAll, createRule, createShieldSchema } from './shield.mjs';
5
+ export { User } from './User.mjs';
6
+ export { isIntrospectionQuery, isNil, isNotNil } from './utils.mjs';
7
7
  //# sourceMappingURL=index.mjs.map