@makerx/graphql-core 3.0.0-beta.7 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/User.d.mts +1 -1
- package/context.d.mts +2 -2
- package/index.d.cts +1 -0
- package/index.d.mts +9 -8
- package/index.d.ts +1 -0
- package/index.js +3 -1
- package/index.js.map +1 -1
- package/index.mjs +2 -1
- package/index.mjs.map +1 -1
- package/logging.d.mts +1 -1
- package/logging.js +3 -2
- package/logging.js.map +1 -1
- package/logging.mjs +3 -2
- package/logging.mjs.map +1 -1
- package/operation.d.cts +2 -0
- package/operation.d.mts +2 -0
- package/operation.d.ts +2 -0
- package/operation.js +17 -0
- package/operation.js.map +1 -0
- package/operation.mjs +14 -0
- package/operation.mjs.map +1 -0
- package/package.json +1 -1
- package/shield.d.mts +1 -1
- package/subscriptions/context.d.mts +2 -2
- package/subscriptions/index.d.cts +1 -0
- package/subscriptions/index.d.mts +4 -3
- 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/server.d.mts +2 -2
- 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/utils.d.cts +0 -1
- package/utils.d.mts +0 -1
- package/utils.d.ts +0 -1
- package/utils.js +0 -2
- package/utils.js.map +1 -1
- package/utils.mjs +1 -2
- package/utils.mjs.map +1 -1
package/README.md
CHANGED
|
@@ -366,9 +366,44 @@ This library includes a `subscriptions` module to provide simple setup using the
|
|
|
366
366
|
|
|
367
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.
|
|
368
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
|
+
|
|
369
397
|
## Utils
|
|
370
398
|
|
|
371
399
|
- `isIntrospectionQuery`: indicates whether the query is an introspection query, based on the operation name or query content.
|
|
400
|
+
- `extractAnonymousOperationName`: derives a synthetic operation name from a query source that has no explicit name, by combining the operation type with the first top-level selection — handy for logging/observability when clients send anonymous operations. Returns `undefined` if no top-level field can be found.
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
extractAnonymousOperationName('{ users { id } }') // 'query users'
|
|
404
|
+
extractAnonymousOperationName('mutation { createUser { id } }') // 'mutation createUser'
|
|
405
|
+
extractAnonymousOperationName('subscription { onUpdate { id } }') // 'subscription onUpdate'
|
|
406
|
+
```
|
|
372
407
|
|
|
373
408
|
## \*Express peer dependency
|
|
374
409
|
|
package/User.d.mts
CHANGED
package/context.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Logger } from '@makerx/node-common';
|
|
2
2
|
import type { Request } from 'express';
|
|
3
|
-
import { type BaseRequestInfo } from './request-info.
|
|
4
|
-
import { User } from './User.
|
|
3
|
+
import { type BaseRequestInfo } from './request-info.mjs';
|
|
4
|
+
import { User } from './User.mjs';
|
|
5
5
|
export interface GraphQLContext<TLogger extends Logger = Logger, TRequestInfo extends BaseRequestInfo = RequestInfo, TUser = User | undefined> {
|
|
6
6
|
logger: TLogger;
|
|
7
7
|
requestInfo: TRequestInfo;
|
package/index.d.cts
CHANGED
package/index.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export * from './context.
|
|
2
|
-
export * from './logging.
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
6
|
-
export * from './
|
|
7
|
-
export * from './
|
|
8
|
-
export * from './
|
|
1
|
+
export * from './context.mjs';
|
|
2
|
+
export * from './logging.mjs';
|
|
3
|
+
export * from './operation.mjs';
|
|
4
|
+
export * from './request-info.mjs';
|
|
5
|
+
export * from './schema-util.mjs';
|
|
6
|
+
export * from './type-utils.mjs';
|
|
7
|
+
export * from './unauthorised.mjs';
|
|
8
|
+
export * from './User.mjs';
|
|
9
|
+
export * from './utils.mjs';
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var context = require('./context.js');
|
|
4
4
|
var logging = require('./logging.js');
|
|
5
|
+
var operation = require('./operation.js');
|
|
5
6
|
var requestInfo = require('./request-info.js');
|
|
6
7
|
var schemaUtil = require('./schema-util.js');
|
|
7
8
|
var unauthorised = require('./unauthorised.js');
|
|
@@ -14,6 +15,8 @@ exports.createContextFactory = context.createContextFactory;
|
|
|
14
15
|
exports.defaultCreateUser = context.defaultCreateUser;
|
|
15
16
|
exports.logGraphQLOperation = logging.logGraphQLOperation;
|
|
16
17
|
exports.logSubscriptionOperation = logging.logSubscriptionOperation;
|
|
18
|
+
exports.extractAnonymousOperationName = operation.extractAnonymousOperationName;
|
|
19
|
+
exports.isIntrospectionQuery = operation.isIntrospectionQuery;
|
|
17
20
|
exports.buildBaseRequestInfo = requestInfo.buildBaseRequestInfo;
|
|
18
21
|
exports.buildConnectRequestInfo = requestInfo.buildConnectRequestInfo;
|
|
19
22
|
exports.connectRequestBaseUrl = requestInfo.connectRequestBaseUrl;
|
|
@@ -23,7 +26,6 @@ exports.permissionInvariant = unauthorised.permissionInvariant;
|
|
|
23
26
|
exports.throwUnauthorised = unauthorised.throwUnauthorised;
|
|
24
27
|
exports.unauthorisedError = unauthorised.unauthorisedError;
|
|
25
28
|
exports.User = User.User;
|
|
26
|
-
exports.isIntrospectionQuery = utils.isIntrospectionQuery;
|
|
27
29
|
exports.isNil = utils.isNil;
|
|
28
30
|
exports.isNotNil = utils.isNotNil;
|
|
29
31
|
//# sourceMappingURL=index.js.map
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export { createContextFactory, defaultCreateUser } from './context.mjs';
|
|
2
2
|
export { logGraphQLOperation, logSubscriptionOperation } from './logging.mjs';
|
|
3
|
+
export { extractAnonymousOperationName, isIntrospectionQuery } from './operation.mjs';
|
|
3
4
|
export { buildBaseRequestInfo, buildConnectRequestInfo, connectRequestBaseUrl, requestBaseUrl } from './request-info.mjs';
|
|
4
5
|
export { requireExplicitResolversForScalars } from './schema-util.mjs';
|
|
5
6
|
export { permissionInvariant, throwUnauthorised, unauthorisedError } from './unauthorised.mjs';
|
|
6
7
|
export { User } from './User.mjs';
|
|
7
|
-
export {
|
|
8
|
+
export { isNil, isNotNil } from './utils.mjs';
|
|
8
9
|
//# sourceMappingURL=index.mjs.map
|
package/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
|
package/logging.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Logger } from '@makerx/node-common';
|
|
|
2
2
|
import type { ExecutionArgs, GraphQLFormattedError } from 'graphql';
|
|
3
3
|
import { OperationTypeNode } from 'graphql';
|
|
4
4
|
import type { ExecutionResult } from 'graphql-ws';
|
|
5
|
-
import type { GraphQLContext } from './context.
|
|
5
|
+
import type { GraphQLContext } from './context.mjs';
|
|
6
6
|
type LogFunction = Logger['info'];
|
|
7
7
|
export type LoggerLogFunctions<T extends Logger> = {
|
|
8
8
|
[Property in keyof T]: LogFunction;
|
package/logging.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var nodeCommon = require('@makerx/node-common');
|
|
4
|
-
var graphql = require('graphql');
|
|
5
4
|
var compat = require('es-toolkit/compat');
|
|
5
|
+
var graphql = require('graphql');
|
|
6
|
+
var operation = require('./operation.js');
|
|
6
7
|
var utils = require('./utils.js');
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -10,7 +11,7 @@ var utils = require('./utils.js');
|
|
|
10
11
|
* Top level and result entries with null or undefined values will be omitted.
|
|
11
12
|
*/
|
|
12
13
|
const logGraphQLOperation = ({ message = 'GraphQL operation', started, type, operationName, query, variables, result, logger, logLevel = 'info', ...rest }) => {
|
|
13
|
-
const isIntrospection = query &&
|
|
14
|
+
const isIntrospection = query && operation.isIntrospectionQuery(query);
|
|
14
15
|
if (nodeCommon.isLocalDev && isIntrospection)
|
|
15
16
|
return;
|
|
16
17
|
logger[logLevel](message, compat.omitBy({
|
package/logging.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logging.js","sources":["../src/logging.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { isLocalDev } from '@makerx/node-common'\nimport type { ExecutionArgs, GraphQLFormattedError } from 'graphql'\nimport { OperationTypeNode, print } from 'graphql'\nimport type { ExecutionResult } from 'graphql-ws'\nimport {
|
|
1
|
+
{"version":3,"file":"logging.js","sources":["../src/logging.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { isLocalDev } from '@makerx/node-common'\nimport { omitBy } from 'es-toolkit/compat'\nimport type { ExecutionArgs, GraphQLFormattedError } from 'graphql'\nimport { OperationTypeNode, print } from 'graphql'\nimport type { ExecutionResult } from 'graphql-ws'\nimport type { GraphQLContext } from './context'\nimport { isIntrospectionQuery } from './operation'\nimport { isNil } from './utils'\n\ntype LogFunction = Logger['info']\nexport type LoggerLogFunctions<T extends Logger> = {\n [Property in keyof T]: LogFunction\n}\n\n/**\n * Info for logging a GraphQL operation in a consistent format with the option of including any additional data.\n */\nexport interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extends Record<string, unknown> {\n /**\n * The message to log, defaults to 'GraphQL operation'.\n */\n message?: string\n /**\n * The timestamp when the operation started, if supplied, the duration will be logged.\n */\n started?: number\n /**\n * The type of GraphQL operation.\n */\n type?: OperationTypeNode | null\n /**\n * The name of the GraphQL operation.\n */\n operationName?: string | null\n /**\n * The formatted GraphQL query.\n */\n query?: string | null\n /**\n * The GraphQL variables.\n */\n variables?: Record<string, unknown> | null\n /**\n * The result of the GraphQL operation.\n * Generally, we don't log the data or extensions, just errors.\n */\n result?: {\n data?: Record<string, unknown> | null\n errors?: readonly GraphQLFormattedError[] | null\n extensions?: Record<string, unknown> | null\n hasNext?: boolean\n }\n /**\n * Whether the operation is an introspection query.\n */\n isIntrospectionQuery?: boolean\n /**\n * Whether the operation is part of an incremental response.\n */\n isIncrementalResponse?: boolean\n /**\n * Whether the operation is a subsequent payload of an incremental response.\n */\n isSubsequentPayload?: boolean\n /**\n * The logger to use.\n */\n logger: TLogger\n /**\n * The logger function to use, defaults to 'info'.\n */\n logLevel?: keyof LoggerLogFunctions<TLogger>\n}\n\n/**\n * Logs a GraphQL operation in a consistent format with the option of including any additional data.\n * Top level and result entries with null or undefined values will be omitted.\n */\nexport const logGraphQLOperation = <TLogger extends Logger = Logger>({\n message = 'GraphQL operation',\n started,\n type,\n operationName,\n query,\n variables,\n result,\n logger,\n logLevel = 'info',\n ...rest\n}: GraphQLLogOperationInfo<TLogger>) => {\n const isIntrospection = query && isIntrospectionQuery(query)\n if (isLocalDev && isIntrospection) return\n logger[logLevel as keyof Logger](\n message,\n omitBy(\n {\n type,\n operationName,\n query,\n variables: variables && Object.keys(variables).length > 0 ? variables : undefined,\n duration: started ? Date.now() - started : undefined,\n result: result ? omitBy(result, isNil) : undefined,\n isIntrospectionQuery: isIntrospection || undefined,\n ...omitBy(rest, isNil),\n },\n isNil,\n ),\n )\n}\n\nexport const logSubscriptionOperation = <TLogger extends Logger = Logger>({\n id,\n args,\n result,\n message,\n logLevel,\n resolveLogger,\n}: {\n id?: string\n args: ExecutionArgs\n result?: ExecutionResult\n message?: string\n logLevel?: keyof LoggerLogFunctions<TLogger>\n resolveLogger?: (context: GraphQLContext) => TLogger\n}) => {\n const logger = resolveLogger\n ? resolveLogger(args.contextValue as GraphQLContext)\n : ((args.contextValue as GraphQLContext).logger as TLogger)\n if (!logger) return\n\n const { operationName, variableValues, document } = args\n const { data, ...resultWithoutData } = result ?? {}\n\n logGraphQLOperation({\n message,\n type: OperationTypeNode.SUBSCRIPTION,\n subscriptionId: id,\n operationName,\n query: print(document),\n variables: variableValues,\n result: resultWithoutData,\n logger,\n logLevel,\n })\n}\n"],"names":["isIntrospectionQuery","isLocalDev","omitBy","isNil","OperationTypeNode","print"],"mappings":";;;;;;;;AA2EA;;;AAGG;AACI,MAAM,mBAAmB,GAAG,CAAkC,EACnE,OAAO,GAAG,mBAAmB,EAC7B,OAAO,EACP,IAAI,EACJ,aAAa,EACb,KAAK,EACL,SAAS,EACT,MAAM,EACN,MAAM,EACN,QAAQ,GAAG,MAAM,EACjB,GAAG,IAAI,EAC0B,KAAI;IACrC,MAAM,eAAe,GAAG,KAAK,IAAIA,8BAAoB,CAAC,KAAK,CAAC;IAC5D,IAAIC,qBAAU,IAAI,eAAe;QAAE;AACnC,IAAA,MAAM,CAAC,QAAwB,CAAC,CAC9B,OAAO,EACPC,aAAM,CACJ;QACE,IAAI;QACJ,aAAa;QACb,KAAK;QACL,SAAS,EAAE,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,SAAS,GAAG,SAAS;AACjF,QAAA,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,SAAS;AACpD,QAAA,MAAM,EAAE,MAAM,GAAGA,aAAM,CAAC,MAAM,EAAEC,WAAK,CAAC,GAAG,SAAS;QAClD,oBAAoB,EAAE,eAAe,IAAI,SAAS;AAClD,QAAA,GAAGD,aAAM,CAAC,IAAI,EAAEC,WAAK,CAAC;KACvB,EACDA,WAAK,CACN,CACF;AACH;AAEO,MAAM,wBAAwB,GAAG,CAAkC,EACxE,EAAE,EACF,IAAI,EACJ,MAAM,EACN,OAAO,EACP,QAAQ,EACR,aAAa,GAQd,KAAI;IACH,MAAM,MAAM,GAAG;AACb,UAAE,aAAa,CAAC,IAAI,CAAC,YAA8B;AACnD,UAAI,IAAI,CAAC,YAA+B,CAAC,MAAkB;AAC7D,IAAA,IAAI,CAAC,MAAM;QAAE;IAEb,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,IAAI;IACxD,MAAM,EAAE,IAAI,EAAE,GAAG,iBAAiB,EAAE,GAAG,MAAM,IAAI,EAAE;AAEnD,IAAA,mBAAmB,CAAC;QAClB,OAAO;QACP,IAAI,EAAEC,yBAAiB,CAAC,YAAY;AACpC,QAAA,cAAc,EAAE,EAAE;QAClB,aAAa;AACb,QAAA,KAAK,EAAEC,aAAK,CAAC,QAAQ,CAAC;AACtB,QAAA,SAAS,EAAE,cAAc;AACzB,QAAA,MAAM,EAAE,iBAAiB;QACzB,MAAM;QACN,QAAQ;AACT,KAAA,CAAC;AACJ;;;;;"}
|
package/logging.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { isLocalDev } from '@makerx/node-common';
|
|
2
|
-
import { print, OperationTypeNode } from 'graphql';
|
|
3
2
|
import { omitBy } from 'es-toolkit/compat';
|
|
4
|
-
import {
|
|
3
|
+
import { print, OperationTypeNode } from 'graphql';
|
|
4
|
+
import { isIntrospectionQuery } from './operation.mjs';
|
|
5
|
+
import { isNil } from './utils.mjs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Logs a GraphQL operation in a consistent format with the option of including any additional data.
|
package/logging.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logging.mjs","sources":["../src/logging.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { isLocalDev } from '@makerx/node-common'\nimport type { ExecutionArgs, GraphQLFormattedError } from 'graphql'\nimport { OperationTypeNode, print } from 'graphql'\nimport type { ExecutionResult } from 'graphql-ws'\nimport {
|
|
1
|
+
{"version":3,"file":"logging.mjs","sources":["../src/logging.ts"],"sourcesContent":["import type { Logger } from '@makerx/node-common'\nimport { isLocalDev } from '@makerx/node-common'\nimport { omitBy } from 'es-toolkit/compat'\nimport type { ExecutionArgs, GraphQLFormattedError } from 'graphql'\nimport { OperationTypeNode, print } from 'graphql'\nimport type { ExecutionResult } from 'graphql-ws'\nimport type { GraphQLContext } from './context'\nimport { isIntrospectionQuery } from './operation'\nimport { isNil } from './utils'\n\ntype LogFunction = Logger['info']\nexport type LoggerLogFunctions<T extends Logger> = {\n [Property in keyof T]: LogFunction\n}\n\n/**\n * Info for logging a GraphQL operation in a consistent format with the option of including any additional data.\n */\nexport interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extends Record<string, unknown> {\n /**\n * The message to log, defaults to 'GraphQL operation'.\n */\n message?: string\n /**\n * The timestamp when the operation started, if supplied, the duration will be logged.\n */\n started?: number\n /**\n * The type of GraphQL operation.\n */\n type?: OperationTypeNode | null\n /**\n * The name of the GraphQL operation.\n */\n operationName?: string | null\n /**\n * The formatted GraphQL query.\n */\n query?: string | null\n /**\n * The GraphQL variables.\n */\n variables?: Record<string, unknown> | null\n /**\n * The result of the GraphQL operation.\n * Generally, we don't log the data or extensions, just errors.\n */\n result?: {\n data?: Record<string, unknown> | null\n errors?: readonly GraphQLFormattedError[] | null\n extensions?: Record<string, unknown> | null\n hasNext?: boolean\n }\n /**\n * Whether the operation is an introspection query.\n */\n isIntrospectionQuery?: boolean\n /**\n * Whether the operation is part of an incremental response.\n */\n isIncrementalResponse?: boolean\n /**\n * Whether the operation is a subsequent payload of an incremental response.\n */\n isSubsequentPayload?: boolean\n /**\n * The logger to use.\n */\n logger: TLogger\n /**\n * The logger function to use, defaults to 'info'.\n */\n logLevel?: keyof LoggerLogFunctions<TLogger>\n}\n\n/**\n * Logs a GraphQL operation in a consistent format with the option of including any additional data.\n * Top level and result entries with null or undefined values will be omitted.\n */\nexport const logGraphQLOperation = <TLogger extends Logger = Logger>({\n message = 'GraphQL operation',\n started,\n type,\n operationName,\n query,\n variables,\n result,\n logger,\n logLevel = 'info',\n ...rest\n}: GraphQLLogOperationInfo<TLogger>) => {\n const isIntrospection = query && isIntrospectionQuery(query)\n if (isLocalDev && isIntrospection) return\n logger[logLevel as keyof Logger](\n message,\n omitBy(\n {\n type,\n operationName,\n query,\n variables: variables && Object.keys(variables).length > 0 ? variables : undefined,\n duration: started ? Date.now() - started : undefined,\n result: result ? omitBy(result, isNil) : undefined,\n isIntrospectionQuery: isIntrospection || undefined,\n ...omitBy(rest, isNil),\n },\n isNil,\n ),\n )\n}\n\nexport const logSubscriptionOperation = <TLogger extends Logger = Logger>({\n id,\n args,\n result,\n message,\n logLevel,\n resolveLogger,\n}: {\n id?: string\n args: ExecutionArgs\n result?: ExecutionResult\n message?: string\n logLevel?: keyof LoggerLogFunctions<TLogger>\n resolveLogger?: (context: GraphQLContext) => TLogger\n}) => {\n const logger = resolveLogger\n ? resolveLogger(args.contextValue as GraphQLContext)\n : ((args.contextValue as GraphQLContext).logger as TLogger)\n if (!logger) return\n\n const { operationName, variableValues, document } = args\n const { data, ...resultWithoutData } = result ?? {}\n\n logGraphQLOperation({\n message,\n type: OperationTypeNode.SUBSCRIPTION,\n subscriptionId: id,\n operationName,\n query: print(document),\n variables: variableValues,\n result: resultWithoutData,\n logger,\n logLevel,\n })\n}\n"],"names":[],"mappings":";;;;;;AA2EA;;;AAGG;AACI,MAAM,mBAAmB,GAAG,CAAkC,EACnE,OAAO,GAAG,mBAAmB,EAC7B,OAAO,EACP,IAAI,EACJ,aAAa,EACb,KAAK,EACL,SAAS,EACT,MAAM,EACN,MAAM,EACN,QAAQ,GAAG,MAAM,EACjB,GAAG,IAAI,EAC0B,KAAI;IACrC,MAAM,eAAe,GAAG,KAAK,IAAI,oBAAoB,CAAC,KAAK,CAAC;IAC5D,IAAI,UAAU,IAAI,eAAe;QAAE;AACnC,IAAA,MAAM,CAAC,QAAwB,CAAC,CAC9B,OAAO,EACP,MAAM,CACJ;QACE,IAAI;QACJ,aAAa;QACb,KAAK;QACL,SAAS,EAAE,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,SAAS,GAAG,SAAS;AACjF,QAAA,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,SAAS;AACpD,QAAA,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,SAAS;QAClD,oBAAoB,EAAE,eAAe,IAAI,SAAS;AAClD,QAAA,GAAG,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC;KACvB,EACD,KAAK,CACN,CACF;AACH;AAEO,MAAM,wBAAwB,GAAG,CAAkC,EACxE,EAAE,EACF,IAAI,EACJ,MAAM,EACN,OAAO,EACP,QAAQ,EACR,aAAa,GAQd,KAAI;IACH,MAAM,MAAM,GAAG;AACb,UAAE,aAAa,CAAC,IAAI,CAAC,YAA8B;AACnD,UAAI,IAAI,CAAC,YAA+B,CAAC,MAAkB;AAC7D,IAAA,IAAI,CAAC,MAAM;QAAE;IAEb,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,IAAI;IACxD,MAAM,EAAE,IAAI,EAAE,GAAG,iBAAiB,EAAE,GAAG,MAAM,IAAI,EAAE;AAEnD,IAAA,mBAAmB,CAAC;QAClB,OAAO;QACP,IAAI,EAAE,iBAAiB,CAAC,YAAY;AACpC,QAAA,cAAc,EAAE,EAAE;QAClB,aAAa;AACb,QAAA,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC;AACtB,QAAA,SAAS,EAAE,cAAc;AACzB,QAAA,MAAM,EAAE,iBAAiB;QACzB,MAAM;QACN,QAAQ;AACT,KAAA,CAAC;AACJ;;;;"}
|
package/operation.d.cts
ADDED
package/operation.d.mts
ADDED
package/operation.d.ts
ADDED
package/operation.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const anonOperationMatch = /(query|mutation|subscription)?(\s*)({)(\s*)(\w*)/im;
|
|
4
|
+
function extractAnonymousOperationName(source) {
|
|
5
|
+
const match = source.match(anonOperationMatch);
|
|
6
|
+
if (match && match[5]) {
|
|
7
|
+
const type = match[1] ?? 'query';
|
|
8
|
+
const name = match[5];
|
|
9
|
+
return `${type} ${name}`;
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const isIntrospectionQuery = (query) => query?.includes('__schema');
|
|
14
|
+
|
|
15
|
+
exports.extractAnonymousOperationName = extractAnonymousOperationName;
|
|
16
|
+
exports.isIntrospectionQuery = isIntrospectionQuery;
|
|
17
|
+
//# sourceMappingURL=operation.js.map
|
package/operation.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation.js","sources":["../src/operation.ts"],"sourcesContent":["const anonOperationMatch = /(query|mutation|subscription)?(\\s*)({)(\\s*)(\\w*)/im\n\nexport function extractAnonymousOperationName(source: string) {\n const match = source.match(anonOperationMatch)\n if (match && match[5]) {\n const type = match[1] ?? 'query'\n const name = match[5]\n return `${type} ${name}`\n }\n return undefined\n}\n\nexport const isIntrospectionQuery = (query?: string) => query?.includes('__schema')\n"],"names":[],"mappings":";;AAAA,MAAM,kBAAkB,GAAG,oDAAoD;AAEzE,SAAU,6BAA6B,CAAC,MAAc,EAAA;IAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC;AAC9C,IAAA,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE;QACrB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO;AAChC,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC;AACrB,QAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,IAAI,EAAE;IAC1B;AACA,IAAA,OAAO,SAAS;AAClB;AAEO,MAAM,oBAAoB,GAAG,CAAC,KAAc,KAAK,KAAK,EAAE,QAAQ,CAAC,UAAU;;;;;"}
|
package/operation.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const anonOperationMatch = /(query|mutation|subscription)?(\s*)({)(\s*)(\w*)/im;
|
|
2
|
+
function extractAnonymousOperationName(source) {
|
|
3
|
+
const match = source.match(anonOperationMatch);
|
|
4
|
+
if (match && match[5]) {
|
|
5
|
+
const type = match[1] ?? 'query';
|
|
6
|
+
const name = match[5];
|
|
7
|
+
return `${type} ${name}`;
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const isIntrospectionQuery = (query) => query?.includes('__schema');
|
|
12
|
+
|
|
13
|
+
export { extractAnonymousOperationName, isIntrospectionQuery };
|
|
14
|
+
//# sourceMappingURL=operation.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation.mjs","sources":["../src/operation.ts"],"sourcesContent":["const anonOperationMatch = /(query|mutation|subscription)?(\\s*)({)(\\s*)(\\w*)/im\n\nexport function extractAnonymousOperationName(source: string) {\n const match = source.match(anonOperationMatch)\n if (match && match[5]) {\n const type = match[1] ?? 'query'\n const name = match[5]\n return `${type} ${name}`\n }\n return undefined\n}\n\nexport const isIntrospectionQuery = (query?: string) => query?.includes('__schema')\n"],"names":[],"mappings":"AAAA,MAAM,kBAAkB,GAAG,oDAAoD;AAEzE,SAAU,6BAA6B,CAAC,MAAc,EAAA;IAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC;AAC9C,IAAA,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE;QACrB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO;AAChC,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC;AACrB,QAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,IAAI,EAAE;IAC1B;AACA,IAAA,OAAO,SAAS;AAClB;AAEO,MAAM,oBAAoB,GAAG,CAAC,KAAc,KAAK,KAAK,EAAE,QAAQ,CAAC,UAAU;;;;"}
|
package/package.json
CHANGED
package/shield.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GraphQLResolveInfo } from 'graphql';
|
|
2
2
|
import type { allow, and, chain, or, race } from 'graphql-shield';
|
|
3
3
|
import { rule, shield } from 'graphql-shield';
|
|
4
|
-
import type { Primitive } from './type-utils.
|
|
4
|
+
import type { Primitive } from './type-utils.mjs';
|
|
5
5
|
export type ShieldOptions = NonNullable<Parameters<typeof shield>[1]>;
|
|
6
6
|
type RuleCombinator = typeof chain | typeof race | typeof or | typeof and;
|
|
7
7
|
export type ShieldRule = ReturnType<(typeof allow)['getRules']>[number];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Logger } from '@makerx/node-common';
|
|
2
2
|
import type { IncomingMessage } from 'http';
|
|
3
|
-
import { User } from '../User.
|
|
4
|
-
import type { CreateRequestLogger, GraphQLContext, JwtPayload, RequestInfo, RequestInfoLogKey } from '../context.
|
|
3
|
+
import { User } from '../User.mjs';
|
|
4
|
+
import type { CreateRequestLogger, GraphQLContext, JwtPayload, RequestInfo, RequestInfoLogKey } from '../context.mjs';
|
|
5
5
|
export interface SubscriptionContextInput {
|
|
6
6
|
connectRequest: IncomingMessage;
|
|
7
7
|
connectionParams?: Readonly<Record<string, unknown>>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export * from './context.
|
|
2
|
-
export
|
|
3
|
-
export
|
|
1
|
+
export * from './context.mjs';
|
|
2
|
+
export { wrapSubscriptionIterator } from './subscription-iterator.mjs';
|
|
3
|
+
export * from './server.mjs';
|
|
4
|
+
export { extractTokenFromConnectionParams } from './utils.mjs';
|
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":";;;"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Logger } from '@makerx/node-common';
|
|
2
2
|
import type { GraphQLSchema } from 'graphql';
|
|
3
3
|
import type { Server } from 'http';
|
|
4
|
-
import type { GraphQLContext, JwtPayload } from '../context.
|
|
5
|
-
import type { CreateSubscriptionContext } from './context.
|
|
4
|
+
import type { GraphQLContext, JwtPayload } from '../context.mjs';
|
|
5
|
+
import type { CreateSubscriptionContext } from './context.mjs';
|
|
6
6
|
export declare function useSubscriptionsServer<TLogger extends Logger = Logger>({ schema, httpServer, createSubscriptionContext, logger, operationLogLevel, path, verifyToken, requireAuth, jwtClaimsToLog, resolveSubscriptionOperationLogger, }: {
|
|
7
7
|
schema: GraphQLSchema;
|
|
8
8
|
httpServer: Server;
|
|
@@ -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;;;;"}
|
package/utils.d.cts
CHANGED
package/utils.d.mts
CHANGED
package/utils.d.ts
CHANGED
package/utils.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const isIntrospectionQuery = (query) => query?.includes('__schema');
|
|
4
3
|
const isNil = (value) => value === null || value === undefined;
|
|
5
4
|
const isNotNil = (value) => !isNil(value);
|
|
6
5
|
|
|
7
|
-
exports.isIntrospectionQuery = isIntrospectionQuery;
|
|
8
6
|
exports.isNil = isNil;
|
|
9
7
|
exports.isNotNil = isNotNil;
|
|
10
8
|
//# sourceMappingURL=utils.js.map
|
package/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["export const
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["export const isNil = (value: unknown): value is null | undefined => value === null || value === undefined\nexport const isNotNil = <T>(value: T): value is Exclude<T, undefined | null> => !isNil(value)\n"],"names":[],"mappings":";;AAAO,MAAM,KAAK,GAAG,CAAC,KAAc,KAAgC,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK;AACzF,MAAM,QAAQ,GAAG,CAAI,KAAQ,KAA4C,CAAC,KAAK,CAAC,KAAK;;;;;"}
|
package/utils.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
const isIntrospectionQuery = (query) => query?.includes('__schema');
|
|
2
1
|
const isNil = (value) => value === null || value === undefined;
|
|
3
2
|
const isNotNil = (value) => !isNil(value);
|
|
4
3
|
|
|
5
|
-
export {
|
|
4
|
+
export { isNil, isNotNil };
|
|
6
5
|
//# sourceMappingURL=utils.mjs.map
|
package/utils.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","sources":["../src/utils.ts"],"sourcesContent":["export const
|
|
1
|
+
{"version":3,"file":"utils.mjs","sources":["../src/utils.ts"],"sourcesContent":["export const isNil = (value: unknown): value is null | undefined => value === null || value === undefined\nexport const isNotNil = <T>(value: T): value is Exclude<T, undefined | null> => !isNil(value)\n"],"names":[],"mappings":"AAAO,MAAM,KAAK,GAAG,CAAC,KAAc,KAAgC,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK;AACzF,MAAM,QAAQ,GAAG,CAAI,KAAQ,KAA4C,CAAC,KAAK,CAAC,KAAK;;;;"}
|