Skip to content

Commit 7627000

Browse files
Feedback from open incremental PRs (#13010)
Co-authored-by: Lenz Weber-Tronic <[email protected]>
1 parent a617c10 commit 7627000

File tree

16 files changed

+930
-340
lines changed

16 files changed

+930
-340
lines changed

.api-reports/api-report-utilities_internal.api.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,28 @@ export namespace DeepMerger {
109109
// (undocumented)
110110
export type ArrayMergeStrategy = "truncate" | "combine";
111111
// (undocumented)
112+
export interface MergeOptions {
113+
// (undocumented)
114+
atPath?: ReadonlyArray<string | number>;
115+
}
116+
// (undocumented)
112117
export interface Options {
113118
// (undocumented)
114119
arrayMerge?: DeepMerger.ArrayMergeStrategy;
120+
// Warning: (ae-forgotten-export) The symbol "ReconcilerFunction" needs to be exported by the entry point index.d.ts
121+
//
122+
// (undocumented)
123+
reconciler?: ReconcilerFunction;
115124
}
116125
}
117126

118127
// @internal @deprecated (undocumented)
119-
export class DeepMerger<TContextArgs extends any[] = any[]> {
120-
// Warning: (ae-forgotten-export) The symbol "ReconcilerFunction" needs to be exported by the entry point index.d.ts
121-
constructor(reconciler?: ReconcilerFunction<TContextArgs>, options?: DeepMerger.Options);
128+
export class DeepMerger {
129+
constructor(options?: DeepMerger.Options);
122130
// (undocumented)
123131
isObject: typeof isNonNullObject;
124132
// (undocumented)
125-
merge(target: any, source: any, ...context: TContextArgs): any;
133+
merge(target: any, source: any, mergeOptions?: DeepMerger.MergeOptions): any;
126134
// (undocumented)
127135
shallowCopyForMerge<T>(value: T): T;
128136
}
@@ -400,7 +408,7 @@ export type Primitive = null | undefined | string | number | boolean | symbol |
400408
// Warning: (ae-incompatible-release-tags) The symbol "ReconcilerFunction" is marked as @public, but its signature references "DeepMerger" which is marked as @internal
401409
//
402410
// @public (undocumented)
403-
type ReconcilerFunction<TContextArgs extends any[]> = (this: DeepMerger<TContextArgs>, target: Record<string | number, any>, source: Record<string | number, any>, property: string | number, ...context: TContextArgs) => any;
411+
type ReconcilerFunction = (this: DeepMerger, target: Record<string | number, any>, source: Record<string | number, any>, property: string | number) => any;
404412

405413
// Warning: (ae-forgotten-export) The symbol "globalCaches" needs to be exported by the entry point index.d.ts
406414
//

.changeset/early-flies-thank.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/client": patch
3+
---
4+
5+
Fix an issue where errors parsed from incremental chunks in `ErrorLink` might throw when using the `GraphQL17Alpha9Handler`.

.changeset/tricky-cycles-buy.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/client": patch
3+
---
4+
5+
Handle `@stream` payloads that send multiple items in the same chunk when using the `Defer20220824Handler`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/client": patch
3+
---
4+
5+
Handle an edge case with the `Defer20220824Handler` where an error for a `@stream` item that bubbles to the `@stream` boundary (such as an item returning `null` for a non-null array item) would write items from future chunks to the wrong array index. In these cases, the `@stream` field is no longer processed and future updates to the field are ignored. This prevents runtime errors that TypeScript would otherwise not be able to catch.

src/cache/inmemory/entityStore.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,9 @@ export abstract class EntityStore implements NormalizedCache {
131131

132132
invariant(typeof dataId === "string", "store.merge expects a string ID");
133133

134-
const merged: StoreObject = new DeepMerger(storeObjectReconciler).merge(
135-
existing,
136-
incoming
137-
);
134+
const merged: StoreObject = new DeepMerger({
135+
reconciler: storeObjectReconciler,
136+
}).merge(existing, incoming);
138137

139138
// Even if merged === existing, existing may have come from a lower
140139
// layer, so we always need to set this.data[dataId] on this level.

src/cache/inmemory/readFromStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ export class StoreReader {
414414
context,
415415
}: ExecSubSelectedArrayOptions): ExecResult {
416416
let missing: MissingTree | undefined;
417-
let missingMerger = new DeepMerger<MissingTree[]>();
417+
let missingMerger = new DeepMerger();
418418

419419
function handleMissing<T>(childResult: ExecResult<T>, i: number): T {
420420
if (childResult.missing) {

src/core/ObservableQuery.ts

Lines changed: 41 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -894,20 +894,7 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`,
894894
{ shouldEmit: EmitBehavior.networkStatusChange }
895895
);
896896

897-
const { promise, operator } = getTrackingOperatorPromise(
898-
(value: QueryNotification.Value<TFetchData>) => {
899-
switch (value.kind) {
900-
case "E": {
901-
throw value.error;
902-
}
903-
case "N": {
904-
if (value.source !== "newNetworkStatus" && !value.value.loading) {
905-
return value.value;
906-
}
907-
}
908-
}
909-
}
910-
);
897+
const { promise, operator } = getTrackingOperatorPromise<TFetchData>();
911898

912899
const { observable } = this.queryManager.fetchObservableWithInfo(
913900
combinedOptions,
@@ -1055,12 +1042,12 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`,
10551042
.then((result) => toQueryResult(this.maskResult(result)))
10561043
.finally(() => {
10571044
subscription.unsubscribe();
1058-
if (isCached && !wasUpdated) {
1059-
finalize();
1045+
finalize();
10601046

1047+
if (isCached && !wasUpdated) {
10611048
const lastResult = this.getCurrentResult();
10621049

1063-
if (lastResult.networkStatus === NetworkStatus.streaming) {
1050+
if (lastResult.dataState === "streaming") {
10641051
pushNotification({
10651052
kind: "N",
10661053
source: "network",
@@ -1610,15 +1597,6 @@ Did you mean to call refetch(variables) instead of refetch({ variables })?`,
16101597

16111598
this.resubscribeCache();
16121599
const { promise, operator: promiseOperator } = getTrackingOperatorPromise(
1613-
(value: QueryNotification.Value<TData>) => {
1614-
switch (value.kind) {
1615-
case "E":
1616-
throw value.error;
1617-
case "N":
1618-
if (value.source !== "newNetworkStatus" && !value.value.loading)
1619-
return value.value;
1620-
}
1621-
},
16221600
// This default value should only be used when using a `fetchPolicy` of
16231601
// `standby` since that fetch policy completes without emitting a
16241602
// result. Since we are converting this to a QueryResult type, we
@@ -2039,46 +2017,49 @@ function isEqualQuery(
20392017
return !!(a && b && a.query === b.query && equal(a.variables, b.variables));
20402018
}
20412019

2042-
function getTrackingOperatorPromise<ObservedValue, ReturnValue = ObservedValue>(
2043-
filterMapCb: (value: ObservedValue) => ReturnValue | undefined,
2044-
defaultValue?: ReturnValue
2020+
function getTrackingOperatorPromise<TData>(
2021+
defaultValue?: ObservableQuery.Result<TData>
20452022
) {
20462023
let lastValue = defaultValue,
2047-
resolve: (value: ReturnValue) => void,
2024+
resolve: (value: ObservableQuery.Result<TData>) => void,
20482025
reject: (error: unknown) => void;
2049-
const promise = new Promise<ReturnValue>((res, rej) => {
2026+
const promise = new Promise<ObservableQuery.Result<TData>>((res, rej) => {
20502027
resolve = res;
20512028
reject = rej;
20522029
});
2053-
const operator: MonoTypeOperatorFunction<ObservedValue> = tap({
2054-
next(value) {
2055-
try {
2056-
const newValue = filterMapCb(value);
2057-
if (newValue !== undefined) {
2058-
lastValue = newValue;
2030+
const operator: MonoTypeOperatorFunction<QueryNotification.Value<TData>> =
2031+
tap({
2032+
next(value) {
2033+
if (value.kind === "E") {
2034+
return reject(value.error);
20592035
}
2060-
} catch (error) {
2061-
reject(error);
2062-
}
2063-
},
2064-
finalize: () => {
2065-
if (lastValue) {
2066-
resolve(lastValue);
2067-
} else {
2068-
const message = "The operation was aborted.";
2069-
const name = "AbortError";
2070-
reject(
2071-
typeof DOMException !== "undefined" ?
2072-
new DOMException(message, name)
2073-
// some environments do not have `DOMException`, e.g. node
2074-
// uses a normal `Error` with a `name` property instead: https://github.com/phryneas/node/blob/d0579b64f0f6b722f8e49bf8a471dd0d0604a21e/lib/internal/errors.js#L964
2075-
// error.code is a legacy property that is not used anymore,
2076-
// and also inconsistent across environments (in supporting
2077-
// browsers it is `20`, in node `'ABORT_ERR'`) so we omit that.
2078-
: Object.assign(new Error(message), { name })
2079-
);
2080-
}
2081-
},
2082-
});
2036+
2037+
if (
2038+
value.kind === "N" &&
2039+
value.source !== "newNetworkStatus" &&
2040+
!value.value.loading
2041+
) {
2042+
lastValue = value.value;
2043+
}
2044+
},
2045+
finalize: () => {
2046+
if (lastValue) {
2047+
resolve(lastValue);
2048+
} else {
2049+
const message = "The operation was aborted.";
2050+
const name = "AbortError";
2051+
reject(
2052+
typeof DOMException !== "undefined" ?
2053+
new DOMException(message, name)
2054+
// some environments do not have `DOMException`, e.g. node
2055+
// uses a normal `Error` with a `name` property instead: https://github.com/phryneas/node/blob/d0579b64f0f6b722f8e49bf8a471dd0d0604a21e/lib/internal/errors.js#L964
2056+
// error.code is a legacy property that is not used anymore,
2057+
// and also inconsistent across environments (in supporting
2058+
// browsers it is `20`, in node `'ABORT_ERR'`) so we omit that.
2059+
: Object.assign(new Error(message), { name })
2060+
);
2061+
}
2062+
},
2063+
});
20832064
return { promise, operator };
20842065
}

0 commit comments

Comments
 (0)