@osdk/client 2.8.0-beta.3 → 2.8.0-beta.4
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/CHANGELOG.md +13 -0
- package/build/browser/observable/internal/AbstractHelper.js +19 -1
- package/build/browser/observable/internal/AbstractHelper.js.map +1 -1
- package/build/browser/observable/internal/ListQueryView.js +115 -0
- package/build/browser/observable/internal/ListQueryView.js.map +1 -0
- package/build/browser/observable/internal/Query.js +7 -5
- package/build/browser/observable/internal/Query.js.map +1 -1
- package/build/browser/observable/internal/Store.test.js +83 -1
- package/build/browser/observable/internal/Store.test.js.map +1 -1
- package/build/browser/observable/internal/base-list/BaseListQuery.js +72 -0
- package/build/browser/observable/internal/base-list/BaseListQuery.js.map +1 -1
- package/build/browser/observable/internal/links/SpecificLinkQuery.js +1 -1
- package/build/browser/observable/internal/links/SpecificLinkQuery.js.map +1 -1
- package/build/browser/observable/internal/list/ListQuery.js +2 -2
- package/build/browser/observable/internal/list/ListQuery.js.map +1 -1
- package/build/browser/observable/internal/objectset/ObjectSetQuery.js +1 -1
- package/build/browser/observable/internal/objectset/ObjectSetQuery.js.map +1 -1
- package/build/browser/util/UserAgent.js +2 -2
- package/build/cjs/{chunk-W5MGPHBY.cjs → chunk-RKC3366I.cjs} +4 -4
- package/build/cjs/{chunk-W5MGPHBY.cjs.map → chunk-RKC3366I.cjs.map} +1 -1
- package/build/cjs/index.cjs +7 -7
- package/build/cjs/public/unstable-do-not-use.cjs +160 -26
- package/build/cjs/public/unstable-do-not-use.cjs.map +1 -1
- package/build/esm/observable/internal/AbstractHelper.js +19 -1
- package/build/esm/observable/internal/AbstractHelper.js.map +1 -1
- package/build/esm/observable/internal/ListQueryView.js +115 -0
- package/build/esm/observable/internal/ListQueryView.js.map +1 -0
- package/build/esm/observable/internal/Query.js +7 -5
- package/build/esm/observable/internal/Query.js.map +1 -1
- package/build/esm/observable/internal/Store.test.js +83 -1
- package/build/esm/observable/internal/Store.test.js.map +1 -1
- package/build/esm/observable/internal/base-list/BaseListQuery.js +72 -0
- package/build/esm/observable/internal/base-list/BaseListQuery.js.map +1 -1
- package/build/esm/observable/internal/links/SpecificLinkQuery.js +1 -1
- package/build/esm/observable/internal/links/SpecificLinkQuery.js.map +1 -1
- package/build/esm/observable/internal/list/ListQuery.js +2 -2
- package/build/esm/observable/internal/list/ListQuery.js.map +1 -1
- package/build/esm/observable/internal/objectset/ObjectSetQuery.js +1 -1
- package/build/esm/observable/internal/objectset/ObjectSetQuery.js.map +1 -1
- package/build/esm/util/UserAgent.js +2 -2
- package/build/types/observable/internal/AbstractHelper.d.ts +1 -1
- package/build/types/observable/internal/AbstractHelper.d.ts.map +1 -1
- package/build/types/observable/internal/ListQueryView.d.ts +21 -0
- package/build/types/observable/internal/ListQueryView.d.ts.map +1 -0
- package/build/types/observable/internal/Query.d.ts.map +1 -1
- package/build/types/observable/internal/Store.test.d.ts.map +1 -1
- package/build/types/observable/internal/base-list/BaseListQuery.d.ts +46 -2
- package/build/types/observable/internal/base-list/BaseListQuery.d.ts.map +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @osdk/client
|
|
2
2
|
|
|
3
|
+
## 2.8.0-beta.4
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 73e617e: expose dedupeInterval on useLinks and fix forced revalidation bypassing dedupeInterval
|
|
8
|
+
- 5848e3c: add a new View abstraction layer to fix a pageSize caching bug where multiple subscribers with different pageSize values would share cached data incorrectly
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- @osdk/api@2.8.0-beta.4
|
|
13
|
+
- @osdk/client.unstable@2.8.0-beta.4
|
|
14
|
+
- @osdk/generator-converters@2.8.0-beta.4
|
|
15
|
+
|
|
3
16
|
## 2.8.0-beta.3
|
|
4
17
|
|
|
5
18
|
### Minor Changes
|
|
@@ -14,7 +14,20 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { ListQueryView } from "./ListQueryView.js";
|
|
17
18
|
import { QuerySubscription } from "./QuerySubscription.js";
|
|
19
|
+
/**
|
|
20
|
+
* Check if a query supports view-based pagination (has the required methods).
|
|
21
|
+
* Generic over PAYLOAD to preserve type information when the guard passes.
|
|
22
|
+
*/
|
|
23
|
+
function supportsViews(query) {
|
|
24
|
+
return query != null && typeof query.registerFetchPageSize === "function" && typeof query.getLoadedCount === "function" && typeof query.hasMorePages === "function" && typeof query.notifySubscribers === "function" && typeof query.fetchMore === "function";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Options that may include list-specific pagination settings.
|
|
29
|
+
*/
|
|
30
|
+
|
|
18
31
|
export class AbstractHelper {
|
|
19
32
|
constructor(store, cacheKeys) {
|
|
20
33
|
this.store = store;
|
|
@@ -41,7 +54,12 @@ export class AbstractHelper {
|
|
|
41
54
|
}
|
|
42
55
|
});
|
|
43
56
|
}
|
|
44
|
-
|
|
57
|
+
|
|
58
|
+
// For queries that support views (list-like queries), wrap with ListQueryView
|
|
59
|
+
// to handle per-subscriber view data such as pageSize
|
|
60
|
+
const listOptions = options;
|
|
61
|
+
const useView = supportsViews(query) && (listOptions.pageSize !== undefined || listOptions.autoFetchMore !== undefined);
|
|
62
|
+
const sub = useView ? new ListQueryView(query, listOptions.pageSize ?? 100, listOptions.autoFetchMore).subscribe(subFn) : query.subscribe(subFn);
|
|
45
63
|
const querySub = new QuerySubscription(query, sub);
|
|
46
64
|
query.registerSubscriptionDedupeInterval(querySub.subscriptionId, options.dedupeInterval);
|
|
47
65
|
sub.add(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AbstractHelper.js","names":["QuerySubscription","AbstractHelper","constructor","store","cacheKeys","observe","options","subFn","
|
|
1
|
+
{"version":3,"file":"AbstractHelper.js","names":["ListQueryView","QuerySubscription","supportsViews","query","registerFetchPageSize","getLoadedCount","hasMorePages","notifySubscribers","fetchMore","AbstractHelper","constructor","store","cacheKeys","observe","options","subFn","getQuery","_subscribe","retain","cacheKey","mode","revalidate","catch","e","error","logger","listOptions","useView","pageSize","undefined","autoFetchMore","sub","subscribe","querySub","registerSubscriptionDedupeInterval","subscriptionId","dedupeInterval","add","unregisterSubscriptionDedupeInterval","release"],"sources":["AbstractHelper.ts"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n CommonObserveOptions,\n Observer,\n} from \"../ObservableClient/common.js\";\nimport type { BaseListPayloadShape } from \"./base-list/BaseListQuery.js\";\nimport type { CacheKeys } from \"./CacheKeys.js\";\nimport type { KnownCacheKey } from \"./KnownCacheKey.js\";\nimport { ListQueryView, type ListQueryViewTarget } from \"./ListQueryView.js\";\nimport type { Query } from \"./Query.js\";\nimport { QuerySubscription } from \"./QuerySubscription.js\";\nimport type { Store } from \"./Store.js\";\n\n/**\n * Check if a query supports view-based pagination (has the required methods).\n * Generic over PAYLOAD to preserve type information when the guard passes.\n */\nfunction supportsViews<PAYLOAD extends BaseListPayloadShape>(\n query: unknown,\n): query is ListQueryViewTarget<PAYLOAD> {\n return (\n query != null\n && typeof (query as ListQueryViewTarget<PAYLOAD>).registerFetchPageSize\n === \"function\"\n && typeof (query as ListQueryViewTarget<PAYLOAD>).getLoadedCount\n === \"function\"\n && typeof (query as ListQueryViewTarget<PAYLOAD>).hasMorePages\n === \"function\"\n && typeof (query as ListQueryViewTarget<PAYLOAD>).notifySubscribers\n === \"function\"\n && typeof (query as ListQueryViewTarget<PAYLOAD>).fetchMore === \"function\"\n );\n}\n\n/**\n * Options that may include list-specific pagination settings.\n */\ninterface ListObserveOptions {\n pageSize?: number;\n autoFetchMore?: boolean | number;\n}\n\nexport abstract class AbstractHelper<\n TQuery extends Query<KnownCacheKey, any, CommonObserveOptions>,\n TObserveOptions extends CommonObserveOptions,\n> {\n protected readonly store: Store;\n protected readonly cacheKeys: CacheKeys<KnownCacheKey>;\n\n constructor(store: Store, cacheKeys: CacheKeys<KnownCacheKey>) {\n this.store = store;\n this.cacheKeys = cacheKeys;\n }\n\n observe(\n options: TObserveOptions,\n subFn: Observer<\n TQuery extends Query<any, infer PAYLOAD, any> ? PAYLOAD : never\n >,\n ): QuerySubscription<TQuery> {\n const query = this.getQuery(options);\n return this._subscribe(query, options, subFn);\n }\n\n abstract getQuery(options: TObserveOptions): TQuery;\n\n protected _subscribe<\n PAYLOAD extends (TQuery extends Query<any, infer P, any> ? P : never),\n >(\n query: TQuery,\n options: TObserveOptions,\n subFn: Observer<PAYLOAD>,\n ): QuerySubscription<TQuery> {\n // the ListQuery represents the shared state of the list\n this.store.cacheKeys.retain(query.cacheKey);\n\n if (options.mode !== \"offline\") {\n query.revalidate(options.mode === \"force\").catch((e: unknown) => {\n subFn.error(e);\n\n // we don't want observeObject() to return a promise,\n // so we settle for logging an error here instead of\n // dropping it on the floor.\n if (this.store.logger) {\n this.store.logger.error(\"Unhandled error in observeObject\", e);\n } else {\n throw e;\n }\n });\n }\n\n // For queries that support views (list-like queries), wrap with ListQueryView\n // to handle per-subscriber view data such as pageSize\n const listOptions = options as ListObserveOptions;\n const useView = supportsViews<PAYLOAD & BaseListPayloadShape>(query)\n && (listOptions.pageSize !== undefined\n || listOptions.autoFetchMore !== undefined);\n\n const sub = useView\n ? new ListQueryView<PAYLOAD & BaseListPayloadShape>(\n query,\n listOptions.pageSize ?? 100,\n listOptions.autoFetchMore,\n ).subscribe(subFn as Observer<PAYLOAD & BaseListPayloadShape>)\n : query.subscribe(subFn);\n\n const querySub = new QuerySubscription(query, sub);\n\n query.registerSubscriptionDedupeInterval(\n querySub.subscriptionId,\n options.dedupeInterval,\n );\n\n sub.add(() => {\n query.unregisterSubscriptionDedupeInterval(querySub.subscriptionId);\n this.store.cacheKeys.release(query.cacheKey);\n });\n\n return querySub;\n }\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AASA,SAASA,aAAa,QAAkC,oBAAoB;AAE5E,SAASC,iBAAiB,QAAQ,wBAAwB;AAG1D;AACA;AACA;AACA;AACA,SAASC,aAAaA,CACpBC,KAAc,EACyB;EACvC,OACEA,KAAK,IAAI,IAAI,IACV,OAAQA,KAAK,CAAkCC,qBAAqB,KACjE,UAAU,IACb,OAAQD,KAAK,CAAkCE,cAAc,KAC1D,UAAU,IACb,OAAQF,KAAK,CAAkCG,YAAY,KACxD,UAAU,IACb,OAAQH,KAAK,CAAkCI,iBAAiB,KAC7D,UAAU,IACb,OAAQJ,KAAK,CAAkCK,SAAS,KAAK,UAAU;AAE9E;;AAEA;AACA;AACA;;AAMA,OAAO,MAAeC,cAAc,CAGlC;EAIAC,WAAWA,CAACC,KAAY,EAAEC,SAAmC,EAAE;IAC7D,IAAI,CAACD,KAAK,GAAGA,KAAK;IAClB,IAAI,CAACC,SAAS,GAAGA,SAAS;EAC5B;EAEAC,OAAOA,CACLC,OAAwB,EACxBC,KAEC,EAC0B;IAC3B,MAAMZ,KAAK,GAAG,IAAI,CAACa,QAAQ,CAACF,OAAO,CAAC;IACpC,OAAO,IAAI,CAACG,UAAU,CAACd,KAAK,EAAEW,OAAO,EAAEC,KAAK,CAAC;EAC/C;EAIUE,UAAUA,CAGlBd,KAAa,EACbW,OAAwB,EACxBC,KAAwB,EACG;IAC3B;IACA,IAAI,CAACJ,KAAK,CAACC,SAAS,CAACM,MAAM,CAACf,KAAK,CAACgB,QAAQ,CAAC;IAE3C,IAAIL,OAAO,CAACM,IAAI,KAAK,SAAS,EAAE;MAC9BjB,KAAK,CAACkB,UAAU,CAACP,OAAO,CAACM,IAAI,KAAK,OAAO,CAAC,CAACE,KAAK,CAAEC,CAAU,IAAK;QAC/DR,KAAK,CAACS,KAAK,CAACD,CAAC,CAAC;;QAEd;QACA;QACA;QACA,IAAI,IAAI,CAACZ,KAAK,CAACc,MAAM,EAAE;UACrB,IAAI,CAACd,KAAK,CAACc,MAAM,CAACD,KAAK,CAAC,kCAAkC,EAAED,CAAC,CAAC;QAChE,CAAC,MAAM;UACL,MAAMA,CAAC;QACT;MACF,CAAC,CAAC;IACJ;;IAEA;IACA;IACA,MAAMG,WAAW,GAAGZ,OAA6B;IACjD,MAAMa,OAAO,GAAGzB,aAAa,CAAiCC,KAAK,CAAC,KAC9DuB,WAAW,CAACE,QAAQ,KAAKC,SAAS,IACjCH,WAAW,CAACI,aAAa,KAAKD,SAAS,CAAC;IAE/C,MAAME,GAAG,GAAGJ,OAAO,GACf,IAAI3B,aAAa,CACjBG,KAAK,EACLuB,WAAW,CAACE,QAAQ,IAAI,GAAG,EAC3BF,WAAW,CAACI,aACd,CAAC,CAACE,SAAS,CAACjB,KAAiD,CAAC,GAC5DZ,KAAK,CAAC6B,SAAS,CAACjB,KAAK,CAAC;IAE1B,MAAMkB,QAAQ,GAAG,IAAIhC,iBAAiB,CAACE,KAAK,EAAE4B,GAAG,CAAC;IAElD5B,KAAK,CAAC+B,kCAAkC,CACtCD,QAAQ,CAACE,cAAc,EACvBrB,OAAO,CAACsB,cACV,CAAC;IAEDL,GAAG,CAACM,GAAG,CAAC,MAAM;MACZlC,KAAK,CAACmC,oCAAoC,CAACL,QAAQ,CAACE,cAAc,CAAC;MACnE,IAAI,CAACxB,KAAK,CAACC,SAAS,CAAC2B,OAAO,CAACpC,KAAK,CAACgB,QAAQ,CAAC;IAC9C,CAAC,CAAC;IAEF,OAAOc,QAAQ;EACjB;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Palantir Technologies, Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Interface for the query methods that ListQueryView needs.
|
|
19
|
+
* This allows ListQueryView to work with any query that supports these operations.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A view into a shared ListQuery cache.
|
|
24
|
+
*
|
|
25
|
+
* Each subscriber gets their own View instance that tracks how much of the
|
|
26
|
+
* shared cache they want to see (viewLimit). This allows multiple components
|
|
27
|
+
* with different pageSize requirements to share the same underlying cache.
|
|
28
|
+
*
|
|
29
|
+
* The View:
|
|
30
|
+
* - Slices the shared data to the subscriber's viewLimit
|
|
31
|
+
* - Provides a fetchMore that increments viewLimit and fetches if needed
|
|
32
|
+
* - Reports hasMore based on both local viewLimit and server pagination
|
|
33
|
+
*/
|
|
34
|
+
let viewIdCounter = 0;
|
|
35
|
+
export class ListQueryView {
|
|
36
|
+
#query;
|
|
37
|
+
#viewLimit;
|
|
38
|
+
#pageSize;
|
|
39
|
+
#viewId;
|
|
40
|
+
#fetchMore;
|
|
41
|
+
#pendingFetchMore;
|
|
42
|
+
#lastPayload;
|
|
43
|
+
#observer;
|
|
44
|
+
constructor(query, pageSize, autoFetchMore) {
|
|
45
|
+
this.#query = query;
|
|
46
|
+
this.#pageSize = pageSize;
|
|
47
|
+
this.#viewId = `view_${++viewIdCounter}`;
|
|
48
|
+
|
|
49
|
+
// With autoFetchMore, subscriber sees all loaded data (no view limit)
|
|
50
|
+
// Otherwise, limit to their pageSize
|
|
51
|
+
|
|
52
|
+
this.#viewLimit = autoFetchMore === true || typeof autoFetchMore === "number" && autoFetchMore > 0 ? Number.MAX_SAFE_INTEGER : pageSize;
|
|
53
|
+
|
|
54
|
+
// Memoize fetchMore to maintain stable function identity
|
|
55
|
+
this.#fetchMore = this.#createFetchMore();
|
|
56
|
+
|
|
57
|
+
// Tell the query to fetch with at least this pageSize
|
|
58
|
+
query.registerFetchPageSize(this.#viewId, pageSize);
|
|
59
|
+
}
|
|
60
|
+
subscribe(observer) {
|
|
61
|
+
this.#observer = observer;
|
|
62
|
+
const sub = this.#query.subscribe({
|
|
63
|
+
next: payload => {
|
|
64
|
+
this.#lastPayload = payload;
|
|
65
|
+
observer.next?.(this.#transformPayload(payload));
|
|
66
|
+
},
|
|
67
|
+
error: err => observer.error?.(err),
|
|
68
|
+
complete: () => observer.complete?.()
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Cleanup: unregister pageSize when subscriber unsubscribes
|
|
72
|
+
sub.add(() => {
|
|
73
|
+
this.#query.unregisterFetchPageSize(this.#viewId);
|
|
74
|
+
this.#observer = undefined;
|
|
75
|
+
this.#lastPayload = undefined;
|
|
76
|
+
});
|
|
77
|
+
return sub;
|
|
78
|
+
}
|
|
79
|
+
#reEmitWithNewViewLimit() {
|
|
80
|
+
if (this.#lastPayload && this.#observer) {
|
|
81
|
+
this.#observer.next?.(this.#transformPayload(this.#lastPayload));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
#transformPayload(payload) {
|
|
85
|
+
const loadedCount = payload.resolvedList.length;
|
|
86
|
+
return {
|
|
87
|
+
...payload,
|
|
88
|
+
resolvedList: payload.resolvedList.slice(0, this.#viewLimit),
|
|
89
|
+
hasMore: this.#viewLimit < loadedCount || payload.hasMore,
|
|
90
|
+
fetchMore: this.#fetchMore
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
#createFetchMore() {
|
|
94
|
+
return () => {
|
|
95
|
+
if (this.#pendingFetchMore) {
|
|
96
|
+
return this.#pendingFetchMore;
|
|
97
|
+
}
|
|
98
|
+
this.#viewLimit += this.#pageSize;
|
|
99
|
+
const loadedCount = this.#query.getLoadedCount();
|
|
100
|
+
const hasMoreOnServer = this.#query.hasMorePages();
|
|
101
|
+
if (this.#viewLimit > loadedCount && hasMoreOnServer) {
|
|
102
|
+
// Need to fetch more data from server
|
|
103
|
+
this.#pendingFetchMore = this.#query.fetchMore().finally(() => {
|
|
104
|
+
this.#pendingFetchMore = undefined;
|
|
105
|
+
});
|
|
106
|
+
return this.#pendingFetchMore;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// We have enough data in cache, just re-emit with new viewLimit (sync)
|
|
110
|
+
this.#reEmitWithNewViewLimit();
|
|
111
|
+
return Promise.resolve();
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=ListQueryView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListQueryView.js","names":["viewIdCounter","ListQueryView","query","viewLimit","pageSize","viewId","fetchMore","pendingFetchMore","lastPayload","observer","constructor","autoFetchMore","Number","MAX_SAFE_INTEGER","createFetchMore","registerFetchPageSize","subscribe","sub","next","payload","transformPayload","error","err","complete","add","unregisterFetchPageSize","undefined","reEmitWithNewViewLimit","#reEmitWithNewViewLimit","#transformPayload","loadedCount","resolvedList","length","slice","hasMore","#createFetchMore","getLoadedCount","hasMoreOnServer","hasMorePages","finally","Promise","resolve"],"sources":["ListQueryView.ts"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Subscription } from \"rxjs\";\nimport type { Observer } from \"../ObservableClient/common.js\";\nimport type { BaseListPayloadShape } from \"./base-list/BaseListQuery.js\";\n\n/**\n * Interface for the query methods that ListQueryView needs.\n * This allows ListQueryView to work with any query that supports these operations.\n */\nexport interface ListQueryViewTarget<PAYLOAD extends BaseListPayloadShape> {\n subscribe(observer: Observer<PAYLOAD>): Subscription;\n registerFetchPageSize(viewId: string, pageSize: number): void;\n unregisterFetchPageSize(viewId: string): void;\n getLoadedCount(): number;\n hasMorePages(): boolean;\n notifySubscribers(): void;\n fetchMore(): Promise<void>;\n}\n\n/**\n * A view into a shared ListQuery cache.\n *\n * Each subscriber gets their own View instance that tracks how much of the\n * shared cache they want to see (viewLimit). This allows multiple components\n * with different pageSize requirements to share the same underlying cache.\n *\n * The View:\n * - Slices the shared data to the subscriber's viewLimit\n * - Provides a fetchMore that increments viewLimit and fetches if needed\n * - Reports hasMore based on both local viewLimit and server pagination\n */\nlet viewIdCounter = 0;\n\nexport class ListQueryView<PAYLOAD extends BaseListPayloadShape> {\n #query: ListQueryViewTarget<PAYLOAD>;\n #viewLimit: number;\n #pageSize: number;\n #viewId: string;\n #fetchMore: () => Promise<void>;\n #pendingFetchMore: Promise<void> | undefined;\n #lastPayload: PAYLOAD | undefined;\n #observer: Observer<PAYLOAD> | undefined;\n\n constructor(\n query: ListQueryViewTarget<PAYLOAD>,\n pageSize: number,\n autoFetchMore?: boolean | number,\n ) {\n this.#query = query;\n this.#pageSize = pageSize;\n this.#viewId = `view_${++viewIdCounter}`;\n\n // With autoFetchMore, subscriber sees all loaded data (no view limit)\n // Otherwise, limit to their pageSize\n const hasAutoFetch = autoFetchMore === true\n || (typeof autoFetchMore === \"number\" && autoFetchMore > 0);\n this.#viewLimit = hasAutoFetch ? Number.MAX_SAFE_INTEGER : pageSize;\n\n // Memoize fetchMore to maintain stable function identity\n this.#fetchMore = this.#createFetchMore();\n\n // Tell the query to fetch with at least this pageSize\n query.registerFetchPageSize(this.#viewId, pageSize);\n }\n\n subscribe(observer: Observer<PAYLOAD>): Subscription {\n this.#observer = observer;\n const sub = this.#query.subscribe({\n next: (payload) => {\n this.#lastPayload = payload;\n observer.next?.(this.#transformPayload(payload));\n },\n error: (err) => observer.error?.(err),\n complete: () => observer.complete?.(),\n });\n\n // Cleanup: unregister pageSize when subscriber unsubscribes\n sub.add(() => {\n this.#query.unregisterFetchPageSize(this.#viewId);\n this.#observer = undefined;\n this.#lastPayload = undefined;\n });\n\n return sub;\n }\n\n #reEmitWithNewViewLimit(): void {\n if (this.#lastPayload && this.#observer) {\n this.#observer.next?.(this.#transformPayload(this.#lastPayload));\n }\n }\n\n #transformPayload(payload: PAYLOAD): PAYLOAD {\n const loadedCount = payload.resolvedList.length;\n\n return {\n ...payload,\n resolvedList: payload.resolvedList.slice(0, this.#viewLimit),\n hasMore: this.#viewLimit < loadedCount || payload.hasMore,\n fetchMore: this.#fetchMore,\n };\n }\n\n #createFetchMore(): () => Promise<void> {\n return () => {\n if (this.#pendingFetchMore) {\n return this.#pendingFetchMore;\n }\n\n this.#viewLimit += this.#pageSize;\n\n const loadedCount = this.#query.getLoadedCount();\n const hasMoreOnServer = this.#query.hasMorePages();\n\n if (this.#viewLimit > loadedCount && hasMoreOnServer) {\n // Need to fetch more data from server\n this.#pendingFetchMore = this.#query.fetchMore().finally(() => {\n this.#pendingFetchMore = undefined;\n });\n return this.#pendingFetchMore;\n }\n\n // We have enough data in cache, just re-emit with new viewLimit (sync)\n this.#reEmitWithNewViewLimit();\n return Promise.resolve();\n };\n }\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAMA;AACA;AACA;AACA;;AAWA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAIA,aAAa,GAAG,CAAC;AAErB,OAAO,MAAMC,aAAa,CAAuC;EAC/D,CAACC,KAAK;EACN,CAACC,SAAS;EACV,CAACC,QAAQ;EACT,CAACC,MAAM;EACP,CAACC,SAAS;EACV,CAACC,gBAAgB;EACjB,CAACC,WAAW;EACZ,CAACC,QAAQ;EAETC,WAAWA,CACTR,KAAmC,EACnCE,QAAgB,EAChBO,aAAgC,EAChC;IACA,IAAI,CAAC,CAACT,KAAK,GAAGA,KAAK;IACnB,IAAI,CAAC,CAACE,QAAQ,GAAGA,QAAQ;IACzB,IAAI,CAAC,CAACC,MAAM,GAAG,QAAQ,EAAEL,aAAa,EAAE;;IAExC;IACA;;IAGA,IAAI,CAAC,CAACG,SAAS,GAFMQ,aAAa,KAAK,IAAI,IACrC,OAAOA,aAAa,KAAK,QAAQ,IAAIA,aAAa,GAAG,CAAE,GAC5BC,MAAM,CAACC,gBAAgB,GAAGT,QAAQ;;IAEnE;IACA,IAAI,CAAC,CAACE,SAAS,GAAG,IAAI,CAAC,CAACQ,eAAe,CAAC,CAAC;;IAEzC;IACAZ,KAAK,CAACa,qBAAqB,CAAC,IAAI,CAAC,CAACV,MAAM,EAAED,QAAQ,CAAC;EACrD;EAEAY,SAASA,CAACP,QAA2B,EAAgB;IACnD,IAAI,CAAC,CAACA,QAAQ,GAAGA,QAAQ;IACzB,MAAMQ,GAAG,GAAG,IAAI,CAAC,CAACf,KAAK,CAACc,SAAS,CAAC;MAChCE,IAAI,EAAGC,OAAO,IAAK;QACjB,IAAI,CAAC,CAACX,WAAW,GAAGW,OAAO;QAC3BV,QAAQ,CAACS,IAAI,GAAG,IAAI,CAAC,CAACE,gBAAgB,CAACD,OAAO,CAAC,CAAC;MAClD,CAAC;MACDE,KAAK,EAAGC,GAAG,IAAKb,QAAQ,CAACY,KAAK,GAAGC,GAAG,CAAC;MACrCC,QAAQ,EAAEA,CAAA,KAAMd,QAAQ,CAACc,QAAQ,GAAG;IACtC,CAAC,CAAC;;IAEF;IACAN,GAAG,CAACO,GAAG,CAAC,MAAM;MACZ,IAAI,CAAC,CAACtB,KAAK,CAACuB,uBAAuB,CAAC,IAAI,CAAC,CAACpB,MAAM,CAAC;MACjD,IAAI,CAAC,CAACI,QAAQ,GAAGiB,SAAS;MAC1B,IAAI,CAAC,CAAClB,WAAW,GAAGkB,SAAS;IAC/B,CAAC,CAAC;IAEF,OAAOT,GAAG;EACZ;EAEA,CAACU,sBAAsBC,CAAA,EAAS;IAC9B,IAAI,IAAI,CAAC,CAACpB,WAAW,IAAI,IAAI,CAAC,CAACC,QAAQ,EAAE;MACvC,IAAI,CAAC,CAACA,QAAQ,CAACS,IAAI,GAAG,IAAI,CAAC,CAACE,gBAAgB,CAAC,IAAI,CAAC,CAACZ,WAAW,CAAC,CAAC;IAClE;EACF;EAEA,CAACY,gBAAgBS,CAACV,OAAgB,EAAW;IAC3C,MAAMW,WAAW,GAAGX,OAAO,CAACY,YAAY,CAACC,MAAM;IAE/C,OAAO;MACL,GAAGb,OAAO;MACVY,YAAY,EAAEZ,OAAO,CAACY,YAAY,CAACE,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC9B,SAAS,CAAC;MAC5D+B,OAAO,EAAE,IAAI,CAAC,CAAC/B,SAAS,GAAG2B,WAAW,IAAIX,OAAO,CAACe,OAAO;MACzD5B,SAAS,EAAE,IAAI,CAAC,CAACA;IACnB,CAAC;EACH;EAEA,CAACQ,eAAeqB,CAAA,EAAwB;IACtC,OAAO,MAAM;MACX,IAAI,IAAI,CAAC,CAAC5B,gBAAgB,EAAE;QAC1B,OAAO,IAAI,CAAC,CAACA,gBAAgB;MAC/B;MAEA,IAAI,CAAC,CAACJ,SAAS,IAAI,IAAI,CAAC,CAACC,QAAQ;MAEjC,MAAM0B,WAAW,GAAG,IAAI,CAAC,CAAC5B,KAAK,CAACkC,cAAc,CAAC,CAAC;MAChD,MAAMC,eAAe,GAAG,IAAI,CAAC,CAACnC,KAAK,CAACoC,YAAY,CAAC,CAAC;MAElD,IAAI,IAAI,CAAC,CAACnC,SAAS,GAAG2B,WAAW,IAAIO,eAAe,EAAE;QACpD;QACA,IAAI,CAAC,CAAC9B,gBAAgB,GAAG,IAAI,CAAC,CAACL,KAAK,CAACI,SAAS,CAAC,CAAC,CAACiC,OAAO,CAAC,MAAM;UAC7D,IAAI,CAAC,CAAChC,gBAAgB,GAAGmB,SAAS;QACpC,CAAC,CAAC;QACF,OAAO,IAAI,CAAC,CAACnB,gBAAgB;MAC/B;;MAEA;MACA,IAAI,CAAC,CAACoB,sBAAsB,CAAC,CAAC;MAC9B,OAAOa,OAAO,CAACC,OAAO,CAAC,CAAC;IAC1B,CAAC;EACH;AACF","ignoreList":[]}
|
|
@@ -111,12 +111,14 @@ export class Query {
|
|
|
111
111
|
await this.pendingFetch;
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
114
|
+
if (!force) {
|
|
115
|
+
const minDedupeInterval = this.getMinimumDedupeInterval();
|
|
116
|
+
if (minDedupeInterval > 0 && this.lastFetchStarted != null && Date.now() - this.lastFetchStarted < minDedupeInterval) {
|
|
117
|
+
if (process.env.NODE_ENV !== "production") {
|
|
118
|
+
logger?.debug("Within dupeInterval, aborting revalidate");
|
|
119
|
+
}
|
|
120
|
+
return Promise.resolve();
|
|
118
121
|
}
|
|
119
|
-
return Promise.resolve();
|
|
120
122
|
}
|
|
121
123
|
if (process.env.NODE_ENV !== "production") {
|
|
122
124
|
logger?.debug("Starting actual revalidate");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Query.js","names":["additionalContext","Query","retainCount","connectable","subscription","subject","subscriptionDedupeIntervals","Map","constructor","store","observable","opts","cacheKey","logger","options","cacheKeys","process","env","NODE_ENV","client","child","msgPrefix","type","otherKeys","map","x","JSON","stringify","join","subscribe","observer","_createConnectable","connect","sub","next","value","error","err","complete","registerSubscriptionDedupeInterval","subscriptionId","dedupeInterval","set","unregisterSubscriptionDedupeInterval","delete","getMinimumDedupeInterval","size","Math","min","values","revalidate","force","methodName","abortController","abort","pendingFetch","debug","minDedupeInterval","lastFetchStarted","Date","now","Promise","resolve","batch","setStatus","_preFetch","_fetchAndStore","finally","undefined","status","existing","read","write","dispose","unsubscribe","_dispose"],"sources":["Query.ts"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Logger } from \"@osdk/api\";\nimport type {\n Connectable,\n Observable,\n Observer,\n Subscribable,\n Subscription,\n} from \"rxjs\";\nimport { additionalContext } from \"../../Client.js\";\nimport type {\n CommonObserveOptions,\n Status,\n} from \"../ObservableClient/common.js\";\nimport type { BatchContext } from \"./BatchContext.js\";\nimport type { CacheKeys } from \"./CacheKeys.js\";\nimport type { Changes } from \"./Changes.js\";\nimport type { KnownCacheKey } from \"./KnownCacheKey.js\";\nimport type { Entry } from \"./Layer.js\";\nimport type { OptimisticId } from \"./OptimisticId.js\";\nimport type { Store } from \"./Store.js\";\nimport type { SubjectPayload } from \"./SubjectPayload.js\";\n\nexport abstract class Query<\n KEY extends KnownCacheKey,\n PAYLOAD,\n O extends CommonObserveOptions,\n> implements Subscribable<PAYLOAD> {\n lastFetchStarted?: number;\n pendingFetch?: Promise<void>;\n retainCount: number = 0;\n options: O;\n cacheKey: KEY;\n store: Store;\n abortController?: AbortController;\n #connectable?: Connectable<PAYLOAD>;\n #subscription?: Subscription;\n #subject: Observable<SubjectPayload<KEY>>;\n #subscriptionDedupeIntervals: Map<string, number> = new Map();\n\n /** @internal */\n protected logger: Logger | undefined;\n\n protected readonly cacheKeys: CacheKeys<KnownCacheKey>;\n\n constructor(\n store: Store,\n observable: Observable<SubjectPayload<KEY>>,\n opts: O,\n cacheKey: KEY,\n logger?: Logger,\n ) {\n this.options = opts;\n this.cacheKey = cacheKey;\n this.store = store;\n this.cacheKeys = store.cacheKeys;\n this.#subject = observable;\n\n this.logger = logger ?? (\n process.env.NODE_ENV === \"production\"\n ? store.client[additionalContext].logger\n : store.client[additionalContext].logger?.child({}, {\n msgPrefix: process.env.NODE_ENV !== \"production\"\n ? (`Query<${cacheKey.type}, ${\n cacheKey.otherKeys.map(x => JSON.stringify(x)).join(\", \")\n }>`)\n : \"Query\",\n })\n );\n }\n\n protected abstract _createConnectable(\n subject: Observable<SubjectPayload<KEY>>,\n ): Connectable<PAYLOAD>;\n\n public subscribe(\n observer: Observer<PAYLOAD>,\n ): Subscription {\n this.#connectable ??= this._createConnectable(this.#subject);\n this.#subscription = this.#connectable.connect();\n const sub = this.#connectable.subscribe({\n next: (value) => {\n if (observer.next) {\n observer.next(value);\n }\n },\n error: (err) => {\n if (observer.error) {\n observer.error(err);\n }\n },\n complete: () => {\n if (observer.complete) {\n observer.complete();\n }\n },\n });\n return sub;\n }\n\n /**\n * Register a subscription's dedupeInterval value\n */\n registerSubscriptionDedupeInterval(\n subscriptionId: string,\n dedupeInterval: number | undefined,\n ): void {\n if (dedupeInterval != null && dedupeInterval > 0) {\n this.#subscriptionDedupeIntervals.set(subscriptionId, dedupeInterval);\n }\n }\n\n /**\n * Unregister a subscription's dedupeInterval value\n */\n unregisterSubscriptionDedupeInterval(subscriptionId: string): void {\n this.#subscriptionDedupeIntervals.delete(subscriptionId);\n }\n\n /**\n * Get the minimum dedupeInterval from all active subscriptions\n */\n private getMinimumDedupeInterval(): number {\n if (this.#subscriptionDedupeIntervals.size === 0) {\n return this.options.dedupeInterval ?? 0;\n }\n\n return Math.min(...this.#subscriptionDedupeIntervals.values());\n }\n\n /**\n * Causes the query to revalidate. This will cause the query to fetch\n * the latest data from the server and update the store if it is deemed\n * \"stale\" or if `force` is true.\n *\n * @param force\n * @returns\n */\n async revalidate(force?: boolean): Promise<void> {\n const logger = process.env.NODE_ENV !== \"production\"\n ? this.logger?.child({ methodName: \"revalidate\" })\n : this.logger;\n\n if (force) {\n this.abortController?.abort();\n }\n\n // n.b. I think this isn't quite right since we may require multiple\n // pages to properly \"revalidate\" for someone. This only really works if you\n // have a single page/object. It needs to be redone. FIXME\n\n // if we are pending the first page/object we can just ignore this\n if (this.pendingFetch) {\n if (process.env.NODE_ENV !== \"production\") {\n logger?.debug(\"Fetch is already pending, using it\");\n }\n await this.pendingFetch;\n return;\n }\n\n const minDedupeInterval = this.getMinimumDedupeInterval();\n if (\n minDedupeInterval > 0 && (\n this.lastFetchStarted != null\n && Date.now() - this.lastFetchStarted < minDedupeInterval\n )\n ) {\n if (process.env.NODE_ENV !== \"production\") {\n logger?.debug(\"Within dupeInterval, aborting revalidate\");\n }\n\n return Promise.resolve();\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n logger?.debug(\"Starting actual revalidate\");\n }\n\n this.store.batch({}, (batch) => {\n // make sure the truth layer knows we are loading\n\n // this will not trigger an update to `changes` so it cannot trigger an\n // update of a list either. This may not be the behavior we want.\n this.setStatus(\"loading\", batch);\n });\n\n this._preFetch();\n\n this.lastFetchStarted = Date.now();\n\n if (process.env.NODE_ENV !== \"production\") {\n logger?.debug(\"calling _fetchAndStore()\");\n }\n this.pendingFetch = this._fetchAndStore()\n .finally(() => {\n logger?.debug(\"promise's finally for _fetchAndStore()\");\n this.pendingFetch = undefined;\n });\n\n await this.pendingFetch;\n return;\n }\n\n protected _preFetch(): void {}\n\n protected abstract _fetchAndStore(): Promise<void>;\n\n /**\n * Sets the status of the query in the store (but does not store that in `changes`).\n *\n * @param status\n * @param batch\n * @returns\n */\n setStatus(\n status: Status,\n batch: BatchContext,\n ): void {\n if (process.env.NODE_ENV !== \"production\") {\n this.logger?.child({ methodName: \"setStatus\" }).debug(\n `Attempting to set status to '${status}'`,\n );\n }\n const existing = batch.read(this.cacheKey);\n if (existing?.status === status) {\n if (process.env.NODE_ENV !== \"production\") {\n this.logger?.child({ methodName: \"setStatus\" }).debug(\n `Status is already set to '${status}'; aborting`,\n );\n }\n return;\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n this.logger?.child({ methodName: \"setStatus\" }).debug(\n `Writing status '${status}' to cache`,\n );\n }\n batch.write(this.cacheKey, existing?.value, status);\n }\n\n dispose(): void {\n if (this.abortController) {\n this.abortController.abort();\n }\n this.#subscription?.unsubscribe();\n this._dispose();\n }\n\n /**\n * Per query type dispose functionality\n */\n protected _dispose(): void {}\n\n /**\n * The purpose of this method is to provide a way for others to write\n * directly into the store for this query.\n *\n * @param data\n * @param status\n * @param batch\n */\n abstract writeToStore(\n data: KEY[\"__cacheKey\"][\"value\"],\n status: Status,\n batch: BatchContext,\n ): Entry<KEY>;\n\n /**\n * @param changes\n * @param optimisticId\n * @returns If revalidation is needed, a promise that resolves after the\n * revalidation is complete. Otherwise, undefined.\n */\n maybeUpdateAndRevalidate?: (\n changes: Changes,\n optimisticId: OptimisticId | undefined,\n ) => Promise<void> | undefined;\n\n abstract invalidateObjectType(\n objectType: string,\n changes: Changes | undefined,\n ): Promise<void>;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"Query.js","names":["additionalContext","Query","retainCount","connectable","subscription","subject","subscriptionDedupeIntervals","Map","constructor","store","observable","opts","cacheKey","logger","options","cacheKeys","process","env","NODE_ENV","client","child","msgPrefix","type","otherKeys","map","x","JSON","stringify","join","subscribe","observer","_createConnectable","connect","sub","next","value","error","err","complete","registerSubscriptionDedupeInterval","subscriptionId","dedupeInterval","set","unregisterSubscriptionDedupeInterval","delete","getMinimumDedupeInterval","size","Math","min","values","revalidate","force","methodName","abortController","abort","pendingFetch","debug","minDedupeInterval","lastFetchStarted","Date","now","Promise","resolve","batch","setStatus","_preFetch","_fetchAndStore","finally","undefined","status","existing","read","write","dispose","unsubscribe","_dispose"],"sources":["Query.ts"],"sourcesContent":["/*\n * Copyright 2025 Palantir Technologies, Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { Logger } from \"@osdk/api\";\nimport type {\n Connectable,\n Observable,\n Observer,\n Subscribable,\n Subscription,\n} from \"rxjs\";\nimport { additionalContext } from \"../../Client.js\";\nimport type {\n CommonObserveOptions,\n Status,\n} from \"../ObservableClient/common.js\";\nimport type { BatchContext } from \"./BatchContext.js\";\nimport type { CacheKeys } from \"./CacheKeys.js\";\nimport type { Changes } from \"./Changes.js\";\nimport type { KnownCacheKey } from \"./KnownCacheKey.js\";\nimport type { Entry } from \"./Layer.js\";\nimport type { OptimisticId } from \"./OptimisticId.js\";\nimport type { Store } from \"./Store.js\";\nimport type { SubjectPayload } from \"./SubjectPayload.js\";\n\nexport abstract class Query<\n KEY extends KnownCacheKey,\n PAYLOAD,\n O extends CommonObserveOptions,\n> implements Subscribable<PAYLOAD> {\n lastFetchStarted?: number;\n pendingFetch?: Promise<void>;\n retainCount: number = 0;\n options: O;\n cacheKey: KEY;\n store: Store;\n abortController?: AbortController;\n #connectable?: Connectable<PAYLOAD>;\n #subscription?: Subscription;\n #subject: Observable<SubjectPayload<KEY>>;\n #subscriptionDedupeIntervals: Map<string, number> = new Map();\n\n /** @internal */\n protected logger: Logger | undefined;\n\n protected readonly cacheKeys: CacheKeys<KnownCacheKey>;\n\n constructor(\n store: Store,\n observable: Observable<SubjectPayload<KEY>>,\n opts: O,\n cacheKey: KEY,\n logger?: Logger,\n ) {\n this.options = opts;\n this.cacheKey = cacheKey;\n this.store = store;\n this.cacheKeys = store.cacheKeys;\n this.#subject = observable;\n\n this.logger = logger ?? (\n process.env.NODE_ENV === \"production\"\n ? store.client[additionalContext].logger\n : store.client[additionalContext].logger?.child({}, {\n msgPrefix: process.env.NODE_ENV !== \"production\"\n ? (`Query<${cacheKey.type}, ${\n cacheKey.otherKeys.map(x => JSON.stringify(x)).join(\", \")\n }>`)\n : \"Query\",\n })\n );\n }\n\n protected abstract _createConnectable(\n subject: Observable<SubjectPayload<KEY>>,\n ): Connectable<PAYLOAD>;\n\n public subscribe(\n observer: Observer<PAYLOAD>,\n ): Subscription {\n this.#connectable ??= this._createConnectable(this.#subject);\n this.#subscription = this.#connectable.connect();\n const sub = this.#connectable.subscribe({\n next: (value) => {\n if (observer.next) {\n observer.next(value);\n }\n },\n error: (err) => {\n if (observer.error) {\n observer.error(err);\n }\n },\n complete: () => {\n if (observer.complete) {\n observer.complete();\n }\n },\n });\n return sub;\n }\n\n /**\n * Register a subscription's dedupeInterval value\n */\n registerSubscriptionDedupeInterval(\n subscriptionId: string,\n dedupeInterval: number | undefined,\n ): void {\n if (dedupeInterval != null && dedupeInterval > 0) {\n this.#subscriptionDedupeIntervals.set(subscriptionId, dedupeInterval);\n }\n }\n\n /**\n * Unregister a subscription's dedupeInterval value\n */\n unregisterSubscriptionDedupeInterval(subscriptionId: string): void {\n this.#subscriptionDedupeIntervals.delete(subscriptionId);\n }\n\n /**\n * Get the minimum dedupeInterval from all active subscriptions\n */\n private getMinimumDedupeInterval(): number {\n if (this.#subscriptionDedupeIntervals.size === 0) {\n return this.options.dedupeInterval ?? 0;\n }\n\n return Math.min(...this.#subscriptionDedupeIntervals.values());\n }\n\n /**\n * Causes the query to revalidate. This will cause the query to fetch\n * the latest data from the server and update the store if it is deemed\n * \"stale\" or if `force` is true.\n *\n * @param force\n * @returns\n */\n async revalidate(force?: boolean): Promise<void> {\n const logger = process.env.NODE_ENV !== \"production\"\n ? this.logger?.child({ methodName: \"revalidate\" })\n : this.logger;\n\n if (force) {\n this.abortController?.abort();\n }\n\n // n.b. I think this isn't quite right since we may require multiple\n // pages to properly \"revalidate\" for someone. This only really works if you\n // have a single page/object. It needs to be redone. FIXME\n\n // if we are pending the first page/object we can just ignore this\n if (this.pendingFetch) {\n if (process.env.NODE_ENV !== \"production\") {\n logger?.debug(\"Fetch is already pending, using it\");\n }\n await this.pendingFetch;\n return;\n }\n\n if (!force) {\n const minDedupeInterval = this.getMinimumDedupeInterval();\n if (\n minDedupeInterval > 0 && (\n this.lastFetchStarted != null\n && Date.now() - this.lastFetchStarted < minDedupeInterval\n )\n ) {\n if (process.env.NODE_ENV !== \"production\") {\n logger?.debug(\"Within dupeInterval, aborting revalidate\");\n }\n\n return Promise.resolve();\n }\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n logger?.debug(\"Starting actual revalidate\");\n }\n\n this.store.batch({}, (batch) => {\n // make sure the truth layer knows we are loading\n\n // this will not trigger an update to `changes` so it cannot trigger an\n // update of a list either. This may not be the behavior we want.\n this.setStatus(\"loading\", batch);\n });\n\n this._preFetch();\n\n this.lastFetchStarted = Date.now();\n\n if (process.env.NODE_ENV !== \"production\") {\n logger?.debug(\"calling _fetchAndStore()\");\n }\n this.pendingFetch = this._fetchAndStore()\n .finally(() => {\n logger?.debug(\"promise's finally for _fetchAndStore()\");\n this.pendingFetch = undefined;\n });\n\n await this.pendingFetch;\n return;\n }\n\n protected _preFetch(): void {}\n\n protected abstract _fetchAndStore(): Promise<void>;\n\n /**\n * Sets the status of the query in the store (but does not store that in `changes`).\n *\n * @param status\n * @param batch\n * @returns\n */\n setStatus(\n status: Status,\n batch: BatchContext,\n ): void {\n if (process.env.NODE_ENV !== \"production\") {\n this.logger?.child({ methodName: \"setStatus\" }).debug(\n `Attempting to set status to '${status}'`,\n );\n }\n const existing = batch.read(this.cacheKey);\n if (existing?.status === status) {\n if (process.env.NODE_ENV !== \"production\") {\n this.logger?.child({ methodName: \"setStatus\" }).debug(\n `Status is already set to '${status}'; aborting`,\n );\n }\n return;\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n this.logger?.child({ methodName: \"setStatus\" }).debug(\n `Writing status '${status}' to cache`,\n );\n }\n batch.write(this.cacheKey, existing?.value, status);\n }\n\n dispose(): void {\n if (this.abortController) {\n this.abortController.abort();\n }\n this.#subscription?.unsubscribe();\n this._dispose();\n }\n\n /**\n * Per query type dispose functionality\n */\n protected _dispose(): void {}\n\n /**\n * The purpose of this method is to provide a way for others to write\n * directly into the store for this query.\n *\n * @param data\n * @param status\n * @param batch\n */\n abstract writeToStore(\n data: KEY[\"__cacheKey\"][\"value\"],\n status: Status,\n batch: BatchContext,\n ): Entry<KEY>;\n\n /**\n * @param changes\n * @param optimisticId\n * @returns If revalidation is needed, a promise that resolves after the\n * revalidation is complete. Otherwise, undefined.\n */\n maybeUpdateAndRevalidate?: (\n changes: Changes,\n optimisticId: OptimisticId | undefined,\n ) => Promise<void> | undefined;\n\n abstract invalidateObjectType(\n objectType: string,\n changes: Changes | undefined,\n ): Promise<void>;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -26,7 +26,7 @@ import { TestLogger } from "../../logger/TestLogger.js";
|
|
|
26
26
|
import { runOptimisticJob } from "./actions/OptimisticJob.js";
|
|
27
27
|
import { createOptimisticId } from "./OptimisticId.js";
|
|
28
28
|
import { Store } from "./Store.js";
|
|
29
|
-
import { applyCustomMatchers, createClientMockHelper, createDefer, expectNoMoreCalls, expectSingleLinkCallAndClear, expectSingleListCallAndClear, expectSingleObjectCallAndClear, getObject, mockListSubCallback, mockObserver, mockSingleSubCallback, objectPayloadContaining, updateList, updateObject, waitForCall } from "./testUtils.js";
|
|
29
|
+
import { applyCustomMatchers, createClientMockHelper, createDefer, expectNoMoreCalls, expectSingleLinkCallAndClear, expectSingleListCallAndClear, expectSingleObjectCallAndClear, getObject, mockLinkSubCallback, mockListSubCallback, mockObserver, mockSingleSubCallback, objectPayloadContaining, updateList, updateObject, waitForCall } from "./testUtils.js";
|
|
30
30
|
import { invalidateList } from "./testUtils/invalidateList.js";
|
|
31
31
|
import { expectStandardObserveLink } from "./testUtils/observeLink/expectStandardObserveLink.js";
|
|
32
32
|
import { expectStandardObserveObject } from "./testUtils/observeObject/expectStandardObserveObject.js";
|
|
@@ -301,6 +301,88 @@ describe(Store, () => {
|
|
|
301
301
|
expect(empSubFn.next).not.toHaveBeenCalled();
|
|
302
302
|
expect(officeSubFn.next).not.toHaveBeenCalled();
|
|
303
303
|
});
|
|
304
|
+
it("re-subscribing within dedupeInterval does not refetch", async () => {
|
|
305
|
+
const {
|
|
306
|
+
payload: emp1Payload
|
|
307
|
+
} = await expectStandardObserveObject({
|
|
308
|
+
cache,
|
|
309
|
+
type: Employee,
|
|
310
|
+
primaryKey: 2
|
|
311
|
+
});
|
|
312
|
+
const emp2 = emp1Payload?.object;
|
|
313
|
+
!emp2 ? process.env.NODE_ENV !== "production" ? invariant(false) : invariant(false) : void 0;
|
|
314
|
+
const linkSubFn1 = mockLinkSubCallback();
|
|
315
|
+
const sub1 = cache.links.observe({
|
|
316
|
+
linkName: "peeps",
|
|
317
|
+
srcType: {
|
|
318
|
+
type: "object",
|
|
319
|
+
apiName: emp2.$apiName
|
|
320
|
+
},
|
|
321
|
+
pk: emp2.$primaryKey,
|
|
322
|
+
dedupeInterval: 60_000
|
|
323
|
+
}, linkSubFn1);
|
|
324
|
+
await waitForCall(linkSubFn1);
|
|
325
|
+
expectSingleLinkCallAndClear(linkSubFn1, [], {
|
|
326
|
+
status: "loading"
|
|
327
|
+
});
|
|
328
|
+
await waitForCall(linkSubFn1);
|
|
329
|
+
expectSingleLinkCallAndClear(linkSubFn1, [], {
|
|
330
|
+
status: "loaded"
|
|
331
|
+
});
|
|
332
|
+
sub1.unsubscribe();
|
|
333
|
+
const linkSubFn2 = mockLinkSubCallback();
|
|
334
|
+
defer(cache.links.observe({
|
|
335
|
+
linkName: "peeps",
|
|
336
|
+
srcType: {
|
|
337
|
+
type: "object",
|
|
338
|
+
apiName: emp2.$apiName
|
|
339
|
+
},
|
|
340
|
+
pk: emp2.$primaryKey,
|
|
341
|
+
dedupeInterval: 60_000
|
|
342
|
+
}, linkSubFn2));
|
|
343
|
+
await waitForCall(linkSubFn2);
|
|
344
|
+
expectSingleLinkCallAndClear(linkSubFn2, [], {
|
|
345
|
+
status: "loaded"
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
it("forced revalidation bypasses dedupeInterval", async () => {
|
|
349
|
+
const {
|
|
350
|
+
payload: emp1Payload
|
|
351
|
+
} = await expectStandardObserveObject({
|
|
352
|
+
cache,
|
|
353
|
+
type: Employee,
|
|
354
|
+
primaryKey: 2
|
|
355
|
+
});
|
|
356
|
+
const emp2 = emp1Payload?.object;
|
|
357
|
+
!emp2 ? process.env.NODE_ENV !== "production" ? invariant(false) : invariant(false) : void 0;
|
|
358
|
+
const linkSubFn = mockLinkSubCallback();
|
|
359
|
+
defer(cache.links.observe({
|
|
360
|
+
linkName: "peeps",
|
|
361
|
+
srcType: {
|
|
362
|
+
type: "object",
|
|
363
|
+
apiName: emp2.$apiName
|
|
364
|
+
},
|
|
365
|
+
pk: emp2.$primaryKey,
|
|
366
|
+
dedupeInterval: 60_000
|
|
367
|
+
}, linkSubFn));
|
|
368
|
+
await waitForCall(linkSubFn);
|
|
369
|
+
expectSingleLinkCallAndClear(linkSubFn, [], {
|
|
370
|
+
status: "loading"
|
|
371
|
+
});
|
|
372
|
+
await waitForCall(linkSubFn);
|
|
373
|
+
expectSingleLinkCallAndClear(linkSubFn, [], {
|
|
374
|
+
status: "loaded"
|
|
375
|
+
});
|
|
376
|
+
const invalidatePromise = cache.invalidateObjectType("Employee", undefined);
|
|
377
|
+
await waitForCall(linkSubFn);
|
|
378
|
+
expectSingleLinkCallAndClear(linkSubFn, [], {
|
|
379
|
+
status: "loading"
|
|
380
|
+
});
|
|
381
|
+
await invalidatePromise;
|
|
382
|
+
expectSingleLinkCallAndClear(linkSubFn, [], {
|
|
383
|
+
status: "loaded"
|
|
384
|
+
});
|
|
385
|
+
});
|
|
304
386
|
});
|
|
305
387
|
describe("with mock server", () => {
|
|
306
388
|
let client;
|