@makerx/graphql-core 3.0.0-beta.6 → 3.0.0-beta.8
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 +33 -3
- package/package.json +1 -1
- package/request-info.d.cts +24 -0
- package/request-info.d.mts +24 -0
- package/request-info.d.ts +24 -0
- package/request-info.js.map +1 -1
- package/request-info.mjs.map +1 -1
- package/shield.d.cts +15 -13
- package/shield.d.mts +15 -13
- package/shield.d.ts +15 -13
- package/shield.js +11 -12
- package/shield.js.map +1 -1
- package/shield.mjs +11 -12
- package/shield.mjs.map +1 -1
- package/subscriptions/index.d.cts +1 -0
- package/subscriptions/index.d.mts +1 -0
- package/subscriptions/index.d.ts +1 -0
- package/subscriptions/index.js +2 -0
- package/subscriptions/index.js.map +1 -1
- package/subscriptions/index.mjs +1 -0
- package/subscriptions/index.mjs.map +1 -1
- package/subscriptions/subscription-iterator.d.cts +21 -0
- package/subscriptions/subscription-iterator.d.mts +21 -0
- package/subscriptions/subscription-iterator.d.ts +21 -0
- package/subscriptions/subscription-iterator.js +56 -0
- package/subscriptions/subscription-iterator.js.map +1 -0
- package/subscriptions/subscription-iterator.mjs +54 -0
- package/subscriptions/subscription-iterator.mjs.map +1 -0
package/README.md
CHANGED
|
@@ -236,13 +236,15 @@ Convenience builders that read `ctx.user.roles` / `ctx.user.scopes`. All use gra
|
|
|
236
236
|
|
|
237
237
|
### createRule
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
Strongly typed wrapper around graphql-shield's `rule(...)`. Mirrors the underlying call signature — `createRule(name?, options?)(fn)` — but types `parent`, `args` and `ctx` on `fn` to your generics instead of `any`.
|
|
240
240
|
|
|
241
241
|
```ts
|
|
242
|
-
const isOwner = createRule<GraphQLContext,
|
|
242
|
+
const isOwner = createRule<GraphQLContext, { ownerId: string }>()((parent, _, ctx) => parent.ownerId === ctx.user?.id)
|
|
243
|
+
|
|
244
|
+
const isAdmin = createRule<GraphQLContext>('isAdmin', { cache: 'contextual' })((_, __, ctx) => ctx.user?.roles.includes('admin') === true)
|
|
243
245
|
```
|
|
244
246
|
|
|
245
|
-
|
|
247
|
+
Generics are `<TContext, TParent = unknown, TArgs = unknown>`. The `name` and `options` arguments are forwarded verbatim to graphql-shield's `rule`.
|
|
246
248
|
|
|
247
249
|
### combineRuleWithAll
|
|
248
250
|
|
|
@@ -364,6 +366,34 @@ This library includes a `subscriptions` module to provide simple setup using the
|
|
|
364
366
|
|
|
365
367
|
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.
|
|
366
368
|
|
|
369
|
+
### wrapSubscriptionIterator
|
|
370
|
+
|
|
371
|
+
Wraps an `AsyncIterableIterator` (e.g. one returned by a pubsub `asyncIterableIterator(...)` call) with two optional behaviours:
|
|
372
|
+
|
|
373
|
+
- `initialPayload` — a single event or array of events yielded before the wrapped iterator's events. Useful when a client subscribes after a state change has already happened, so they don't miss the current state while waiting for the next event.
|
|
374
|
+
- `eventIsFinal` — a predicate that ends iteration eagerly when it returns `true` for an event. The final event is still yielded, then `wrapped.return()` is called to release resources. Only applied to events from the wrapped iterator, not to entries in `initialPayload`.
|
|
375
|
+
|
|
376
|
+
Typical use is in a subscription resolver that needs to bridge a "current state" (cache or other persisted state lookup) with a live event stream, and close the subscription once a terminal event arrives:
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
const eventIsFinal = (data: BackgroundJobTopicData) =>
|
|
380
|
+
[BackgroundJobStatus.Completed, BackgroundJobStatus.Failed].includes(data.event.status)
|
|
381
|
+
|
|
382
|
+
export const subscribeToBackgroundJobEvents = async (
|
|
383
|
+
args: SubscriptionBackgroundJobEventArgs,
|
|
384
|
+
): Promise<AsyncIterator<BackgroundJobTopicData> & AsyncIterable<BackgroundJobTopicData>> => {
|
|
385
|
+
const initialPayload = await getJobFinishedEventFromCache(args.jobId)
|
|
386
|
+
const iterator = pubsub().asyncIterableIterator<BackgroundJobTopicData>(`${BACKGROUND_JOB_TOPIC}.${args.jobId}`)
|
|
387
|
+
return wrapSubscriptionIterator({
|
|
388
|
+
iterator,
|
|
389
|
+
initialPayload: initialPayload ? [initialPayload] : undefined,
|
|
390
|
+
eventIsFinal,
|
|
391
|
+
})
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
The returned value is both an `AsyncIterator` and `AsyncIterable`, so it can be consumed via `for await` or by manual `next()` calls. `return()` and `throw()` are forwarded to the wrapped iterator when present.
|
|
396
|
+
|
|
367
397
|
## Utils
|
|
368
398
|
|
|
369
399
|
- `isIntrospectionQuery`: indicates whether the query is an introspection query, based on the operation name or query content.
|
package/package.json
CHANGED
package/request-info.d.cts
CHANGED
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
import type { Request } from 'express';
|
|
2
2
|
import type { IncomingMessage } from 'http';
|
|
3
|
+
/**
|
|
4
|
+
* Normalised metadata describing an inbound HTTP request or WebSocket upgrade, used as a
|
|
5
|
+
* common shape for logging, tracing, and request-scoped context across HTTP and subscription
|
|
6
|
+
* transports. Extends `Record<string, unknown>` so consumers can augment it with additional
|
|
7
|
+
* fields without losing type compatibility.
|
|
8
|
+
*/
|
|
3
9
|
export interface BaseRequestInfo extends Record<string, unknown> {
|
|
10
|
+
/** Unique identifier for the request, taken from the `x-request-id` header or generated as a UUID. */
|
|
4
11
|
requestId: string;
|
|
12
|
+
/** Transport that produced the request: `http` for Express requests, `subscription` for WebSocket upgrades. */
|
|
5
13
|
source: 'http' | 'subscription';
|
|
14
|
+
/** Resolved scheme of the request, accounting for TLS termination and `x-forwarded-proto`. */
|
|
6
15
|
protocol: 'http' | 'https' | 'ws' | 'wss';
|
|
16
|
+
/** Hostname resolved from `x-forwarded-host`, the `host` header, or the Express hostname fallback. */
|
|
7
17
|
host: string;
|
|
18
|
+
/** Port resolved from the host header, omitted when it matches the default for the protocol. */
|
|
8
19
|
port?: number;
|
|
20
|
+
/** HTTP method (e.g. `GET`, `POST`); empty string when not provided on the underlying request. */
|
|
9
21
|
method: string;
|
|
22
|
+
/** Base URL in the form `protocol://host[:port]`, with default ports omitted. */
|
|
10
23
|
baseUrl: string;
|
|
24
|
+
/**
|
|
25
|
+
* Request path with query string (not an absolute URL), e.g. `/graphql?op=Foo`. Sourced from
|
|
26
|
+
* Express `req.originalUrl` for HTTP (preserved across router mounts/rewrites) or raw `req.url`
|
|
27
|
+
* for WebSocket upgrades. Combine with `baseUrl` to form an absolute URL.
|
|
28
|
+
*/
|
|
11
29
|
url: string;
|
|
30
|
+
/** Value of the `origin` header, or empty string when absent. */
|
|
12
31
|
origin: string;
|
|
32
|
+
/** Value of the `referer` header, when present. */
|
|
13
33
|
referer?: string;
|
|
34
|
+
/** Value of the `x-correlation-id` header for cross-service request correlation, when present. */
|
|
14
35
|
correlationId?: string;
|
|
36
|
+
/** Azure Application Request Routing log id from the `x-arr-log-id` header, when present. */
|
|
15
37
|
arrLogId?: string;
|
|
38
|
+
/** Client IP from the first `x-forwarded-for` entry, falling back to the socket remote address. */
|
|
16
39
|
clientIp?: string;
|
|
40
|
+
/** Value of the `user-agent` header, when present. */
|
|
17
41
|
userAgent?: string;
|
|
18
42
|
}
|
|
19
43
|
/**
|
package/request-info.d.mts
CHANGED
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
import type { Request } from 'express';
|
|
2
2
|
import type { IncomingMessage } from 'http';
|
|
3
|
+
/**
|
|
4
|
+
* Normalised metadata describing an inbound HTTP request or WebSocket upgrade, used as a
|
|
5
|
+
* common shape for logging, tracing, and request-scoped context across HTTP and subscription
|
|
6
|
+
* transports. Extends `Record<string, unknown>` so consumers can augment it with additional
|
|
7
|
+
* fields without losing type compatibility.
|
|
8
|
+
*/
|
|
3
9
|
export interface BaseRequestInfo extends Record<string, unknown> {
|
|
10
|
+
/** Unique identifier for the request, taken from the `x-request-id` header or generated as a UUID. */
|
|
4
11
|
requestId: string;
|
|
12
|
+
/** Transport that produced the request: `http` for Express requests, `subscription` for WebSocket upgrades. */
|
|
5
13
|
source: 'http' | 'subscription';
|
|
14
|
+
/** Resolved scheme of the request, accounting for TLS termination and `x-forwarded-proto`. */
|
|
6
15
|
protocol: 'http' | 'https' | 'ws' | 'wss';
|
|
16
|
+
/** Hostname resolved from `x-forwarded-host`, the `host` header, or the Express hostname fallback. */
|
|
7
17
|
host: string;
|
|
18
|
+
/** Port resolved from the host header, omitted when it matches the default for the protocol. */
|
|
8
19
|
port?: number;
|
|
20
|
+
/** HTTP method (e.g. `GET`, `POST`); empty string when not provided on the underlying request. */
|
|
9
21
|
method: string;
|
|
22
|
+
/** Base URL in the form `protocol://host[:port]`, with default ports omitted. */
|
|
10
23
|
baseUrl: string;
|
|
24
|
+
/**
|
|
25
|
+
* Request path with query string (not an absolute URL), e.g. `/graphql?op=Foo`. Sourced from
|
|
26
|
+
* Express `req.originalUrl` for HTTP (preserved across router mounts/rewrites) or raw `req.url`
|
|
27
|
+
* for WebSocket upgrades. Combine with `baseUrl` to form an absolute URL.
|
|
28
|
+
*/
|
|
11
29
|
url: string;
|
|
30
|
+
/** Value of the `origin` header, or empty string when absent. */
|
|
12
31
|
origin: string;
|
|
32
|
+
/** Value of the `referer` header, when present. */
|
|
13
33
|
referer?: string;
|
|
34
|
+
/** Value of the `x-correlation-id` header for cross-service request correlation, when present. */
|
|
14
35
|
correlationId?: string;
|
|
36
|
+
/** Azure Application Request Routing log id from the `x-arr-log-id` header, when present. */
|
|
15
37
|
arrLogId?: string;
|
|
38
|
+
/** Client IP from the first `x-forwarded-for` entry, falling back to the socket remote address. */
|
|
16
39
|
clientIp?: string;
|
|
40
|
+
/** Value of the `user-agent` header, when present. */
|
|
17
41
|
userAgent?: string;
|
|
18
42
|
}
|
|
19
43
|
/**
|
package/request-info.d.ts
CHANGED
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
import type { Request } from 'express';
|
|
2
2
|
import type { IncomingMessage } from 'http';
|
|
3
|
+
/**
|
|
4
|
+
* Normalised metadata describing an inbound HTTP request or WebSocket upgrade, used as a
|
|
5
|
+
* common shape for logging, tracing, and request-scoped context across HTTP and subscription
|
|
6
|
+
* transports. Extends `Record<string, unknown>` so consumers can augment it with additional
|
|
7
|
+
* fields without losing type compatibility.
|
|
8
|
+
*/
|
|
3
9
|
export interface BaseRequestInfo extends Record<string, unknown> {
|
|
10
|
+
/** Unique identifier for the request, taken from the `x-request-id` header or generated as a UUID. */
|
|
4
11
|
requestId: string;
|
|
12
|
+
/** Transport that produced the request: `http` for Express requests, `subscription` for WebSocket upgrades. */
|
|
5
13
|
source: 'http' | 'subscription';
|
|
14
|
+
/** Resolved scheme of the request, accounting for TLS termination and `x-forwarded-proto`. */
|
|
6
15
|
protocol: 'http' | 'https' | 'ws' | 'wss';
|
|
16
|
+
/** Hostname resolved from `x-forwarded-host`, the `host` header, or the Express hostname fallback. */
|
|
7
17
|
host: string;
|
|
18
|
+
/** Port resolved from the host header, omitted when it matches the default for the protocol. */
|
|
8
19
|
port?: number;
|
|
20
|
+
/** HTTP method (e.g. `GET`, `POST`); empty string when not provided on the underlying request. */
|
|
9
21
|
method: string;
|
|
22
|
+
/** Base URL in the form `protocol://host[:port]`, with default ports omitted. */
|
|
10
23
|
baseUrl: string;
|
|
24
|
+
/**
|
|
25
|
+
* Request path with query string (not an absolute URL), e.g. `/graphql?op=Foo`. Sourced from
|
|
26
|
+
* Express `req.originalUrl` for HTTP (preserved across router mounts/rewrites) or raw `req.url`
|
|
27
|
+
* for WebSocket upgrades. Combine with `baseUrl` to form an absolute URL.
|
|
28
|
+
*/
|
|
11
29
|
url: string;
|
|
30
|
+
/** Value of the `origin` header, or empty string when absent. */
|
|
12
31
|
origin: string;
|
|
32
|
+
/** Value of the `referer` header, when present. */
|
|
13
33
|
referer?: string;
|
|
34
|
+
/** Value of the `x-correlation-id` header for cross-service request correlation, when present. */
|
|
14
35
|
correlationId?: string;
|
|
36
|
+
/** Azure Application Request Routing log id from the `x-arr-log-id` header, when present. */
|
|
15
37
|
arrLogId?: string;
|
|
38
|
+
/** Client IP from the first `x-forwarded-for` entry, falling back to the socket remote address. */
|
|
16
39
|
clientIp?: string;
|
|
40
|
+
/** Value of the `user-agent` header, when present. */
|
|
17
41
|
userAgent?: string;
|
|
18
42
|
}
|
|
19
43
|
/**
|
package/request-info.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-info.js","sources":["../src/request-info.ts"],"sourcesContent":["import { randomUUID } from 'crypto'\nimport type { Request } from 'express'\nimport type { IncomingMessage } from 'http'\nimport type { TLSSocket } from 'tls'\n\nexport interface BaseRequestInfo extends Record<string, unknown> {\n requestId: string\n source: 'http' | 'subscription'\n protocol: 'http' | 'https' | 'ws' | 'wss'\n host: string\n port?: number\n method: string\n baseUrl: string\n url: string\n origin: string\n referer?: string\n correlationId?: string\n arrLogId?: string\n clientIp?: string\n userAgent?: string\n}\n\nconst isDefaultPort = (protocol: string, port: number | undefined): boolean =>\n port == null || (protocol === 'http' && port === 80) || (protocol === 'https' && port === 443)\n\nconst formatBaseUrl = (protocol: string, hostname: string, port: number | undefined): string =>\n isDefaultPort(protocol, port) ? `${protocol}://${hostname}` : `${protocol}://${hostname}:${port}`\n\nconst firstHeaderValue = (value: string | string[] | undefined): string | undefined => {\n const raw = Array.isArray(value) ? value[0] : value\n return raw?.split(',')[0]?.trim() || undefined\n}\n\nconst parseHostHeader = (hostHeader: string): { hostname: string; port: number | undefined } => {\n const url = new URL(`http://${hostHeader}`)\n return { hostname: url.hostname, port: url.port ? Number(url.port) : undefined }\n}\n\nconst isEncryptedSocket = (req: IncomingMessage): boolean => 'encrypted' in req.socket && (req.socket as TLSSocket).encrypted === true\n\nconst isEncryptedConnect = (req: IncomingMessage): boolean => {\n const forwarded = firstHeaderValue(req.headers['x-forwarded-proto'])?.toLowerCase()\n if (forwarded === 'https' || forwarded === 'wss') return true\n if (forwarded === 'http' || forwarded === 'ws') return false\n return isEncryptedSocket(req)\n}\n\nconst resolveForwardedHost = (req: IncomingMessage): string | undefined => firstHeaderValue(req.headers['x-forwarded-host'])\n\nconst resolveHostAndPort = (req: IncomingMessage, fallbackHostname?: string): { host: string; port: number | undefined } => {\n const headerValue = resolveForwardedHost(req) ?? req.headers.host\n if (headerValue) {\n const { hostname, port } = parseHostHeader(headerValue)\n return { host: hostname, port }\n }\n return { host: fallbackHostname ?? '', port: undefined }\n}\n\n/**\n * Builds the base URL (`protocol://host[:port]`) for an Express HTTP request, using the request's\n * protocol and resolving the host/port from forwarding headers with the Express hostname as fallback.\n * The port is omitted when it matches the default for the protocol (80 for http, 443 for https).\n * @param req The Express request.\n * @returns The base URL string.\n */\nexport const requestBaseUrl = (req: Request): string => {\n const { host, port } = resolveHostAndPort(req, req.hostname)\n return formatBaseUrl(req.protocol, host, port)\n}\n\n/**\n * Builds the base URL (`http(s)://host[:port]`) for a raw WebSocket upgrade (connect) request.\n * Returns an `https` URL when the connection is TLS-encrypted or `x-forwarded-proto` indicates a\n * secure scheme, otherwise `http`. Throws if no host can be determined from the request headers,\n * since upgrade requests have no Express `hostname` fallback.\n * @param req The incoming upgrade request.\n * @returns The base URL string.\n * @throws Error when the host cannot be resolved from the request.\n */\nexport const connectRequestBaseUrl = (req: IncomingMessage): string => {\n const { host, port } = resolveHostAndPort(req)\n if (!host) throw new Error('Cannot determine base URL from websocket connect request')\n return formatBaseUrl(isEncryptedConnect(req) ? 'https' : 'http', host, port)\n}\n\nconst buildSharedRequestInfo = (req: IncomingMessage) => ({\n requestId: req.headers['x-request-id']?.toString() ?? randomUUID(),\n method: req.method ?? '',\n origin: req.headers.origin ?? '',\n referer: req.headers.referer?.toString() ?? '',\n arrLogId: req.headers['x-arr-log-id']?.toString() ?? undefined,\n clientIp: firstHeaderValue(req.headers['x-forwarded-for']) ?? req.socket.remoteAddress,\n correlationId: req.headers['x-correlation-id']?.toString() ?? undefined,\n userAgent: req.headers['user-agent']?.toString() ?? undefined,\n})\n\nexport const buildBaseRequestInfo = (req: Request): BaseRequestInfo => {\n const { host, port } = resolveHostAndPort(req, req.hostname)\n return {\n ...buildSharedRequestInfo(req),\n source: 'http',\n protocol: req.protocol as 'http' | 'https',\n host,\n port,\n baseUrl: formatBaseUrl(req.protocol, host, port),\n url: req.originalUrl,\n }\n}\n\nexport const buildConnectRequestInfo = (req: IncomingMessage): BaseRequestInfo => {\n const { host, port } = resolveHostAndPort(req)\n const protocol = isEncryptedConnect(req) ? 'wss' : 'ws'\n if (!host) throw new Error('Cannot determine base URL from websocket connect request')\n return {\n ...buildSharedRequestInfo(req),\n source: 'subscription',\n protocol,\n host,\n port,\n baseUrl: formatBaseUrl(protocol === 'wss' ? 'https' : 'http', host, port),\n url: req.url ?? '',\n }\n}\n"],"names":["randomUUID"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"request-info.js","sources":["../src/request-info.ts"],"sourcesContent":["import { randomUUID } from 'crypto'\nimport type { Request } from 'express'\nimport type { IncomingMessage } from 'http'\nimport type { TLSSocket } from 'tls'\n\n/**\n * Normalised metadata describing an inbound HTTP request or WebSocket upgrade, used as a\n * common shape for logging, tracing, and request-scoped context across HTTP and subscription\n * transports. Extends `Record<string, unknown>` so consumers can augment it with additional\n * fields without losing type compatibility.\n */\nexport interface BaseRequestInfo extends Record<string, unknown> {\n /** Unique identifier for the request, taken from the `x-request-id` header or generated as a UUID. */\n requestId: string\n /** Transport that produced the request: `http` for Express requests, `subscription` for WebSocket upgrades. */\n source: 'http' | 'subscription'\n /** Resolved scheme of the request, accounting for TLS termination and `x-forwarded-proto`. */\n protocol: 'http' | 'https' | 'ws' | 'wss'\n /** Hostname resolved from `x-forwarded-host`, the `host` header, or the Express hostname fallback. */\n host: string\n /** Port resolved from the host header, omitted when it matches the default for the protocol. */\n port?: number\n /** HTTP method (e.g. `GET`, `POST`); empty string when not provided on the underlying request. */\n method: string\n /** Base URL in the form `protocol://host[:port]`, with default ports omitted. */\n baseUrl: string\n /**\n * Request path with query string (not an absolute URL), e.g. `/graphql?op=Foo`. Sourced from\n * Express `req.originalUrl` for HTTP (preserved across router mounts/rewrites) or raw `req.url`\n * for WebSocket upgrades. Combine with `baseUrl` to form an absolute URL.\n */\n url: string\n /** Value of the `origin` header, or empty string when absent. */\n origin: string\n /** Value of the `referer` header, when present. */\n referer?: string\n /** Value of the `x-correlation-id` header for cross-service request correlation, when present. */\n correlationId?: string\n /** Azure Application Request Routing log id from the `x-arr-log-id` header, when present. */\n arrLogId?: string\n /** Client IP from the first `x-forwarded-for` entry, falling back to the socket remote address. */\n clientIp?: string\n /** Value of the `user-agent` header, when present. */\n userAgent?: string\n}\n\nconst isDefaultPort = (protocol: string, port: number | undefined): boolean =>\n port == null || (protocol === 'http' && port === 80) || (protocol === 'https' && port === 443)\n\nconst formatBaseUrl = (protocol: string, hostname: string, port: number | undefined): string =>\n isDefaultPort(protocol, port) ? `${protocol}://${hostname}` : `${protocol}://${hostname}:${port}`\n\nconst firstHeaderValue = (value: string | string[] | undefined): string | undefined => {\n const raw = Array.isArray(value) ? value[0] : value\n return raw?.split(',')[0]?.trim() || undefined\n}\n\nconst parseHostHeader = (hostHeader: string): { hostname: string; port: number | undefined } => {\n const url = new URL(`http://${hostHeader}`)\n return { hostname: url.hostname, port: url.port ? Number(url.port) : undefined }\n}\n\nconst isEncryptedSocket = (req: IncomingMessage): boolean => 'encrypted' in req.socket && (req.socket as TLSSocket).encrypted === true\n\nconst isEncryptedConnect = (req: IncomingMessage): boolean => {\n const forwarded = firstHeaderValue(req.headers['x-forwarded-proto'])?.toLowerCase()\n if (forwarded === 'https' || forwarded === 'wss') return true\n if (forwarded === 'http' || forwarded === 'ws') return false\n return isEncryptedSocket(req)\n}\n\nconst resolveForwardedHost = (req: IncomingMessage): string | undefined => firstHeaderValue(req.headers['x-forwarded-host'])\n\nconst resolveHostAndPort = (req: IncomingMessage, fallbackHostname?: string): { host: string; port: number | undefined } => {\n const headerValue = resolveForwardedHost(req) ?? req.headers.host\n if (headerValue) {\n const { hostname, port } = parseHostHeader(headerValue)\n return { host: hostname, port }\n }\n return { host: fallbackHostname ?? '', port: undefined }\n}\n\n/**\n * Builds the base URL (`protocol://host[:port]`) for an Express HTTP request, using the request's\n * protocol and resolving the host/port from forwarding headers with the Express hostname as fallback.\n * The port is omitted when it matches the default for the protocol (80 for http, 443 for https).\n * @param req The Express request.\n * @returns The base URL string.\n */\nexport const requestBaseUrl = (req: Request): string => {\n const { host, port } = resolveHostAndPort(req, req.hostname)\n return formatBaseUrl(req.protocol, host, port)\n}\n\n/**\n * Builds the base URL (`http(s)://host[:port]`) for a raw WebSocket upgrade (connect) request.\n * Returns an `https` URL when the connection is TLS-encrypted or `x-forwarded-proto` indicates a\n * secure scheme, otherwise `http`. Throws if no host can be determined from the request headers,\n * since upgrade requests have no Express `hostname` fallback.\n * @param req The incoming upgrade request.\n * @returns The base URL string.\n * @throws Error when the host cannot be resolved from the request.\n */\nexport const connectRequestBaseUrl = (req: IncomingMessage): string => {\n const { host, port } = resolveHostAndPort(req)\n if (!host) throw new Error('Cannot determine base URL from websocket connect request')\n return formatBaseUrl(isEncryptedConnect(req) ? 'https' : 'http', host, port)\n}\n\nconst buildSharedRequestInfo = (req: IncomingMessage) => ({\n requestId: req.headers['x-request-id']?.toString() ?? randomUUID(),\n method: req.method ?? '',\n origin: req.headers.origin ?? '',\n referer: req.headers.referer?.toString() ?? '',\n arrLogId: req.headers['x-arr-log-id']?.toString() ?? undefined,\n clientIp: firstHeaderValue(req.headers['x-forwarded-for']) ?? req.socket.remoteAddress,\n correlationId: req.headers['x-correlation-id']?.toString() ?? undefined,\n userAgent: req.headers['user-agent']?.toString() ?? undefined,\n})\n\nexport const buildBaseRequestInfo = (req: Request): BaseRequestInfo => {\n const { host, port } = resolveHostAndPort(req, req.hostname)\n return {\n ...buildSharedRequestInfo(req),\n source: 'http',\n protocol: req.protocol as 'http' | 'https',\n host,\n port,\n baseUrl: formatBaseUrl(req.protocol, host, port),\n url: req.originalUrl,\n }\n}\n\nexport const buildConnectRequestInfo = (req: IncomingMessage): BaseRequestInfo => {\n const { host, port } = resolveHostAndPort(req)\n const protocol = isEncryptedConnect(req) ? 'wss' : 'ws'\n if (!host) throw new Error('Cannot determine base URL from websocket connect request')\n return {\n ...buildSharedRequestInfo(req),\n source: 'subscription',\n protocol,\n host,\n port,\n baseUrl: formatBaseUrl(protocol === 'wss' ? 'https' : 'http', host, port),\n url: req.url ?? '',\n }\n}\n"],"names":["randomUUID"],"mappings":";;;;AA8CA,MAAM,aAAa,GAAG,CAAC,QAAgB,EAAE,IAAwB,KAC/D,IAAI,IAAI,IAAI,KAAK,QAAQ,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC,KAAK,QAAQ,KAAK,OAAO,IAAI,IAAI,KAAK,GAAG,CAAC;AAEhG,MAAM,aAAa,GAAG,CAAC,QAAgB,EAAE,QAAgB,EAAE,IAAwB,KACjF,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAA,EAAG,QAAQ,CAAA,GAAA,EAAM,QAAQ,EAAE,GAAG,CAAA,EAAG,QAAQ,CAAA,GAAA,EAAM,QAAQ,CAAA,CAAA,EAAI,IAAI,EAAE;AAEnG,MAAM,gBAAgB,GAAG,CAAC,KAAoC,KAAwB;AACpF,IAAA,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK;AACnD,IAAA,OAAO,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS;AAChD,CAAC;AAED,MAAM,eAAe,GAAG,CAAC,UAAkB,KAAoD;IAC7F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAA,OAAA,EAAU,UAAU,CAAA,CAAE,CAAC;IAC3C,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,SAAS,EAAE;AAClF,CAAC;AAED,MAAM,iBAAiB,GAAG,CAAC,GAAoB,KAAc,WAAW,IAAI,GAAG,CAAC,MAAM,IAAK,GAAG,CAAC,MAAoB,CAAC,SAAS,KAAK,IAAI;AAEtI,MAAM,kBAAkB,GAAG,CAAC,GAAoB,KAAa;AAC3D,IAAA,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,EAAE,WAAW,EAAE;AACnF,IAAA,IAAI,SAAS,KAAK,OAAO,IAAI,SAAS,KAAK,KAAK;AAAE,QAAA,OAAO,IAAI;AAC7D,IAAA,IAAI,SAAS,KAAK,MAAM,IAAI,SAAS,KAAK,IAAI;AAAE,QAAA,OAAO,KAAK;AAC5D,IAAA,OAAO,iBAAiB,CAAC,GAAG,CAAC;AAC/B,CAAC;AAED,MAAM,oBAAoB,GAAG,CAAC,GAAoB,KAAyB,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAE5H,MAAM,kBAAkB,GAAG,CAAC,GAAoB,EAAE,gBAAyB,KAAgD;AACzH,IAAA,MAAM,WAAW,GAAG,oBAAoB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI;IACjE,IAAI,WAAW,EAAE;QACf,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC,WAAW,CAAC;AACvD,QAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;IACjC;IACA,OAAO,EAAE,IAAI,EAAE,gBAAgB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;AAC1D,CAAC;AAED;;;;;;AAMG;AACI,MAAM,cAAc,GAAG,CAAC,GAAY,KAAY;AACrD,IAAA,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC;IAC5D,OAAO,aAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC;AAChD;AAEA;;;;;;;;AAQG;AACI,MAAM,qBAAqB,GAAG,CAAC,GAAoB,KAAY;IACpE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC;AAC9C,IAAA,IAAI,CAAC,IAAI;AAAE,QAAA,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC;AACtF,IAAA,OAAO,aAAa,CAAC,kBAAkB,CAAC,GAAG,CAAC,GAAG,OAAO,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;AAC9E;AAEA,MAAM,sBAAsB,GAAG,CAAC,GAAoB,MAAM;AACxD,IAAA,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,IAAIA,iBAAU,EAAE;AAClE,IAAA,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE;AACxB,IAAA,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE;IAChC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE;IAC9C,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,IAAI,SAAS;AAC9D,IAAA,QAAQ,EAAE,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa;IACtF,aAAa,EAAE,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,QAAQ,EAAE,IAAI,SAAS;IACvE,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,IAAI,SAAS;AAC9D,CAAA,CAAC;AAEK,MAAM,oBAAoB,GAAG,CAAC,GAAY,KAAqB;AACpE,IAAA,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC;IAC5D,OAAO;QACL,GAAG,sBAAsB,CAAC,GAAG,CAAC;AAC9B,QAAA,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,GAAG,CAAC,QAA4B;QAC1C,IAAI;QACJ,IAAI;QACJ,OAAO,EAAE,aAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC;QAChD,GAAG,EAAE,GAAG,CAAC,WAAW;KACrB;AACH;AAEO,MAAM,uBAAuB,GAAG,CAAC,GAAoB,KAAqB;IAC/E,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC;AAC9C,IAAA,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,IAAI;AACvD,IAAA,IAAI,CAAC,IAAI;AAAE,QAAA,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC;IACtF,OAAO;QACL,GAAG,sBAAsB,CAAC,GAAG,CAAC;AAC9B,QAAA,MAAM,EAAE,cAAc;QACtB,QAAQ;QACR,IAAI;QACJ,IAAI;AACJ,QAAA,OAAO,EAAE,aAAa,CAAC,QAAQ,KAAK,KAAK,GAAG,OAAO,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC;AACzE,QAAA,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE;KACnB;AACH;;;;;;;"}
|
package/request-info.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-info.mjs","sources":["../src/request-info.ts"],"sourcesContent":["import { randomUUID } from 'crypto'\nimport type { Request } from 'express'\nimport type { IncomingMessage } from 'http'\nimport type { TLSSocket } from 'tls'\n\nexport interface BaseRequestInfo extends Record<string, unknown> {\n requestId: string\n source: 'http' | 'subscription'\n protocol: 'http' | 'https' | 'ws' | 'wss'\n host: string\n port?: number\n method: string\n baseUrl: string\n url: string\n origin: string\n referer?: string\n correlationId?: string\n arrLogId?: string\n clientIp?: string\n userAgent?: string\n}\n\nconst isDefaultPort = (protocol: string, port: number | undefined): boolean =>\n port == null || (protocol === 'http' && port === 80) || (protocol === 'https' && port === 443)\n\nconst formatBaseUrl = (protocol: string, hostname: string, port: number | undefined): string =>\n isDefaultPort(protocol, port) ? `${protocol}://${hostname}` : `${protocol}://${hostname}:${port}`\n\nconst firstHeaderValue = (value: string | string[] | undefined): string | undefined => {\n const raw = Array.isArray(value) ? value[0] : value\n return raw?.split(',')[0]?.trim() || undefined\n}\n\nconst parseHostHeader = (hostHeader: string): { hostname: string; port: number | undefined } => {\n const url = new URL(`http://${hostHeader}`)\n return { hostname: url.hostname, port: url.port ? Number(url.port) : undefined }\n}\n\nconst isEncryptedSocket = (req: IncomingMessage): boolean => 'encrypted' in req.socket && (req.socket as TLSSocket).encrypted === true\n\nconst isEncryptedConnect = (req: IncomingMessage): boolean => {\n const forwarded = firstHeaderValue(req.headers['x-forwarded-proto'])?.toLowerCase()\n if (forwarded === 'https' || forwarded === 'wss') return true\n if (forwarded === 'http' || forwarded === 'ws') return false\n return isEncryptedSocket(req)\n}\n\nconst resolveForwardedHost = (req: IncomingMessage): string | undefined => firstHeaderValue(req.headers['x-forwarded-host'])\n\nconst resolveHostAndPort = (req: IncomingMessage, fallbackHostname?: string): { host: string; port: number | undefined } => {\n const headerValue = resolveForwardedHost(req) ?? req.headers.host\n if (headerValue) {\n const { hostname, port } = parseHostHeader(headerValue)\n return { host: hostname, port }\n }\n return { host: fallbackHostname ?? '', port: undefined }\n}\n\n/**\n * Builds the base URL (`protocol://host[:port]`) for an Express HTTP request, using the request's\n * protocol and resolving the host/port from forwarding headers with the Express hostname as fallback.\n * The port is omitted when it matches the default for the protocol (80 for http, 443 for https).\n * @param req The Express request.\n * @returns The base URL string.\n */\nexport const requestBaseUrl = (req: Request): string => {\n const { host, port } = resolveHostAndPort(req, req.hostname)\n return formatBaseUrl(req.protocol, host, port)\n}\n\n/**\n * Builds the base URL (`http(s)://host[:port]`) for a raw WebSocket upgrade (connect) request.\n * Returns an `https` URL when the connection is TLS-encrypted or `x-forwarded-proto` indicates a\n * secure scheme, otherwise `http`. Throws if no host can be determined from the request headers,\n * since upgrade requests have no Express `hostname` fallback.\n * @param req The incoming upgrade request.\n * @returns The base URL string.\n * @throws Error when the host cannot be resolved from the request.\n */\nexport const connectRequestBaseUrl = (req: IncomingMessage): string => {\n const { host, port } = resolveHostAndPort(req)\n if (!host) throw new Error('Cannot determine base URL from websocket connect request')\n return formatBaseUrl(isEncryptedConnect(req) ? 'https' : 'http', host, port)\n}\n\nconst buildSharedRequestInfo = (req: IncomingMessage) => ({\n requestId: req.headers['x-request-id']?.toString() ?? randomUUID(),\n method: req.method ?? '',\n origin: req.headers.origin ?? '',\n referer: req.headers.referer?.toString() ?? '',\n arrLogId: req.headers['x-arr-log-id']?.toString() ?? undefined,\n clientIp: firstHeaderValue(req.headers['x-forwarded-for']) ?? req.socket.remoteAddress,\n correlationId: req.headers['x-correlation-id']?.toString() ?? undefined,\n userAgent: req.headers['user-agent']?.toString() ?? undefined,\n})\n\nexport const buildBaseRequestInfo = (req: Request): BaseRequestInfo => {\n const { host, port } = resolveHostAndPort(req, req.hostname)\n return {\n ...buildSharedRequestInfo(req),\n source: 'http',\n protocol: req.protocol as 'http' | 'https',\n host,\n port,\n baseUrl: formatBaseUrl(req.protocol, host, port),\n url: req.originalUrl,\n }\n}\n\nexport const buildConnectRequestInfo = (req: IncomingMessage): BaseRequestInfo => {\n const { host, port } = resolveHostAndPort(req)\n const protocol = isEncryptedConnect(req) ? 'wss' : 'ws'\n if (!host) throw new Error('Cannot determine base URL from websocket connect request')\n return {\n ...buildSharedRequestInfo(req),\n source: 'subscription',\n protocol,\n host,\n port,\n baseUrl: formatBaseUrl(protocol === 'wss' ? 'https' : 'http', host, port),\n url: req.url ?? '',\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"request-info.mjs","sources":["../src/request-info.ts"],"sourcesContent":["import { randomUUID } from 'crypto'\nimport type { Request } from 'express'\nimport type { IncomingMessage } from 'http'\nimport type { TLSSocket } from 'tls'\n\n/**\n * Normalised metadata describing an inbound HTTP request or WebSocket upgrade, used as a\n * common shape for logging, tracing, and request-scoped context across HTTP and subscription\n * transports. Extends `Record<string, unknown>` so consumers can augment it with additional\n * fields without losing type compatibility.\n */\nexport interface BaseRequestInfo extends Record<string, unknown> {\n /** Unique identifier for the request, taken from the `x-request-id` header or generated as a UUID. */\n requestId: string\n /** Transport that produced the request: `http` for Express requests, `subscription` for WebSocket upgrades. */\n source: 'http' | 'subscription'\n /** Resolved scheme of the request, accounting for TLS termination and `x-forwarded-proto`. */\n protocol: 'http' | 'https' | 'ws' | 'wss'\n /** Hostname resolved from `x-forwarded-host`, the `host` header, or the Express hostname fallback. */\n host: string\n /** Port resolved from the host header, omitted when it matches the default for the protocol. */\n port?: number\n /** HTTP method (e.g. `GET`, `POST`); empty string when not provided on the underlying request. */\n method: string\n /** Base URL in the form `protocol://host[:port]`, with default ports omitted. */\n baseUrl: string\n /**\n * Request path with query string (not an absolute URL), e.g. `/graphql?op=Foo`. Sourced from\n * Express `req.originalUrl` for HTTP (preserved across router mounts/rewrites) or raw `req.url`\n * for WebSocket upgrades. Combine with `baseUrl` to form an absolute URL.\n */\n url: string\n /** Value of the `origin` header, or empty string when absent. */\n origin: string\n /** Value of the `referer` header, when present. */\n referer?: string\n /** Value of the `x-correlation-id` header for cross-service request correlation, when present. */\n correlationId?: string\n /** Azure Application Request Routing log id from the `x-arr-log-id` header, when present. */\n arrLogId?: string\n /** Client IP from the first `x-forwarded-for` entry, falling back to the socket remote address. */\n clientIp?: string\n /** Value of the `user-agent` header, when present. */\n userAgent?: string\n}\n\nconst isDefaultPort = (protocol: string, port: number | undefined): boolean =>\n port == null || (protocol === 'http' && port === 80) || (protocol === 'https' && port === 443)\n\nconst formatBaseUrl = (protocol: string, hostname: string, port: number | undefined): string =>\n isDefaultPort(protocol, port) ? `${protocol}://${hostname}` : `${protocol}://${hostname}:${port}`\n\nconst firstHeaderValue = (value: string | string[] | undefined): string | undefined => {\n const raw = Array.isArray(value) ? value[0] : value\n return raw?.split(',')[0]?.trim() || undefined\n}\n\nconst parseHostHeader = (hostHeader: string): { hostname: string; port: number | undefined } => {\n const url = new URL(`http://${hostHeader}`)\n return { hostname: url.hostname, port: url.port ? Number(url.port) : undefined }\n}\n\nconst isEncryptedSocket = (req: IncomingMessage): boolean => 'encrypted' in req.socket && (req.socket as TLSSocket).encrypted === true\n\nconst isEncryptedConnect = (req: IncomingMessage): boolean => {\n const forwarded = firstHeaderValue(req.headers['x-forwarded-proto'])?.toLowerCase()\n if (forwarded === 'https' || forwarded === 'wss') return true\n if (forwarded === 'http' || forwarded === 'ws') return false\n return isEncryptedSocket(req)\n}\n\nconst resolveForwardedHost = (req: IncomingMessage): string | undefined => firstHeaderValue(req.headers['x-forwarded-host'])\n\nconst resolveHostAndPort = (req: IncomingMessage, fallbackHostname?: string): { host: string; port: number | undefined } => {\n const headerValue = resolveForwardedHost(req) ?? req.headers.host\n if (headerValue) {\n const { hostname, port } = parseHostHeader(headerValue)\n return { host: hostname, port }\n }\n return { host: fallbackHostname ?? '', port: undefined }\n}\n\n/**\n * Builds the base URL (`protocol://host[:port]`) for an Express HTTP request, using the request's\n * protocol and resolving the host/port from forwarding headers with the Express hostname as fallback.\n * The port is omitted when it matches the default for the protocol (80 for http, 443 for https).\n * @param req The Express request.\n * @returns The base URL string.\n */\nexport const requestBaseUrl = (req: Request): string => {\n const { host, port } = resolveHostAndPort(req, req.hostname)\n return formatBaseUrl(req.protocol, host, port)\n}\n\n/**\n * Builds the base URL (`http(s)://host[:port]`) for a raw WebSocket upgrade (connect) request.\n * Returns an `https` URL when the connection is TLS-encrypted or `x-forwarded-proto` indicates a\n * secure scheme, otherwise `http`. Throws if no host can be determined from the request headers,\n * since upgrade requests have no Express `hostname` fallback.\n * @param req The incoming upgrade request.\n * @returns The base URL string.\n * @throws Error when the host cannot be resolved from the request.\n */\nexport const connectRequestBaseUrl = (req: IncomingMessage): string => {\n const { host, port } = resolveHostAndPort(req)\n if (!host) throw new Error('Cannot determine base URL from websocket connect request')\n return formatBaseUrl(isEncryptedConnect(req) ? 'https' : 'http', host, port)\n}\n\nconst buildSharedRequestInfo = (req: IncomingMessage) => ({\n requestId: req.headers['x-request-id']?.toString() ?? randomUUID(),\n method: req.method ?? '',\n origin: req.headers.origin ?? '',\n referer: req.headers.referer?.toString() ?? '',\n arrLogId: req.headers['x-arr-log-id']?.toString() ?? undefined,\n clientIp: firstHeaderValue(req.headers['x-forwarded-for']) ?? req.socket.remoteAddress,\n correlationId: req.headers['x-correlation-id']?.toString() ?? undefined,\n userAgent: req.headers['user-agent']?.toString() ?? undefined,\n})\n\nexport const buildBaseRequestInfo = (req: Request): BaseRequestInfo => {\n const { host, port } = resolveHostAndPort(req, req.hostname)\n return {\n ...buildSharedRequestInfo(req),\n source: 'http',\n protocol: req.protocol as 'http' | 'https',\n host,\n port,\n baseUrl: formatBaseUrl(req.protocol, host, port),\n url: req.originalUrl,\n }\n}\n\nexport const buildConnectRequestInfo = (req: IncomingMessage): BaseRequestInfo => {\n const { host, port } = resolveHostAndPort(req)\n const protocol = isEncryptedConnect(req) ? 'wss' : 'ws'\n if (!host) throw new Error('Cannot determine base URL from websocket connect request')\n return {\n ...buildSharedRequestInfo(req),\n source: 'subscription',\n protocol,\n host,\n port,\n baseUrl: formatBaseUrl(protocol === 'wss' ? 'https' : 'http', host, port),\n url: req.url ?? '',\n }\n}\n"],"names":[],"mappings":"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"}
|
package/shield.d.cts
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
|
+
import type { GraphQLResolveInfo } from 'graphql';
|
|
1
2
|
import type { allow, and, chain, or, race } from 'graphql-shield';
|
|
2
|
-
import { shield } from 'graphql-shield';
|
|
3
|
+
import { rule, shield } from 'graphql-shield';
|
|
3
4
|
import type { Primitive } from './type-utils';
|
|
4
5
|
export type ShieldOptions = NonNullable<Parameters<typeof shield>[1]>;
|
|
5
6
|
type RuleCombinator = typeof chain | typeof race | typeof or | typeof and;
|
|
6
7
|
export type ShieldRule = ReturnType<(typeof allow)['getRules']>[number];
|
|
8
|
+
type RuleConstructorOptions = NonNullable<Parameters<typeof rule>[1]>;
|
|
9
|
+
type RuleResult = boolean | string | Error;
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
11
|
+
* Strongly typed wrapper around graphql-shield's `rule(...)`.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Defaults the rule's cache to `'strict'`; use `'contextual'` when the result depends only on
|
|
15
|
-
* `ctx` (e.g. the current user), so it can be reused across fields in the same request.
|
|
16
|
-
*
|
|
17
|
-
* @param logic Predicate returning `true` to allow, `false` to deny, or an `Error` to deny with a specific error.
|
|
18
|
-
* @param cache graphql-shield cache strategy. `'strict'` (default) keys on parent+args+ctx; `'contextual'` keys on ctx only.
|
|
13
|
+
* Mirrors the underlying `rule` call signature — `createRule(name?, options?)(fn)` — with the
|
|
14
|
+
* same parameter order and meanings. The only difference is that `parent`, `args`, and `ctx` on
|
|
15
|
+
* `fn` are typed via the generics instead of `any`. Returns the same {@link ShieldRule} that the
|
|
16
|
+
* underlying `rule` produces.
|
|
19
17
|
*
|
|
20
18
|
* Usage:
|
|
21
19
|
*
|
|
22
|
-
* const isOwner = createRule<GraphQLContext, { ownerId: string }>(
|
|
20
|
+
* const isOwner = createRule<GraphQLContext, { ownerId: string }>()(
|
|
23
21
|
* (parent, _, ctx) => parent.ownerId === ctx.user?.id,
|
|
24
22
|
* )
|
|
23
|
+
*
|
|
24
|
+
* const isAdmin = createRule<GraphQLContext>('isAdmin', { cache: 'contextual' })(
|
|
25
|
+
* (_, __, ctx) => ctx.user?.roles.includes('admin') === true,
|
|
26
|
+
* )
|
|
25
27
|
*/
|
|
26
|
-
export declare const createRule: <TContext, TParent = unknown, TArgs = unknown>(
|
|
28
|
+
export declare const createRule: <TContext, TParent = unknown, TArgs = unknown>(name?: string | RuleConstructorOptions, options?: RuleConstructorOptions) => (fn: (parent: TParent, args: TArgs, ctx: TContext, info: GraphQLResolveInfo) => RuleResult | Promise<RuleResult>) => ShieldRule;
|
|
27
29
|
/**
|
|
28
30
|
* Combines a given rule with all existing defined rules using the provided combinator
|
|
29
31
|
* @param schema The existing shield schema with rules
|
package/shield.d.mts
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
|
+
import type { GraphQLResolveInfo } from 'graphql';
|
|
1
2
|
import type { allow, and, chain, or, race } from 'graphql-shield';
|
|
2
|
-
import { shield } from 'graphql-shield';
|
|
3
|
+
import { rule, shield } from 'graphql-shield';
|
|
3
4
|
import type { Primitive } from './type-utils.js';
|
|
4
5
|
export type ShieldOptions = NonNullable<Parameters<typeof shield>[1]>;
|
|
5
6
|
type RuleCombinator = typeof chain | typeof race | typeof or | typeof and;
|
|
6
7
|
export type ShieldRule = ReturnType<(typeof allow)['getRules']>[number];
|
|
8
|
+
type RuleConstructorOptions = NonNullable<Parameters<typeof rule>[1]>;
|
|
9
|
+
type RuleResult = boolean | string | Error;
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
11
|
+
* Strongly typed wrapper around graphql-shield's `rule(...)`.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Defaults the rule's cache to `'strict'`; use `'contextual'` when the result depends only on
|
|
15
|
-
* `ctx` (e.g. the current user), so it can be reused across fields in the same request.
|
|
16
|
-
*
|
|
17
|
-
* @param logic Predicate returning `true` to allow, `false` to deny, or an `Error` to deny with a specific error.
|
|
18
|
-
* @param cache graphql-shield cache strategy. `'strict'` (default) keys on parent+args+ctx; `'contextual'` keys on ctx only.
|
|
13
|
+
* Mirrors the underlying `rule` call signature — `createRule(name?, options?)(fn)` — with the
|
|
14
|
+
* same parameter order and meanings. The only difference is that `parent`, `args`, and `ctx` on
|
|
15
|
+
* `fn` are typed via the generics instead of `any`. Returns the same {@link ShieldRule} that the
|
|
16
|
+
* underlying `rule` produces.
|
|
19
17
|
*
|
|
20
18
|
* Usage:
|
|
21
19
|
*
|
|
22
|
-
* const isOwner = createRule<GraphQLContext, { ownerId: string }>(
|
|
20
|
+
* const isOwner = createRule<GraphQLContext, { ownerId: string }>()(
|
|
23
21
|
* (parent, _, ctx) => parent.ownerId === ctx.user?.id,
|
|
24
22
|
* )
|
|
23
|
+
*
|
|
24
|
+
* const isAdmin = createRule<GraphQLContext>('isAdmin', { cache: 'contextual' })(
|
|
25
|
+
* (_, __, ctx) => ctx.user?.roles.includes('admin') === true,
|
|
26
|
+
* )
|
|
25
27
|
*/
|
|
26
|
-
export declare const createRule: <TContext, TParent = unknown, TArgs = unknown>(
|
|
28
|
+
export declare const createRule: <TContext, TParent = unknown, TArgs = unknown>(name?: string | RuleConstructorOptions, options?: RuleConstructorOptions) => (fn: (parent: TParent, args: TArgs, ctx: TContext, info: GraphQLResolveInfo) => RuleResult | Promise<RuleResult>) => ShieldRule;
|
|
27
29
|
/**
|
|
28
30
|
* Combines a given rule with all existing defined rules using the provided combinator
|
|
29
31
|
* @param schema The existing shield schema with rules
|
package/shield.d.ts
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
|
+
import type { GraphQLResolveInfo } from 'graphql';
|
|
1
2
|
import type { allow, and, chain, or, race } from 'graphql-shield';
|
|
2
|
-
import { shield } from 'graphql-shield';
|
|
3
|
+
import { rule, shield } from 'graphql-shield';
|
|
3
4
|
import type { Primitive } from './type-utils';
|
|
4
5
|
export type ShieldOptions = NonNullable<Parameters<typeof shield>[1]>;
|
|
5
6
|
type RuleCombinator = typeof chain | typeof race | typeof or | typeof and;
|
|
6
7
|
export type ShieldRule = ReturnType<(typeof allow)['getRules']>[number];
|
|
8
|
+
type RuleConstructorOptions = NonNullable<Parameters<typeof rule>[1]>;
|
|
9
|
+
type RuleResult = boolean | string | Error;
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
11
|
+
* Strongly typed wrapper around graphql-shield's `rule(...)`.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Defaults the rule's cache to `'strict'`; use `'contextual'` when the result depends only on
|
|
15
|
-
* `ctx` (e.g. the current user), so it can be reused across fields in the same request.
|
|
16
|
-
*
|
|
17
|
-
* @param logic Predicate returning `true` to allow, `false` to deny, or an `Error` to deny with a specific error.
|
|
18
|
-
* @param cache graphql-shield cache strategy. `'strict'` (default) keys on parent+args+ctx; `'contextual'` keys on ctx only.
|
|
13
|
+
* Mirrors the underlying `rule` call signature — `createRule(name?, options?)(fn)` — with the
|
|
14
|
+
* same parameter order and meanings. The only difference is that `parent`, `args`, and `ctx` on
|
|
15
|
+
* `fn` are typed via the generics instead of `any`. Returns the same {@link ShieldRule} that the
|
|
16
|
+
* underlying `rule` produces.
|
|
19
17
|
*
|
|
20
18
|
* Usage:
|
|
21
19
|
*
|
|
22
|
-
* const isOwner = createRule<GraphQLContext, { ownerId: string }>(
|
|
20
|
+
* const isOwner = createRule<GraphQLContext, { ownerId: string }>()(
|
|
23
21
|
* (parent, _, ctx) => parent.ownerId === ctx.user?.id,
|
|
24
22
|
* )
|
|
23
|
+
*
|
|
24
|
+
* const isAdmin = createRule<GraphQLContext>('isAdmin', { cache: 'contextual' })(
|
|
25
|
+
* (_, __, ctx) => ctx.user?.roles.includes('admin') === true,
|
|
26
|
+
* )
|
|
25
27
|
*/
|
|
26
|
-
export declare const createRule: <TContext, TParent = unknown, TArgs = unknown>(
|
|
28
|
+
export declare const createRule: <TContext, TParent = unknown, TArgs = unknown>(name?: string | RuleConstructorOptions, options?: RuleConstructorOptions) => (fn: (parent: TParent, args: TArgs, ctx: TContext, info: GraphQLResolveInfo) => RuleResult | Promise<RuleResult>) => ShieldRule;
|
|
27
29
|
/**
|
|
28
30
|
* Combines a given rule with all existing defined rules using the provided combinator
|
|
29
31
|
* @param schema The existing shield schema with rules
|
package/shield.js
CHANGED
|
@@ -3,25 +3,24 @@
|
|
|
3
3
|
var graphqlShield = require('graphql-shield');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Strongly typed wrapper around graphql-shield's `rule(...)`.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Defaults the rule's cache to `'strict'`; use `'contextual'` when the result depends only on
|
|
13
|
-
* `ctx` (e.g. the current user), so it can be reused across fields in the same request.
|
|
14
|
-
*
|
|
15
|
-
* @param logic Predicate returning `true` to allow, `false` to deny, or an `Error` to deny with a specific error.
|
|
16
|
-
* @param cache graphql-shield cache strategy. `'strict'` (default) keys on parent+args+ctx; `'contextual'` keys on ctx only.
|
|
8
|
+
* Mirrors the underlying `rule` call signature — `createRule(name?, options?)(fn)` — with the
|
|
9
|
+
* same parameter order and meanings. The only difference is that `parent`, `args`, and `ctx` on
|
|
10
|
+
* `fn` are typed via the generics instead of `any`. Returns the same {@link ShieldRule} that the
|
|
11
|
+
* underlying `rule` produces.
|
|
17
12
|
*
|
|
18
13
|
* Usage:
|
|
19
14
|
*
|
|
20
|
-
* const isOwner = createRule<GraphQLContext, { ownerId: string }>(
|
|
15
|
+
* const isOwner = createRule<GraphQLContext, { ownerId: string }>()(
|
|
21
16
|
* (parent, _, ctx) => parent.ownerId === ctx.user?.id,
|
|
22
17
|
* )
|
|
18
|
+
*
|
|
19
|
+
* const isAdmin = createRule<GraphQLContext>('isAdmin', { cache: 'contextual' })(
|
|
20
|
+
* (_, __, ctx) => ctx.user?.roles.includes('admin') === true,
|
|
21
|
+
* )
|
|
23
22
|
*/
|
|
24
|
-
const createRule = (
|
|
23
|
+
const createRule = (name, options) => (fn) => graphqlShield.rule(name, options)(fn);
|
|
25
24
|
/**
|
|
26
25
|
* Combines a given rule with all existing defined rules using the provided combinator
|
|
27
26
|
* @param schema The existing shield schema with rules
|
package/shield.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shield.js","sources":["../src/shield.ts"],"sourcesContent":["import type { allow, and, chain, IRules, or, race } from 'graphql-shield'\nimport { rule, shield } from 'graphql-shield'\nimport type { GraphQLContext } from './context'\nimport type { Primitive } from './type-utils'\n\nexport type ShieldOptions = NonNullable<Parameters<typeof shield>[1]>\n\ntype RuleCombinator = typeof chain | typeof race | typeof or | typeof and\n// For whatever reason, graphql-shield doesn't export this type, but we can extract if from\n// what it does export.\nexport type ShieldRule = ReturnType<(typeof allow)['getRules']>[number]\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"shield.js","sources":["../src/shield.ts"],"sourcesContent":["import type { GraphQLResolveInfo } from 'graphql'\nimport type { allow, and, chain, IRules, or, race } from 'graphql-shield'\nimport { rule, shield } from 'graphql-shield'\nimport type { GraphQLContext } from './context'\nimport type { Primitive } from './type-utils'\n\nexport type ShieldOptions = NonNullable<Parameters<typeof shield>[1]>\n\ntype RuleCombinator = typeof chain | typeof race | typeof or | typeof and\n// For whatever reason, graphql-shield doesn't export this type, but we can extract if from\n// what it does export.\nexport type ShieldRule = ReturnType<(typeof allow)['getRules']>[number]\n\ntype RuleConstructorOptions = NonNullable<Parameters<typeof rule>[1]>\ntype RuleResult = boolean | string | Error\ntype ShieldRuleFn = Parameters<ReturnType<typeof rule>>[0]\n\n/**\n * Strongly typed wrapper around graphql-shield's `rule(...)`.\n *\n * Mirrors the underlying `rule` call signature — `createRule(name?, options?)(fn)` — with the\n * same parameter order and meanings. The only difference is that `parent`, `args`, and `ctx` on\n * `fn` are typed via the generics instead of `any`. Returns the same {@link ShieldRule} that the\n * underlying `rule` produces.\n *\n * Usage:\n *\n * const isOwner = createRule<GraphQLContext, { ownerId: string }>()(\n * (parent, _, ctx) => parent.ownerId === ctx.user?.id,\n * )\n *\n * const isAdmin = createRule<GraphQLContext>('isAdmin', { cache: 'contextual' })(\n * (_, __, ctx) => ctx.user?.roles.includes('admin') === true,\n * )\n */\nexport const createRule =\n <TContext, TParent = unknown, TArgs = unknown>(name?: string | RuleConstructorOptions, options?: RuleConstructorOptions) =>\n (fn: (parent: TParent, args: TArgs, ctx: TContext, info: GraphQLResolveInfo) => RuleResult | Promise<RuleResult>): ShieldRule =>\n rule(name, options)(fn as ShieldRuleFn)\n\n/**\n * Combines a given rule with all existing defined rules using the provided combinator\n * @param schema The existing shield schema with rules\n * @param rule The rule to be combined with existing rules\n * @param combinator The combinator to use. Eg. and | or | chain | race\n *\n * Usage:\n *\n * const updatedSchema = combineRuleWithAll(shieldSchema, rules.accountNotBlocked, chain)\n */\nexport function combineRuleWithAll<T>(schema: ShieldSchema<T>, rule: ShieldRule, combinator: RuleCombinator): ShieldSchema<T> {\n return Object.fromEntries(\n Object.entries(schema).map(([key, value]) => {\n if (value.constructor.name !== 'Object') {\n return [key, combinator(rule, value)]\n }\n return [key, combineRuleWithAll(value, rule, combinator)]\n }),\n ) as ShieldSchema<T>\n}\n\n/**\n * Creates graphql shield middleware making use of generics to type the definition of the rules.\n * @param schema A mapping of schema objects to the shield rules that define the access to that object\n * @param options Shield options forwarded to `shield`. Defaults `allowExternalErrors` to `true`.\n *\n * Usage:\n * import { Resolvers } from '../generated/types'\n *\n * const isCoordinator = hasRoleRule(UserRoles.Coordinator)\n * const isSystemAdmin = hasRoleRule(UserRoles.SystemAdmin)\n * const isQualityAssurance = hasRoleRule(UserRoles.QualityAssurance)\n *\n * const isAuthorisedUser = or(isCoordinator, isSystemAdmin, isQualityAssurance)\n * const isDataAuthor = or(isCoordinator, isSystemAdmin)\n *\n * const shieldSchema = createShieldSchema<Resolvers>({\n * Query: {\n * '*': isAuthorisedUser,\n * user: isCoordinator,\n * findUsers: isCoordinator,\n * },\n * Mutation: {\n * '*': isDataAuthor,\n * createUser: isCoordinator,\n * },\n * // specific rules for mutations, types, fields...\n * }, { fallbackRule: isAuthorisedUser, fallbackError: unauthorisedError()})\n *\n * const unprotected = makeExecutableSchema({...})\n * return applyMiddleware(unprotected, shieldSchema)\n */\nexport function createShieldSchema<TRootResolvers>(schema: ShieldSchema<TRootResolvers>, options?: ShieldOptions) {\n return shield(schema as IRules, {\n allowExternalErrors: true,\n ...options,\n })\n}\n\n/**\n * A shield rule tree typed against a resolver map (typically your generated `Resolvers` type).\n *\n * Mirrors the shape of `TResolver` so each type/field can be assigned a {@link ShieldRule}.\n * Object levels may also include a `'*'` key — a fallback rule applied to any field at that\n * level that doesn't have an explicit rule. Leaf (primitive) positions collapse to a single\n * {@link ShieldRule}.\n *\n * Usage:\n *\n * const schema: ShieldSchema<Resolvers> = {\n * Query: {\n * '*': isAuthorisedUser,\n * publicThing: allow,\n * },\n * Mutation: { createUser: isCoordinator },\n * }\n */\nexport type ShieldSchema<TResolver> = TResolver extends Primitive\n ? ShieldRule\n : {\n [key in keyof TResolver]?: ShieldSchema<TResolver[key]> | ShieldRule\n } & {\n '*'?: ShieldRule\n }\n\n/**\n * Shield rule that passes when the current user has the given role.\n * @param role The role the user must have on `ctx.user.roles`.\n * @param ruleName Optional override for the rule's cache key. Defaults to `hasRole-${role}`.\n */\nexport function hasRoleRule(role: string, ruleName?: string): ShieldRule {\n return rule(ruleName ?? `hasRole-${role}`, { cache: 'contextual' })(\n (_, __, { user }: GraphQLContext) => user?.roles.includes(role) === true,\n )\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given roles.\n * Rule name is derived from the roles; use {@link hasAnyRoleRuleWithName} to supply your own.\n * @param roles The set of roles, any of which satisfies the rule.\n */\nexport function hasAnyRoleRule(...roles: string[]): ShieldRule {\n return hasAnyRoleRuleWithName(`hasAnyRole-${roles.join(',')}`, ...roles)\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given roles, with an explicit rule name.\n * Prefer this over {@link hasAnyRoleRule} when the role list is large or dynamic and you want a stable cache key.\n * @param ruleName The name (and cache key) for the rule.\n * @param roles The set of roles, any of which satisfies the rule.\n */\nexport function hasAnyRoleRuleWithName(ruleName: string, ...roles: string[]): ShieldRule {\n return rule(ruleName, { cache: 'contextual' })((_, __, { user }: GraphQLContext) => {\n return user?.roles.some((role: string) => roles.includes(role)) === true\n })\n}\n\n/**\n * Shield rule that passes when the current user has the given scope.\n * @param scope The scope the user must have on `ctx.user.scopes`.\n */\nexport function hasScopeRule(scope: string): ShieldRule {\n return rule(`hasScope-${scope}`, { cache: 'contextual' })((_, __, { user }: GraphQLContext) => user?.scopes.includes(scope) === true)\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given scopes.\n * @param scopes The set of scopes, any of which satisfies the rule.\n */\nexport function hasAnyScopeRule(...scopes: string[]): ShieldRule {\n return rule(`hasAnyScope-${scopes.join(',')}`, { cache: 'contextual' })(\n (_, __, { user }: GraphQLContext) => user?.scopes.some((scope: string) => scopes.includes(scope)) === true,\n )\n}\n"],"names":["rule","shield"],"mappings":"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"}
|
package/shield.mjs
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import { rule, shield } from 'graphql-shield';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Strongly typed wrapper around graphql-shield's `rule(...)`.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Defaults the rule's cache to `'strict'`; use `'contextual'` when the result depends only on
|
|
11
|
-
* `ctx` (e.g. the current user), so it can be reused across fields in the same request.
|
|
12
|
-
*
|
|
13
|
-
* @param logic Predicate returning `true` to allow, `false` to deny, or an `Error` to deny with a specific error.
|
|
14
|
-
* @param cache graphql-shield cache strategy. `'strict'` (default) keys on parent+args+ctx; `'contextual'` keys on ctx only.
|
|
6
|
+
* Mirrors the underlying `rule` call signature — `createRule(name?, options?)(fn)` — with the
|
|
7
|
+
* same parameter order and meanings. The only difference is that `parent`, `args`, and `ctx` on
|
|
8
|
+
* `fn` are typed via the generics instead of `any`. Returns the same {@link ShieldRule} that the
|
|
9
|
+
* underlying `rule` produces.
|
|
15
10
|
*
|
|
16
11
|
* Usage:
|
|
17
12
|
*
|
|
18
|
-
* const isOwner = createRule<GraphQLContext, { ownerId: string }>(
|
|
13
|
+
* const isOwner = createRule<GraphQLContext, { ownerId: string }>()(
|
|
19
14
|
* (parent, _, ctx) => parent.ownerId === ctx.user?.id,
|
|
20
15
|
* )
|
|
16
|
+
*
|
|
17
|
+
* const isAdmin = createRule<GraphQLContext>('isAdmin', { cache: 'contextual' })(
|
|
18
|
+
* (_, __, ctx) => ctx.user?.roles.includes('admin') === true,
|
|
19
|
+
* )
|
|
21
20
|
*/
|
|
22
|
-
const createRule = (
|
|
21
|
+
const createRule = (name, options) => (fn) => rule(name, options)(fn);
|
|
23
22
|
/**
|
|
24
23
|
* Combines a given rule with all existing defined rules using the provided combinator
|
|
25
24
|
* @param schema The existing shield schema with rules
|
package/shield.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shield.mjs","sources":["../src/shield.ts"],"sourcesContent":["import type { allow, and, chain, IRules, or, race } from 'graphql-shield'\nimport { rule, shield } from 'graphql-shield'\nimport type { GraphQLContext } from './context'\nimport type { Primitive } from './type-utils'\n\nexport type ShieldOptions = NonNullable<Parameters<typeof shield>[1]>\n\ntype RuleCombinator = typeof chain | typeof race | typeof or | typeof and\n// For whatever reason, graphql-shield doesn't export this type, but we can extract if from\n// what it does export.\nexport type ShieldRule = ReturnType<(typeof allow)['getRules']>[number]\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"shield.mjs","sources":["../src/shield.ts"],"sourcesContent":["import type { GraphQLResolveInfo } from 'graphql'\nimport type { allow, and, chain, IRules, or, race } from 'graphql-shield'\nimport { rule, shield } from 'graphql-shield'\nimport type { GraphQLContext } from './context'\nimport type { Primitive } from './type-utils'\n\nexport type ShieldOptions = NonNullable<Parameters<typeof shield>[1]>\n\ntype RuleCombinator = typeof chain | typeof race | typeof or | typeof and\n// For whatever reason, graphql-shield doesn't export this type, but we can extract if from\n// what it does export.\nexport type ShieldRule = ReturnType<(typeof allow)['getRules']>[number]\n\ntype RuleConstructorOptions = NonNullable<Parameters<typeof rule>[1]>\ntype RuleResult = boolean | string | Error\ntype ShieldRuleFn = Parameters<ReturnType<typeof rule>>[0]\n\n/**\n * Strongly typed wrapper around graphql-shield's `rule(...)`.\n *\n * Mirrors the underlying `rule` call signature — `createRule(name?, options?)(fn)` — with the\n * same parameter order and meanings. The only difference is that `parent`, `args`, and `ctx` on\n * `fn` are typed via the generics instead of `any`. Returns the same {@link ShieldRule} that the\n * underlying `rule` produces.\n *\n * Usage:\n *\n * const isOwner = createRule<GraphQLContext, { ownerId: string }>()(\n * (parent, _, ctx) => parent.ownerId === ctx.user?.id,\n * )\n *\n * const isAdmin = createRule<GraphQLContext>('isAdmin', { cache: 'contextual' })(\n * (_, __, ctx) => ctx.user?.roles.includes('admin') === true,\n * )\n */\nexport const createRule =\n <TContext, TParent = unknown, TArgs = unknown>(name?: string | RuleConstructorOptions, options?: RuleConstructorOptions) =>\n (fn: (parent: TParent, args: TArgs, ctx: TContext, info: GraphQLResolveInfo) => RuleResult | Promise<RuleResult>): ShieldRule =>\n rule(name, options)(fn as ShieldRuleFn)\n\n/**\n * Combines a given rule with all existing defined rules using the provided combinator\n * @param schema The existing shield schema with rules\n * @param rule The rule to be combined with existing rules\n * @param combinator The combinator to use. Eg. and | or | chain | race\n *\n * Usage:\n *\n * const updatedSchema = combineRuleWithAll(shieldSchema, rules.accountNotBlocked, chain)\n */\nexport function combineRuleWithAll<T>(schema: ShieldSchema<T>, rule: ShieldRule, combinator: RuleCombinator): ShieldSchema<T> {\n return Object.fromEntries(\n Object.entries(schema).map(([key, value]) => {\n if (value.constructor.name !== 'Object') {\n return [key, combinator(rule, value)]\n }\n return [key, combineRuleWithAll(value, rule, combinator)]\n }),\n ) as ShieldSchema<T>\n}\n\n/**\n * Creates graphql shield middleware making use of generics to type the definition of the rules.\n * @param schema A mapping of schema objects to the shield rules that define the access to that object\n * @param options Shield options forwarded to `shield`. Defaults `allowExternalErrors` to `true`.\n *\n * Usage:\n * import { Resolvers } from '../generated/types'\n *\n * const isCoordinator = hasRoleRule(UserRoles.Coordinator)\n * const isSystemAdmin = hasRoleRule(UserRoles.SystemAdmin)\n * const isQualityAssurance = hasRoleRule(UserRoles.QualityAssurance)\n *\n * const isAuthorisedUser = or(isCoordinator, isSystemAdmin, isQualityAssurance)\n * const isDataAuthor = or(isCoordinator, isSystemAdmin)\n *\n * const shieldSchema = createShieldSchema<Resolvers>({\n * Query: {\n * '*': isAuthorisedUser,\n * user: isCoordinator,\n * findUsers: isCoordinator,\n * },\n * Mutation: {\n * '*': isDataAuthor,\n * createUser: isCoordinator,\n * },\n * // specific rules for mutations, types, fields...\n * }, { fallbackRule: isAuthorisedUser, fallbackError: unauthorisedError()})\n *\n * const unprotected = makeExecutableSchema({...})\n * return applyMiddleware(unprotected, shieldSchema)\n */\nexport function createShieldSchema<TRootResolvers>(schema: ShieldSchema<TRootResolvers>, options?: ShieldOptions) {\n return shield(schema as IRules, {\n allowExternalErrors: true,\n ...options,\n })\n}\n\n/**\n * A shield rule tree typed against a resolver map (typically your generated `Resolvers` type).\n *\n * Mirrors the shape of `TResolver` so each type/field can be assigned a {@link ShieldRule}.\n * Object levels may also include a `'*'` key — a fallback rule applied to any field at that\n * level that doesn't have an explicit rule. Leaf (primitive) positions collapse to a single\n * {@link ShieldRule}.\n *\n * Usage:\n *\n * const schema: ShieldSchema<Resolvers> = {\n * Query: {\n * '*': isAuthorisedUser,\n * publicThing: allow,\n * },\n * Mutation: { createUser: isCoordinator },\n * }\n */\nexport type ShieldSchema<TResolver> = TResolver extends Primitive\n ? ShieldRule\n : {\n [key in keyof TResolver]?: ShieldSchema<TResolver[key]> | ShieldRule\n } & {\n '*'?: ShieldRule\n }\n\n/**\n * Shield rule that passes when the current user has the given role.\n * @param role The role the user must have on `ctx.user.roles`.\n * @param ruleName Optional override for the rule's cache key. Defaults to `hasRole-${role}`.\n */\nexport function hasRoleRule(role: string, ruleName?: string): ShieldRule {\n return rule(ruleName ?? `hasRole-${role}`, { cache: 'contextual' })(\n (_, __, { user }: GraphQLContext) => user?.roles.includes(role) === true,\n )\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given roles.\n * Rule name is derived from the roles; use {@link hasAnyRoleRuleWithName} to supply your own.\n * @param roles The set of roles, any of which satisfies the rule.\n */\nexport function hasAnyRoleRule(...roles: string[]): ShieldRule {\n return hasAnyRoleRuleWithName(`hasAnyRole-${roles.join(',')}`, ...roles)\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given roles, with an explicit rule name.\n * Prefer this over {@link hasAnyRoleRule} when the role list is large or dynamic and you want a stable cache key.\n * @param ruleName The name (and cache key) for the rule.\n * @param roles The set of roles, any of which satisfies the rule.\n */\nexport function hasAnyRoleRuleWithName(ruleName: string, ...roles: string[]): ShieldRule {\n return rule(ruleName, { cache: 'contextual' })((_, __, { user }: GraphQLContext) => {\n return user?.roles.some((role: string) => roles.includes(role)) === true\n })\n}\n\n/**\n * Shield rule that passes when the current user has the given scope.\n * @param scope The scope the user must have on `ctx.user.scopes`.\n */\nexport function hasScopeRule(scope: string): ShieldRule {\n return rule(`hasScope-${scope}`, { cache: 'contextual' })((_, __, { user }: GraphQLContext) => user?.scopes.includes(scope) === true)\n}\n\n/**\n * Shield rule that passes when the current user has at least one of the given scopes.\n * @param scopes The set of scopes, any of which satisfies the rule.\n */\nexport function hasAnyScopeRule(...scopes: string[]): ShieldRule {\n return rule(`hasAnyScope-${scopes.join(',')}`, { cache: 'contextual' })(\n (_, __, { user }: GraphQLContext) => user?.scopes.some((scope: string) => scopes.includes(scope)) === true,\n )\n}\n"],"names":[],"mappings":";;AAiBA;;;;;;;;;;;;;;;;;AAiBG;AACI,MAAM,UAAU,GACrB,CAA+C,IAAsC,EAAE,OAAgC,KACvH,CAAC,EAA+G,KAC9G,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,EAAkB;AAE1C;;;;;;;;;AASG;SACa,kBAAkB,CAAI,MAAuB,EAAE,IAAgB,EAAE,UAA0B,EAAA;IACzG,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,KAAI;QAC1C,IAAI,KAAK,CAAC,WAAW,CAAC,IAAI,KAAK,QAAQ,EAAE;YACvC,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACvC;AACA,QAAA,OAAO,CAAC,GAAG,EAAE,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IAC3D,CAAC,CAAC,CACgB;AACtB;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BG;AACG,SAAU,kBAAkB,CAAiB,MAAoC,EAAE,OAAuB,EAAA;IAC9G,OAAO,MAAM,CAAC,MAAgB,EAAE;AAC9B,QAAA,mBAAmB,EAAE,IAAI;AACzB,QAAA,GAAG,OAAO;AACX,KAAA,CAAC;AACJ;AA4BA;;;;AAIG;AACG,SAAU,WAAW,CAAC,IAAY,EAAE,QAAiB,EAAA;AACzD,IAAA,OAAO,IAAI,CAAC,QAAQ,IAAI,CAAA,QAAA,EAAW,IAAI,CAAA,CAAE,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CACjE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAkB,KAAK,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,CACzE;AACH;AAEA;;;;AAIG;AACG,SAAU,cAAc,CAAC,GAAG,KAAe,EAAA;AAC/C,IAAA,OAAO,sBAAsB,CAAC,CAAA,WAAA,EAAc,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA,CAAE,EAAE,GAAG,KAAK,CAAC;AAC1E;AAEA;;;;;AAKG;SACa,sBAAsB,CAAC,QAAgB,EAAE,GAAG,KAAe,EAAA;IACzE,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAkB,KAAI;QACjF,OAAO,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,IAAY,KAAK,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI;AAC1E,IAAA,CAAC,CAAC;AACJ;AAEA;;;AAGG;AACG,SAAU,YAAY,CAAC,KAAa,EAAA;AACxC,IAAA,OAAO,IAAI,CAAC,CAAA,SAAA,EAAY,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAkB,KAAK,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;AACvI;AAEA;;;AAGG;AACG,SAAU,eAAe,CAAC,GAAG,MAAgB,EAAA;IACjD,OAAO,IAAI,CAAC,CAAA,YAAA,EAAe,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA,CAAE,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CACrE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAkB,KAAK,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,KAAa,KAAK,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAC3G;AACH;;;;"}
|
package/subscriptions/index.d.ts
CHANGED
package/subscriptions/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var context = require('./context.js');
|
|
4
|
+
var subscriptionIterator = require('./subscription-iterator.js');
|
|
4
5
|
var server = require('./server.js');
|
|
5
6
|
var utils = require('./utils.js');
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
exports.createSubscriptionContextFactory = context.createSubscriptionContextFactory;
|
|
11
|
+
exports.wrapSubscriptionIterator = subscriptionIterator.wrapSubscriptionIterator;
|
|
10
12
|
exports.useSubscriptionsServer = server.useSubscriptionsServer;
|
|
11
13
|
exports.extractTokenFromConnectionParams = utils.extractTokenFromConnectionParams;
|
|
12
14
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
|
package/subscriptions/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createSubscriptionContextFactory } from './context.mjs';
|
|
2
|
+
export { wrapSubscriptionIterator } from './subscription-iterator.mjs';
|
|
2
3
|
export { useSubscriptionsServer } from './server.mjs';
|
|
3
4
|
export { extractTokenFromConnectionParams } from './utils.mjs';
|
|
4
5
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps an async iterator with options for:
|
|
3
|
+
* - returning an initial payload (e.g. current state)
|
|
4
|
+
* - eagerly ending iteration when a final event is received
|
|
5
|
+
*
|
|
6
|
+
* Providing an initial payload helps avoid race conditions when clients subscribe to events but miss recent state changes,
|
|
7
|
+
* by providing an initial payload to be immediately returned, before subsequent events stream in.
|
|
8
|
+
*
|
|
9
|
+
* Eagerly ending iteration when a final event ensures subscriptions do not continue unnecessarily.
|
|
10
|
+
*
|
|
11
|
+
* @param iterator The async iterator to wrap.
|
|
12
|
+
* @param initialPayload An optional initial payload to return on the first iteration. May be a single event or an array.
|
|
13
|
+
* @param eventIsFinal An optional function to determine if an event is final (and iteration should end).
|
|
14
|
+
* Note: only applied to events from the wrapped iterator, not to entries in `initialPayload`.
|
|
15
|
+
* @returns An async iterator for the event data.
|
|
16
|
+
*/
|
|
17
|
+
export declare function wrapSubscriptionIterator<TEventData>({ iterator: wrapped, initialPayload, eventIsFinal, }: {
|
|
18
|
+
iterator: AsyncIterableIterator<TEventData>;
|
|
19
|
+
initialPayload?: TEventData | TEventData[];
|
|
20
|
+
eventIsFinal?: (event: TEventData) => boolean;
|
|
21
|
+
}): AsyncIterator<TEventData> & AsyncIterable<TEventData>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps an async iterator with options for:
|
|
3
|
+
* - returning an initial payload (e.g. current state)
|
|
4
|
+
* - eagerly ending iteration when a final event is received
|
|
5
|
+
*
|
|
6
|
+
* Providing an initial payload helps avoid race conditions when clients subscribe to events but miss recent state changes,
|
|
7
|
+
* by providing an initial payload to be immediately returned, before subsequent events stream in.
|
|
8
|
+
*
|
|
9
|
+
* Eagerly ending iteration when a final event ensures subscriptions do not continue unnecessarily.
|
|
10
|
+
*
|
|
11
|
+
* @param iterator The async iterator to wrap.
|
|
12
|
+
* @param initialPayload An optional initial payload to return on the first iteration. May be a single event or an array.
|
|
13
|
+
* @param eventIsFinal An optional function to determine if an event is final (and iteration should end).
|
|
14
|
+
* Note: only applied to events from the wrapped iterator, not to entries in `initialPayload`.
|
|
15
|
+
* @returns An async iterator for the event data.
|
|
16
|
+
*/
|
|
17
|
+
export declare function wrapSubscriptionIterator<TEventData>({ iterator: wrapped, initialPayload, eventIsFinal, }: {
|
|
18
|
+
iterator: AsyncIterableIterator<TEventData>;
|
|
19
|
+
initialPayload?: TEventData | TEventData[];
|
|
20
|
+
eventIsFinal?: (event: TEventData) => boolean;
|
|
21
|
+
}): AsyncIterator<TEventData> & AsyncIterable<TEventData>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps an async iterator with options for:
|
|
3
|
+
* - returning an initial payload (e.g. current state)
|
|
4
|
+
* - eagerly ending iteration when a final event is received
|
|
5
|
+
*
|
|
6
|
+
* Providing an initial payload helps avoid race conditions when clients subscribe to events but miss recent state changes,
|
|
7
|
+
* by providing an initial payload to be immediately returned, before subsequent events stream in.
|
|
8
|
+
*
|
|
9
|
+
* Eagerly ending iteration when a final event ensures subscriptions do not continue unnecessarily.
|
|
10
|
+
*
|
|
11
|
+
* @param iterator The async iterator to wrap.
|
|
12
|
+
* @param initialPayload An optional initial payload to return on the first iteration. May be a single event or an array.
|
|
13
|
+
* @param eventIsFinal An optional function to determine if an event is final (and iteration should end).
|
|
14
|
+
* Note: only applied to events from the wrapped iterator, not to entries in `initialPayload`.
|
|
15
|
+
* @returns An async iterator for the event data.
|
|
16
|
+
*/
|
|
17
|
+
export declare function wrapSubscriptionIterator<TEventData>({ iterator: wrapped, initialPayload, eventIsFinal, }: {
|
|
18
|
+
iterator: AsyncIterableIterator<TEventData>;
|
|
19
|
+
initialPayload?: TEventData | TEventData[];
|
|
20
|
+
eventIsFinal?: (event: TEventData) => boolean;
|
|
21
|
+
}): AsyncIterator<TEventData> & AsyncIterable<TEventData>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wraps an async iterator with options for:
|
|
5
|
+
* - returning an initial payload (e.g. current state)
|
|
6
|
+
* - eagerly ending iteration when a final event is received
|
|
7
|
+
*
|
|
8
|
+
* Providing an initial payload helps avoid race conditions when clients subscribe to events but miss recent state changes,
|
|
9
|
+
* by providing an initial payload to be immediately returned, before subsequent events stream in.
|
|
10
|
+
*
|
|
11
|
+
* Eagerly ending iteration when a final event ensures subscriptions do not continue unnecessarily.
|
|
12
|
+
*
|
|
13
|
+
* @param iterator The async iterator to wrap.
|
|
14
|
+
* @param initialPayload An optional initial payload to return on the first iteration. May be a single event or an array.
|
|
15
|
+
* @param eventIsFinal An optional function to determine if an event is final (and iteration should end).
|
|
16
|
+
* Note: only applied to events from the wrapped iterator, not to entries in `initialPayload`.
|
|
17
|
+
* @returns An async iterator for the event data.
|
|
18
|
+
*/
|
|
19
|
+
function wrapSubscriptionIterator({ iterator: wrapped, initialPayload, eventIsFinal, }) {
|
|
20
|
+
let done = false;
|
|
21
|
+
const initialQueue = initialPayload === undefined ? [] : Array.isArray(initialPayload) ? [...initialPayload] : [initialPayload];
|
|
22
|
+
const iterator = {
|
|
23
|
+
next: async () => {
|
|
24
|
+
if (done)
|
|
25
|
+
return { done: true, value: undefined };
|
|
26
|
+
if (initialQueue.length > 0) {
|
|
27
|
+
const value = initialQueue.shift();
|
|
28
|
+
return { done: false, value };
|
|
29
|
+
}
|
|
30
|
+
const next = await wrapped.next();
|
|
31
|
+
if (!next.done && eventIsFinal && eventIsFinal(next.value)) {
|
|
32
|
+
done = true;
|
|
33
|
+
await wrapped.return?.();
|
|
34
|
+
}
|
|
35
|
+
return next;
|
|
36
|
+
},
|
|
37
|
+
return: async (value) => {
|
|
38
|
+
done = true;
|
|
39
|
+
initialQueue.length = 0;
|
|
40
|
+
if (wrapped.return)
|
|
41
|
+
return wrapped.return(value);
|
|
42
|
+
return { done: true, value: value };
|
|
43
|
+
},
|
|
44
|
+
throw: async (err) => {
|
|
45
|
+
done = true;
|
|
46
|
+
initialQueue.length = 0;
|
|
47
|
+
if (wrapped.throw)
|
|
48
|
+
return wrapped.throw(err);
|
|
49
|
+
throw err;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
return { ...iterator, [Symbol.asyncIterator]: () => iterator };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
exports.wrapSubscriptionIterator = wrapSubscriptionIterator;
|
|
56
|
+
//# sourceMappingURL=subscription-iterator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription-iterator.js","sources":["../../src/subscriptions/subscription-iterator.ts"],"sourcesContent":["/**\n * Wraps an async iterator with options for:\n * - returning an initial payload (e.g. current state)\n * - eagerly ending iteration when a final event is received\n *\n * Providing an initial payload helps avoid race conditions when clients subscribe to events but miss recent state changes,\n * by providing an initial payload to be immediately returned, before subsequent events stream in.\n *\n * Eagerly ending iteration when a final event ensures subscriptions do not continue unnecessarily.\n *\n * @param iterator The async iterator to wrap.\n * @param initialPayload An optional initial payload to return on the first iteration. May be a single event or an array.\n * @param eventIsFinal An optional function to determine if an event is final (and iteration should end).\n * Note: only applied to events from the wrapped iterator, not to entries in `initialPayload`.\n * @returns An async iterator for the event data.\n */\nexport function wrapSubscriptionIterator<TEventData>({\n iterator: wrapped,\n initialPayload,\n eventIsFinal,\n}: {\n iterator: AsyncIterableIterator<TEventData>\n initialPayload?: TEventData | TEventData[]\n eventIsFinal?: (event: TEventData) => boolean\n}): AsyncIterator<TEventData> & AsyncIterable<TEventData> {\n let done = false\n const initialQueue: TEventData[] =\n initialPayload === undefined ? [] : Array.isArray(initialPayload) ? [...initialPayload] : [initialPayload]\n\n const iterator: AsyncIterator<TEventData> = {\n next: async () => {\n if (done) return { done: true, value: undefined }\n\n if (initialQueue.length > 0) {\n const value = initialQueue.shift() as TEventData\n return { done: false, value }\n }\n\n const next = await wrapped.next()\n if (!next.done && eventIsFinal && eventIsFinal(next.value)) {\n done = true\n await wrapped.return?.()\n }\n return next\n },\n return: async (value) => {\n done = true\n initialQueue.length = 0\n if (wrapped.return) return wrapped.return(value)\n return { done: true, value: value as TEventData }\n },\n throw: async (err) => {\n done = true\n initialQueue.length = 0\n if (wrapped.throw) return wrapped.throw(err)\n throw err\n },\n }\n\n return { ...iterator, [Symbol.asyncIterator]: () => iterator }\n}\n"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;;;;AAeG;AACG,SAAU,wBAAwB,CAAa,EACnD,QAAQ,EAAE,OAAO,EACjB,cAAc,EACd,YAAY,GAKb,EAAA;IACC,IAAI,IAAI,GAAG,KAAK;AAChB,IAAA,MAAM,YAAY,GAChB,cAAc,KAAK,SAAS,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,cAAc,CAAC;AAE5G,IAAA,MAAM,QAAQ,GAA8B;QAC1C,IAAI,EAAE,YAAW;AACf,YAAA,IAAI,IAAI;gBAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE;AAEjD,YAAA,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;AAC3B,gBAAA,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,EAAgB;AAChD,gBAAA,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE;YAC/B;AAEA,YAAA,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE;AACjC,YAAA,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,YAAY,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;gBAC1D,IAAI,GAAG,IAAI;AACX,gBAAA,MAAM,OAAO,CAAC,MAAM,IAAI;YAC1B;AACA,YAAA,OAAO,IAAI;QACb,CAAC;AACD,QAAA,MAAM,EAAE,OAAO,KAAK,KAAI;YACtB,IAAI,GAAG,IAAI;AACX,YAAA,YAAY,CAAC,MAAM,GAAG,CAAC;YACvB,IAAI,OAAO,CAAC,MAAM;AAAE,gBAAA,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;YAChD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAmB,EAAE;QACnD,CAAC;AACD,QAAA,KAAK,EAAE,OAAO,GAAG,KAAI;YACnB,IAAI,GAAG,IAAI;AACX,YAAA,YAAY,CAAC,MAAM,GAAG,CAAC;YACvB,IAAI,OAAO,CAAC,KAAK;AAAE,gBAAA,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;AAC5C,YAAA,MAAM,GAAG;QACX,CAAC;KACF;AAED,IAAA,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC,MAAM,CAAC,aAAa,GAAG,MAAM,QAAQ,EAAE;AAChE;;;;"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps an async iterator with options for:
|
|
3
|
+
* - returning an initial payload (e.g. current state)
|
|
4
|
+
* - eagerly ending iteration when a final event is received
|
|
5
|
+
*
|
|
6
|
+
* Providing an initial payload helps avoid race conditions when clients subscribe to events but miss recent state changes,
|
|
7
|
+
* by providing an initial payload to be immediately returned, before subsequent events stream in.
|
|
8
|
+
*
|
|
9
|
+
* Eagerly ending iteration when a final event ensures subscriptions do not continue unnecessarily.
|
|
10
|
+
*
|
|
11
|
+
* @param iterator The async iterator to wrap.
|
|
12
|
+
* @param initialPayload An optional initial payload to return on the first iteration. May be a single event or an array.
|
|
13
|
+
* @param eventIsFinal An optional function to determine if an event is final (and iteration should end).
|
|
14
|
+
* Note: only applied to events from the wrapped iterator, not to entries in `initialPayload`.
|
|
15
|
+
* @returns An async iterator for the event data.
|
|
16
|
+
*/
|
|
17
|
+
function wrapSubscriptionIterator({ iterator: wrapped, initialPayload, eventIsFinal, }) {
|
|
18
|
+
let done = false;
|
|
19
|
+
const initialQueue = initialPayload === undefined ? [] : Array.isArray(initialPayload) ? [...initialPayload] : [initialPayload];
|
|
20
|
+
const iterator = {
|
|
21
|
+
next: async () => {
|
|
22
|
+
if (done)
|
|
23
|
+
return { done: true, value: undefined };
|
|
24
|
+
if (initialQueue.length > 0) {
|
|
25
|
+
const value = initialQueue.shift();
|
|
26
|
+
return { done: false, value };
|
|
27
|
+
}
|
|
28
|
+
const next = await wrapped.next();
|
|
29
|
+
if (!next.done && eventIsFinal && eventIsFinal(next.value)) {
|
|
30
|
+
done = true;
|
|
31
|
+
await wrapped.return?.();
|
|
32
|
+
}
|
|
33
|
+
return next;
|
|
34
|
+
},
|
|
35
|
+
return: async (value) => {
|
|
36
|
+
done = true;
|
|
37
|
+
initialQueue.length = 0;
|
|
38
|
+
if (wrapped.return)
|
|
39
|
+
return wrapped.return(value);
|
|
40
|
+
return { done: true, value: value };
|
|
41
|
+
},
|
|
42
|
+
throw: async (err) => {
|
|
43
|
+
done = true;
|
|
44
|
+
initialQueue.length = 0;
|
|
45
|
+
if (wrapped.throw)
|
|
46
|
+
return wrapped.throw(err);
|
|
47
|
+
throw err;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return { ...iterator, [Symbol.asyncIterator]: () => iterator };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { wrapSubscriptionIterator };
|
|
54
|
+
//# sourceMappingURL=subscription-iterator.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription-iterator.mjs","sources":["../../src/subscriptions/subscription-iterator.ts"],"sourcesContent":["/**\n * Wraps an async iterator with options for:\n * - returning an initial payload (e.g. current state)\n * - eagerly ending iteration when a final event is received\n *\n * Providing an initial payload helps avoid race conditions when clients subscribe to events but miss recent state changes,\n * by providing an initial payload to be immediately returned, before subsequent events stream in.\n *\n * Eagerly ending iteration when a final event ensures subscriptions do not continue unnecessarily.\n *\n * @param iterator The async iterator to wrap.\n * @param initialPayload An optional initial payload to return on the first iteration. May be a single event or an array.\n * @param eventIsFinal An optional function to determine if an event is final (and iteration should end).\n * Note: only applied to events from the wrapped iterator, not to entries in `initialPayload`.\n * @returns An async iterator for the event data.\n */\nexport function wrapSubscriptionIterator<TEventData>({\n iterator: wrapped,\n initialPayload,\n eventIsFinal,\n}: {\n iterator: AsyncIterableIterator<TEventData>\n initialPayload?: TEventData | TEventData[]\n eventIsFinal?: (event: TEventData) => boolean\n}): AsyncIterator<TEventData> & AsyncIterable<TEventData> {\n let done = false\n const initialQueue: TEventData[] =\n initialPayload === undefined ? [] : Array.isArray(initialPayload) ? [...initialPayload] : [initialPayload]\n\n const iterator: AsyncIterator<TEventData> = {\n next: async () => {\n if (done) return { done: true, value: undefined }\n\n if (initialQueue.length > 0) {\n const value = initialQueue.shift() as TEventData\n return { done: false, value }\n }\n\n const next = await wrapped.next()\n if (!next.done && eventIsFinal && eventIsFinal(next.value)) {\n done = true\n await wrapped.return?.()\n }\n return next\n },\n return: async (value) => {\n done = true\n initialQueue.length = 0\n if (wrapped.return) return wrapped.return(value)\n return { done: true, value: value as TEventData }\n },\n throw: async (err) => {\n done = true\n initialQueue.length = 0\n if (wrapped.throw) return wrapped.throw(err)\n throw err\n },\n }\n\n return { ...iterator, [Symbol.asyncIterator]: () => iterator }\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;AAeG;AACG,SAAU,wBAAwB,CAAa,EACnD,QAAQ,EAAE,OAAO,EACjB,cAAc,EACd,YAAY,GAKb,EAAA;IACC,IAAI,IAAI,GAAG,KAAK;AAChB,IAAA,MAAM,YAAY,GAChB,cAAc,KAAK,SAAS,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,cAAc,CAAC;AAE5G,IAAA,MAAM,QAAQ,GAA8B;QAC1C,IAAI,EAAE,YAAW;AACf,YAAA,IAAI,IAAI;gBAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE;AAEjD,YAAA,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;AAC3B,gBAAA,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,EAAgB;AAChD,gBAAA,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE;YAC/B;AAEA,YAAA,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE;AACjC,YAAA,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,YAAY,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;gBAC1D,IAAI,GAAG,IAAI;AACX,gBAAA,MAAM,OAAO,CAAC,MAAM,IAAI;YAC1B;AACA,YAAA,OAAO,IAAI;QACb,CAAC;AACD,QAAA,MAAM,EAAE,OAAO,KAAK,KAAI;YACtB,IAAI,GAAG,IAAI;AACX,YAAA,YAAY,CAAC,MAAM,GAAG,CAAC;YACvB,IAAI,OAAO,CAAC,MAAM;AAAE,gBAAA,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;YAChD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAmB,EAAE;QACnD,CAAC;AACD,QAAA,KAAK,EAAE,OAAO,GAAG,KAAI;YACnB,IAAI,GAAG,IAAI;AACX,YAAA,YAAY,CAAC,MAAM,GAAG,CAAC;YACvB,IAAI,OAAO,CAAC,KAAK;AAAE,gBAAA,OAAO,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;AAC5C,YAAA,MAAM,GAAG;QACX,CAAC;KACF;AAED,IAAA,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC,MAAM,CAAC,aAAa,GAAG,MAAM,QAAQ,EAAE;AAChE;;;;"}
|