diff --git a/.changeset/flat-hounds-share.md b/.changeset/flat-hounds-share.md new file mode 100644 index 00000000..247a1802 --- /dev/null +++ b/.changeset/flat-hounds-share.md @@ -0,0 +1,5 @@ +--- +"@n1ru4l/in-memory-live-query-store": patch +--- + +Do not change the context inside the live query executor, and use WeakMap to map the actual context to the live query context diff --git a/packages/in-memory-live-query-store/src/InMemoryLiveQueryStore.spec.ts b/packages/in-memory-live-query-store/src/InMemoryLiveQueryStore.spec.ts index 91cddc37..98c9ee4b 100644 --- a/packages/in-memory-live-query-store/src/InMemoryLiveQueryStore.spec.ts +++ b/packages/in-memory-live-query-store/src/InMemoryLiveQueryStore.spec.ts @@ -964,3 +964,33 @@ it("index via custom compound index", async () => { isLive: true, }); }); + +it("keeps the context as-is", async () => { + const schema = createTestSchema(); + + const store = new InMemoryLiveQueryStore(); + const defaultExecuteFnSpy = jest.fn(defaultExecuteImplementation); + const execute = store.makeExecute(defaultExecuteFnSpy); + + const document = parse(/* GraphQL */ ` + query @live { + foo + } + `); + + const contextValue = { + foo: "bar", + }; + + const executionResult = execute({ document, schema, contextValue }); + assertAsyncIterable(executionResult); + let result = await executionResult.next(); + expect(result.value).toEqual({ + data: { + foo: "queried", + }, + isLive: true, + }); + + expect(defaultExecuteFnSpy.mock.calls[0][0].contextValue).toBe(contextValue); +}); diff --git a/packages/in-memory-live-query-store/src/InMemoryLiveQueryStore.ts b/packages/in-memory-live-query-store/src/InMemoryLiveQueryStore.ts index 1384a110..547d86b5 100644 --- a/packages/in-memory-live-query-store/src/InMemoryLiveQueryStore.ts +++ b/packages/in-memory-live-query-store/src/InMemoryLiveQueryStore.ts @@ -7,6 +7,9 @@ import { getOperationAST, defaultFieldResolver, TypeInfo, + DocumentNode, + visit, + Kind, } from "graphql"; import { mapSchema, MapperKind, isAsyncIterable } from "@graphql-tools/utils"; import { Repeater } from "@repeaterjs/repeater"; @@ -34,8 +37,6 @@ type AddResourceIdentifierFunction = ( values: string | Iterable | None ) => void; -const originalContextSymbol = Symbol("originalContext"); - type ArgumentName = string; type ArgumentValue = string; type IndexConfiguration = Array< @@ -43,12 +44,13 @@ type IndexConfiguration = Array< >; type LiveQueryContextValue = { - [originalContextSymbol]: unknown; collectResourceIdentifier: ResourceIdentifierCollectorFunction; addResourceIdentifier: AddResourceIdentifierFunction; indices: Map> | null; }; +const liveQueryContextMap = new WeakMap(); + const addResourceIdentifierCollectorToSchema = ( schema: GraphQLSchema, idFieldName: string @@ -61,24 +63,24 @@ const addResourceIdentifierCollectorToSchema = ( fieldName === idFieldName && isNonNullIDScalarType(fieldConfig.type); let resolve = fieldConfig.resolve ?? defaultFieldResolver; - newFieldConfig.resolve = (src, args, context, info) => { - if (!context || originalContextSymbol in context === false) { - return resolve(src, args, context, info); + newFieldConfig.resolve = (root, args, context, info) => { + if (context == null) { + return resolve(root, args, context, info); + } + + const liveQueryContext = liveQueryContextMap.get(context); + if (liveQueryContext == null) { + return resolve(root, args, context, info); } const liveQueyContext = context as LiveQueryContextValue; - const result = resolve( - src, - args, - liveQueyContext[originalContextSymbol], - info - ) as any; + const result = resolve(root, args, context, info) as any; const fieldConfigExtensions = fieldConfig.extensions as any | undefined; if (fieldConfigExtensions?.liveQuery?.collectResourceIdentifiers) { liveQueyContext.addResourceIdentifier( fieldConfigExtensions.liveQuery.collectResourceIdentifiers( - src, + root, args ) ); @@ -236,12 +238,13 @@ export class InMemoryLiveQueryStore { schema: inputSchema, document, rootValue, - contextValue, variableValues, operationName, ...additionalArguments } = args; + let contextValue = args.contextValue; + const operationNode = getOperationAST(document, operationName); const fallbackToDefaultExecute = () => @@ -351,19 +354,24 @@ export class InMemoryLiveQueryStore { } }; - const context: LiveQueryContextValue = { - [originalContextSymbol]: contextValue, + const liveQueryContext: LiveQueryContextValue = { collectResourceIdentifier, addResourceIdentifier, indices: liveQueryStore._indices, }; + if (contextValue == null) { + contextValue = liveQueryContext; + } + + liveQueryContextMap.set(contextValue, liveQueryContext); + const result = execute({ schema, document, operationName, rootValue, - contextValue: context, + contextValue, variableValues, ...additionalArguments, // TODO: remove this type-cast once GraphQL.js 16-defer-stream with fixed return type got released @@ -379,6 +387,7 @@ export class InMemoryLiveQueryStore { ); return; } + if (counter === executionCounter) { liveQueryStore._resourceTracker.track( scheduleRun,