@codecks/fetch 0.1.0 → 0.1.2
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/dist/_exploration/api-requester.d.ts +18 -0
- package/dist/_exploration/api-requester.js +120 -0
- package/dist/_exploration/create-hooks.d.ts +12 -0
- package/dist/_exploration/create-hooks.js +137 -0
- package/dist/_exploration/loader.d.ts +26 -0
- package/dist/_exploration/loader.js +89 -0
- package/dist/_exploration/store.d.ts +4 -0
- package/dist/_exploration/store.js +161 -0
- package/dist/_root-BG9fhc-C.d.ts +34 -0
- package/dist/collection-utils-CYJaA1a9.js +32 -0
- package/dist/index.d.ts +4 -2890
- package/dist/index.js +13 -3076
- package/dist/loader-types-DJdGWL4B.d.ts +40 -0
- package/dist/loader-utils-CjrkQskt.d.ts +2688 -0
- package/dist/query-helpers-DsMMOtSO.js +3037 -0
- package/dist/query-type-MH8IxqbW.d.ts +182 -0
- package/dist/store-mAj3G67A.d.ts +44 -0
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "../query-type-MH8IxqbW.js";
|
|
2
|
+
import { FetchOptions } from "../loader-utils-CjrkQskt.js";
|
|
3
|
+
import { BaseRequester, MissingDataRequest } from "../loader-types-DJdGWL4B.js";
|
|
4
|
+
|
|
5
|
+
//#region src/_exploration/api-requester.d.ts
|
|
6
|
+
declare class ApiRequester implements BaseRequester {
|
|
7
|
+
private readonly fetchOptions;
|
|
8
|
+
private readonly limiter;
|
|
9
|
+
constructor(fetchOptions: FetchOptions, maxConcurrent?: number);
|
|
10
|
+
request(requests: MissingDataRequest[]): Promise<{
|
|
11
|
+
model: string;
|
|
12
|
+
key: string;
|
|
13
|
+
partialInstance: Record<string, unknown>;
|
|
14
|
+
}[]>;
|
|
15
|
+
private _doRequest;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { ApiRequester };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { configuredFetch, ensureMapValue } from "../collection-utils-CYJaA1a9.js";
|
|
2
|
+
import { serializeModel } from "../query-helpers-DsMMOtSO.js";
|
|
3
|
+
|
|
4
|
+
//#region src/_exploration/utils/concurrency-limiter.ts
|
|
5
|
+
var ConcurrencyLimiter = class {
|
|
6
|
+
maxConcurrent;
|
|
7
|
+
running = 0;
|
|
8
|
+
queue = [];
|
|
9
|
+
constructor(maxConcurrent) {
|
|
10
|
+
if (maxConcurrent < 1) throw new Error("maxConcurrent must be at least 1");
|
|
11
|
+
this.maxConcurrent = maxConcurrent;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Run an async operation with concurrency limiting.
|
|
15
|
+
* If the limit is reached, the operation will be queued until a slot becomes available.
|
|
16
|
+
*/
|
|
17
|
+
async run(fn) {
|
|
18
|
+
await this.acquireSlot();
|
|
19
|
+
try {
|
|
20
|
+
return await fn();
|
|
21
|
+
} finally {
|
|
22
|
+
this.releaseSlot();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async acquireSlot() {
|
|
26
|
+
if (this.running < this.maxConcurrent) {
|
|
27
|
+
this.running++;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
this.queue.push(() => {
|
|
32
|
+
this.running++;
|
|
33
|
+
resolve();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
releaseSlot() {
|
|
38
|
+
this.running--;
|
|
39
|
+
const next = this.queue.shift();
|
|
40
|
+
if (next) next();
|
|
41
|
+
}
|
|
42
|
+
getRunningCount() {
|
|
43
|
+
return this.running;
|
|
44
|
+
}
|
|
45
|
+
getQueuedCount() {
|
|
46
|
+
return this.queue.length;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/_exploration/api-requester.ts
|
|
52
|
+
const mergeRelations = (r1, r2) => {
|
|
53
|
+
const nextRels = { ...r1.relations };
|
|
54
|
+
for (const [relKey, relQuery] of Object.entries(r2.relations || {})) {
|
|
55
|
+
const exist = nextRels[relKey];
|
|
56
|
+
if (exist && !exist.asField && !relQuery.asField) nextRels[relKey] = mergeRelations(exist, relQuery);
|
|
57
|
+
else nextRels[relKey] = relQuery;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
...r1,
|
|
61
|
+
fields: [...r1.fields ?? [], ...r2.fields ?? []],
|
|
62
|
+
relations: nextRels
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
var ApiRequester = class {
|
|
66
|
+
fetchOptions;
|
|
67
|
+
limiter;
|
|
68
|
+
constructor(fetchOptions, maxConcurrent = 3) {
|
|
69
|
+
this.fetchOptions = fetchOptions;
|
|
70
|
+
this.limiter = new ConcurrencyLimiter(maxConcurrent);
|
|
71
|
+
}
|
|
72
|
+
async request(requests) {
|
|
73
|
+
return this.limiter.run(() => this._doRequest(requests));
|
|
74
|
+
}
|
|
75
|
+
async _doRequest(requests) {
|
|
76
|
+
if (requests.length === 0) return [];
|
|
77
|
+
const dataByModel = new Map();
|
|
78
|
+
for (const req of requests) {
|
|
79
|
+
const instancesById = ensureMapValue(dataByModel, req.model, () => new Map());
|
|
80
|
+
let instanceData = ensureMapValue(instancesById, req.id, () => ({
|
|
81
|
+
fields: new Set(),
|
|
82
|
+
relations: new Map()
|
|
83
|
+
}));
|
|
84
|
+
if (req.type === "field") instanceData.fields.add(req.field);
|
|
85
|
+
else if (req.type === "relation") {
|
|
86
|
+
const existing = instanceData.relations.get(req.relKey);
|
|
87
|
+
if (existing && !existing.asField && !req.query.asField) instanceData.relations.set(req.relKey, mergeRelations(existing, req.query));
|
|
88
|
+
else instanceData.relations.set(req.relKey, req.query);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const query = {};
|
|
92
|
+
for (const [model, instancesById] of dataByModel) for (const [id, instanceData] of instancesById) {
|
|
93
|
+
const queryKey = model === "_root" ? "_root" : `${model}(${id})`;
|
|
94
|
+
query[queryKey] = serializeModel({
|
|
95
|
+
fields: [...instanceData.fields],
|
|
96
|
+
relations: Object.fromEntries(instanceData.relations)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const result = await configuredFetch(this.fetchOptions, "", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "Content-Type": "application/json" },
|
|
102
|
+
body: JSON.stringify({ query })
|
|
103
|
+
});
|
|
104
|
+
const results = [];
|
|
105
|
+
for (const [modelName, instances] of Object.entries(result)) if (modelName === "_root") results.push({
|
|
106
|
+
model: "_root",
|
|
107
|
+
key: "",
|
|
108
|
+
partialInstance: instances
|
|
109
|
+
});
|
|
110
|
+
else for (const [id, data] of Object.entries(instances)) results.push({
|
|
111
|
+
model: modelName,
|
|
112
|
+
key: id,
|
|
113
|
+
partialInstance: data
|
|
114
|
+
});
|
|
115
|
+
return results;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
export { ApiRequester };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AnyDesc, InferModelQuery, InferRelQuery, ModelQuery, RelQuery } from "../query-type-MH8IxqbW.js";
|
|
2
|
+
import "../loader-types-DJdGWL4B.js";
|
|
3
|
+
import { _rootDesc$1 as _rootDesc } from "../_root-BG9fhc-C.js";
|
|
4
|
+
import { Store } from "../store-mAj3G67A.js";
|
|
5
|
+
|
|
6
|
+
//#region src/_exploration/create-hooks.d.ts
|
|
7
|
+
declare const createHooks: (store: Store) => {
|
|
8
|
+
useFetch: <K extends string, Id extends string, const Q extends ModelQuery<AnyDesc, Record<string, AnyDesc>>>(model: K, id: Id, q: Q) => InferModelQuery<AnyDesc, Q, Record<string, AnyDesc>>;
|
|
9
|
+
useFetchFromRoot: <const Q extends RelQuery<typeof _rootDesc, Record<string, AnyDesc>>>(q: Q) => InferRelQuery<typeof _rootDesc, Q, Record<string, AnyDesc>>;
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { createHooks };
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { queryToKey } from "../query-helpers-DsMMOtSO.js";
|
|
2
|
+
import { use, useSyncExternalStore } from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/_exploration/user-query-manager.ts
|
|
5
|
+
const diffResults = (prevResult, prevParts, nextResult) => {
|
|
6
|
+
const nextParts = [];
|
|
7
|
+
return {
|
|
8
|
+
added: nextParts,
|
|
9
|
+
removed: prevParts,
|
|
10
|
+
reconciledResult: {
|
|
11
|
+
parts: nextParts,
|
|
12
|
+
data: nextResult
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
const createStoreQuery = (opts) => {
|
|
17
|
+
const { onDispose, updateDataListener, store, q, ids, model } = opts;
|
|
18
|
+
const queryListeners = new Set();
|
|
19
|
+
let lastData = {
|
|
20
|
+
parts: [],
|
|
21
|
+
data: null
|
|
22
|
+
};
|
|
23
|
+
const reconcileNextResult = (nextData) => {
|
|
24
|
+
const { added, removed, reconciledResult } = diffResults(lastData.data, lastData.parts, nextData);
|
|
25
|
+
if (!added.length && !removed.length) {
|
|
26
|
+
lastData = reconciledResult;
|
|
27
|
+
updateDataListener({
|
|
28
|
+
added,
|
|
29
|
+
removed
|
|
30
|
+
}, notify);
|
|
31
|
+
queryListeners.forEach((listener) => listener());
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const notify = () => {
|
|
35
|
+
loadData();
|
|
36
|
+
};
|
|
37
|
+
const loadData = () => {
|
|
38
|
+
const res = store.loadData(model, ids, q);
|
|
39
|
+
if (res.state === "pending") res.promise.then(reconcileNextResult);
|
|
40
|
+
else reconcileNextResult(res.value);
|
|
41
|
+
return res;
|
|
42
|
+
};
|
|
43
|
+
let data = loadData();
|
|
44
|
+
const subscribe = (listener) => {
|
|
45
|
+
queryListeners.add(listener);
|
|
46
|
+
return () => {
|
|
47
|
+
queryListeners.delete(listener);
|
|
48
|
+
if (queryListeners.size === 0) onDispose();
|
|
49
|
+
updateDataListener({
|
|
50
|
+
added: [],
|
|
51
|
+
removed: lastData.parts
|
|
52
|
+
}, notify);
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const get = () => {
|
|
56
|
+
return data;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
subscribe,
|
|
60
|
+
get
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
var UserQueryManager = class {
|
|
64
|
+
queryCache = new Map();
|
|
65
|
+
activeParts = new Map();
|
|
66
|
+
constructor(store) {
|
|
67
|
+
this.store = store;
|
|
68
|
+
}
|
|
69
|
+
getQuery(model, ids, q) {
|
|
70
|
+
const cacheKey = JSON.stringify({
|
|
71
|
+
model,
|
|
72
|
+
ids,
|
|
73
|
+
q: queryToKey(q)
|
|
74
|
+
});
|
|
75
|
+
let query = this.queryCache.get(cacheKey);
|
|
76
|
+
if (!query) {
|
|
77
|
+
query = createStoreQuery({
|
|
78
|
+
model,
|
|
79
|
+
ids,
|
|
80
|
+
q,
|
|
81
|
+
store: this.store,
|
|
82
|
+
onDispose: () => {
|
|
83
|
+
this.queryCache.delete(cacheKey);
|
|
84
|
+
},
|
|
85
|
+
updateDataListener: ({ added, removed }, notify) => {
|
|
86
|
+
added.forEach((partKey) => {
|
|
87
|
+
let partListeners = this.activeParts.get(partKey);
|
|
88
|
+
if (!partListeners) {
|
|
89
|
+
partListeners = new Set();
|
|
90
|
+
this.activeParts.set(partKey, partListeners);
|
|
91
|
+
}
|
|
92
|
+
partListeners.add(notify);
|
|
93
|
+
});
|
|
94
|
+
removed.forEach((partKey) => {
|
|
95
|
+
const partListeners = this.activeParts.get(partKey);
|
|
96
|
+
if (partListeners) {
|
|
97
|
+
partListeners.delete(notify);
|
|
98
|
+
if (partListeners.size === 0) this.activeParts.delete(partKey);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
this.queryCache.set(cacheKey, query);
|
|
104
|
+
}
|
|
105
|
+
return query;
|
|
106
|
+
}
|
|
107
|
+
onInvalidation(partKeys) {
|
|
108
|
+
const notifyFns = new Set();
|
|
109
|
+
for (const partKey of partKeys) this.activeParts.get(partKey)?.forEach((fn) => notifyFns.add(fn));
|
|
110
|
+
notifyFns.forEach((fn) => fn());
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/_exploration/create-hooks.ts
|
|
116
|
+
const createHooks = (store) => {
|
|
117
|
+
const queryManager = new UserQueryManager(store);
|
|
118
|
+
const useQueryManager = (model, ids, q) => {
|
|
119
|
+
const queryObj = queryManager.getQuery(model, ids, q);
|
|
120
|
+
const val = useSyncExternalStore(queryObj.subscribe, queryObj.get);
|
|
121
|
+
if (val.state === "pending") return use(val.promise);
|
|
122
|
+
return val.value;
|
|
123
|
+
};
|
|
124
|
+
const useFetch = (model, id, q) => {
|
|
125
|
+
return useQueryManager(model, [id], q);
|
|
126
|
+
};
|
|
127
|
+
const useFetchFromRoot = (q) => {
|
|
128
|
+
return useQueryManager("_root", [""], { relations: q });
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
useFetch,
|
|
132
|
+
useFetchFromRoot
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
//#endregion
|
|
137
|
+
export { createHooks };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "../query-type-MH8IxqbW.js";
|
|
2
|
+
import { BaseLoader, BaseRequester, MissingDataRequest, OnLoadedFn } from "../loader-types-DJdGWL4B.js";
|
|
3
|
+
|
|
4
|
+
//#region src/_exploration/loader.d.ts
|
|
5
|
+
type DataRequestWithKey = {
|
|
6
|
+
key: string;
|
|
7
|
+
request: MissingDataRequest;
|
|
8
|
+
};
|
|
9
|
+
declare class BatchedLoader implements BaseLoader {
|
|
10
|
+
private _currentBatch;
|
|
11
|
+
onLoaded: OnLoadedFn;
|
|
12
|
+
readonly requester: BaseRequester;
|
|
13
|
+
private ongoingKeysToBatches;
|
|
14
|
+
batchTimeoutMs: number;
|
|
15
|
+
constructor(opts: {
|
|
16
|
+
batchTimeoutMs: number;
|
|
17
|
+
requester: BaseRequester;
|
|
18
|
+
});
|
|
19
|
+
setOnLoaded(onLoaded: OnLoadedFn): void;
|
|
20
|
+
private getCurrentBatch;
|
|
21
|
+
private getOngoingBatches;
|
|
22
|
+
loadBatch(rawRequests: MissingDataRequest[]): Promise<void[]>;
|
|
23
|
+
onBatchCompleted(requests: DataRequestWithKey[]): void;
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { BatchedLoader };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { queryToKey } from "../query-helpers-DsMMOtSO.js";
|
|
2
|
+
|
|
3
|
+
//#region src/_exploration/loader.ts
|
|
4
|
+
const notYetLoaded = () => {
|
|
5
|
+
throw new Error("Not yet loaded");
|
|
6
|
+
};
|
|
7
|
+
const augmentRequest = (req) => {
|
|
8
|
+
const key = `${req.model}:${req.id}:${req.type === "field" ? req.field : `${req.relKey}:${queryToKey(req.query)}`}`;
|
|
9
|
+
return {
|
|
10
|
+
key,
|
|
11
|
+
request: req
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
var BatchedLoader = class {
|
|
15
|
+
_currentBatch = null;
|
|
16
|
+
onLoaded = notYetLoaded;
|
|
17
|
+
requester;
|
|
18
|
+
ongoingKeysToBatches = new Map();
|
|
19
|
+
batchTimeoutMs;
|
|
20
|
+
constructor(opts) {
|
|
21
|
+
this.batchTimeoutMs = opts.batchTimeoutMs;
|
|
22
|
+
this.requester = opts.requester;
|
|
23
|
+
}
|
|
24
|
+
setOnLoaded(onLoaded) {
|
|
25
|
+
this.onLoaded = onLoaded;
|
|
26
|
+
}
|
|
27
|
+
getCurrentBatch() {
|
|
28
|
+
if (!this._currentBatch) this._currentBatch = new Batch(this, () => this._currentBatch = null);
|
|
29
|
+
return this._currentBatch;
|
|
30
|
+
}
|
|
31
|
+
getOngoingBatches(requests) {
|
|
32
|
+
const batches = new Set();
|
|
33
|
+
const remainingRequests = [];
|
|
34
|
+
requests.forEach((req) => {
|
|
35
|
+
const batch = this.ongoingKeysToBatches.get(req.key);
|
|
36
|
+
if (batch) batches.add(batch);
|
|
37
|
+
else remainingRequests.push(req);
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
promises: [...batches].map((batch) => batch.promise),
|
|
41
|
+
remainingRequests
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async loadBatch(rawRequests) {
|
|
45
|
+
const requests = rawRequests.map(augmentRequest);
|
|
46
|
+
const { promises, remainingRequests } = this.getOngoingBatches(requests);
|
|
47
|
+
if (remainingRequests.length === 0) return Promise.all(promises);
|
|
48
|
+
const batch = this.getCurrentBatch();
|
|
49
|
+
batch.addRequests(remainingRequests);
|
|
50
|
+
remainingRequests.forEach((req) => this.ongoingKeysToBatches.set(req.key, batch));
|
|
51
|
+
promises.push(batch.promise);
|
|
52
|
+
return Promise.all(promises);
|
|
53
|
+
}
|
|
54
|
+
onBatchCompleted(requests) {
|
|
55
|
+
requests.forEach((req) => this.ongoingKeysToBatches.delete(req.key));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var Batch = class {
|
|
59
|
+
requests = [];
|
|
60
|
+
onResolve = notYetLoaded;
|
|
61
|
+
onReject = notYetLoaded;
|
|
62
|
+
timeoutId = null;
|
|
63
|
+
promise;
|
|
64
|
+
constructor(loader, onStarted) {
|
|
65
|
+
this.loader = loader;
|
|
66
|
+
this.onStarted = onStarted;
|
|
67
|
+
this.promise = new Promise((resolve, reject) => {
|
|
68
|
+
this.onResolve = resolve;
|
|
69
|
+
this.onReject = reject;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
addRequests(requests) {
|
|
73
|
+
this.requests.push(...requests);
|
|
74
|
+
if (!this.timeoutId) this.timeoutId = setTimeout(() => {
|
|
75
|
+
this.onStarted();
|
|
76
|
+
this.loader.requester.request(this.requests.map((r) => r.request)).then((results) => {
|
|
77
|
+
results.forEach(({ model, key, partialInstance }) => {
|
|
78
|
+
this.loader.onLoaded(model, key, partialInstance);
|
|
79
|
+
});
|
|
80
|
+
this.onResolve();
|
|
81
|
+
}, (err) => {
|
|
82
|
+
this.onReject(err);
|
|
83
|
+
}).finally(() => this.loader.onBatchCompleted(this.requests));
|
|
84
|
+
}, this.loader.batchTimeoutMs);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
export { BatchedLoader };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { getRelKey, makeRelQuerySerializable, modelMap } from "../query-helpers-DsMMOtSO.js";
|
|
2
|
+
|
|
3
|
+
//#region src/_exploration/immediate-cache.ts
|
|
4
|
+
var ImmediateCache = class {
|
|
5
|
+
cache = new Map();
|
|
6
|
+
partKeyToFields = new Map();
|
|
7
|
+
getInstanceKey(model, id) {
|
|
8
|
+
return `${model}:${id}`;
|
|
9
|
+
}
|
|
10
|
+
get(model, key, field) {
|
|
11
|
+
return this.cache.get(this.getInstanceKey(model, key))?.get(field);
|
|
12
|
+
}
|
|
13
|
+
set(model, key, partialInstance, meta) {
|
|
14
|
+
const instanceKey = this.getInstanceKey(model, key);
|
|
15
|
+
let entry = this.cache.get(instanceKey);
|
|
16
|
+
if (!entry) {
|
|
17
|
+
entry = new Map();
|
|
18
|
+
this.cache.set(instanceKey, entry);
|
|
19
|
+
}
|
|
20
|
+
for (const field in partialInstance) {
|
|
21
|
+
const f = partialInstance[field];
|
|
22
|
+
const fieldEntry = {
|
|
23
|
+
value: f.value,
|
|
24
|
+
partKeys: f.partKeys,
|
|
25
|
+
meta
|
|
26
|
+
};
|
|
27
|
+
entry.set(field, fieldEntry);
|
|
28
|
+
for (const partKey of f.partKeys) {
|
|
29
|
+
const maybeFields = this.partKeyToFields.get(partKey);
|
|
30
|
+
if (!maybeFields) this.partKeyToFields.set(partKey, [fieldEntry]);
|
|
31
|
+
else maybeFields.push(fieldEntry);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
setMetaByPartKeys(partKeys, meta) {
|
|
36
|
+
for (const partKey of partKeys) {
|
|
37
|
+
const fields = this.partKeyToFields.get(partKey);
|
|
38
|
+
if (!fields) continue;
|
|
39
|
+
for (const field of fields) field.meta = meta;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/_exploration/store.ts
|
|
46
|
+
const calcPartKeys = (model, id, field) => {
|
|
47
|
+
return [`${model}:${id}:${field}`];
|
|
48
|
+
};
|
|
49
|
+
var Store = class {
|
|
50
|
+
cache = new ImmediateCache();
|
|
51
|
+
modelMap = modelMap;
|
|
52
|
+
constructor(loader) {
|
|
53
|
+
this.loader = loader;
|
|
54
|
+
loader.setOnLoaded((model, key, res) => this.onLoaded(model, key, res));
|
|
55
|
+
}
|
|
56
|
+
loadData(model, ids, q, acceptDirty, iteration = 0) {
|
|
57
|
+
const desc = this.modelMap[model];
|
|
58
|
+
if (!desc) throw new Error(`Model ${model} not found`);
|
|
59
|
+
const missingRequests = [];
|
|
60
|
+
const resultObj = {};
|
|
61
|
+
const allPartKeys = [];
|
|
62
|
+
let allPresent = true;
|
|
63
|
+
for (const id of ids) {
|
|
64
|
+
const result = this.checkQueryRecursive(desc, model, id, q, missingRequests, acceptDirty);
|
|
65
|
+
if (!result.allPresent) allPresent = false;
|
|
66
|
+
resultObj[id] = result.data;
|
|
67
|
+
allPartKeys.push(...result.partKeys);
|
|
68
|
+
}
|
|
69
|
+
if (!allPresent && iteration > 5) throw new Error(`Can't seem to load ${JSON.stringify(missingRequests)} after 5 iterations`);
|
|
70
|
+
if (allPresent) {
|
|
71
|
+
if (missingRequests.length > 0) this.loader.loadBatch(missingRequests);
|
|
72
|
+
return {
|
|
73
|
+
state: "resolved",
|
|
74
|
+
value: {
|
|
75
|
+
value: resultObj,
|
|
76
|
+
partKeys: allPartKeys
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const promise = this.loader.loadBatch(missingRequests).then(() => {
|
|
81
|
+
return this.loadData(model, ids, q, acceptDirty, iteration + 1);
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
state: "pending",
|
|
85
|
+
promise
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
onLoaded(model, key, partialInstance) {
|
|
89
|
+
const withPartKeys = Object.fromEntries(Object.entries(partialInstance).map(([field, value]) => [field, {
|
|
90
|
+
value,
|
|
91
|
+
partKeys: calcPartKeys(model, key, field)
|
|
92
|
+
}]));
|
|
93
|
+
this.cache.set(model, key, withPartKeys, { state: "fresh" });
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Recursively check if query data is in cache
|
|
97
|
+
* Accumulates missing requests in the missingRequests array
|
|
98
|
+
*/
|
|
99
|
+
checkQueryRecursive(desc, model, id, query, missingRequests, acceptDirty) {
|
|
100
|
+
const data = {};
|
|
101
|
+
const partKeys = [];
|
|
102
|
+
let allPresent = true;
|
|
103
|
+
if (query.fields) for (const field of query.fields) {
|
|
104
|
+
const cachedValue = this.cache.get(model, id, field);
|
|
105
|
+
const needsRequest = !cachedValue || cachedValue.meta.state === "dirty";
|
|
106
|
+
if (needsRequest) missingRequests.push({
|
|
107
|
+
type: "field",
|
|
108
|
+
model,
|
|
109
|
+
id,
|
|
110
|
+
field
|
|
111
|
+
});
|
|
112
|
+
const acceptsData = cachedValue && (cachedValue.meta.state === "fresh" || acceptDirty);
|
|
113
|
+
if (acceptsData) {
|
|
114
|
+
data[field] = cachedValue.value;
|
|
115
|
+
partKeys.push(...cachedValue.partKeys);
|
|
116
|
+
} else allPresent = false;
|
|
117
|
+
}
|
|
118
|
+
if (query.relations) for (const [relationName, rawRelationQuery] of Object.entries(query.relations)) {
|
|
119
|
+
if (!rawRelationQuery) continue;
|
|
120
|
+
const relatedModel = desc.relations[relationName].relName;
|
|
121
|
+
const relatedModelDesc = this.modelMap[relatedModel];
|
|
122
|
+
const relationQueryList = Array.isArray(rawRelationQuery) ? rawRelationQuery : [rawRelationQuery];
|
|
123
|
+
for (const relQuery of relationQueryList) {
|
|
124
|
+
const relKey = getRelKey(relQuery, relationName);
|
|
125
|
+
const cachedValue = this.cache.get(model, id, relKey);
|
|
126
|
+
const needsRequest = !cachedValue || cachedValue.meta.state === "dirty";
|
|
127
|
+
if (needsRequest) missingRequests.push({
|
|
128
|
+
type: "relation",
|
|
129
|
+
model,
|
|
130
|
+
id,
|
|
131
|
+
relKey,
|
|
132
|
+
query: makeRelQuerySerializable(relQuery)
|
|
133
|
+
});
|
|
134
|
+
const acceptsData = cachedValue && (cachedValue.meta.state === "fresh" || acceptDirty);
|
|
135
|
+
if (acceptsData) {
|
|
136
|
+
if (!relatedModelDesc) throw new Error(`Model ${relatedModel} not found`);
|
|
137
|
+
const fieldName = relQuery.as || relationName;
|
|
138
|
+
if (Array.isArray(cachedValue.value)) {
|
|
139
|
+
const relationResults = cachedValue.value.map((key) => this.checkQueryRecursive(relatedModelDesc, relatedModel, key, relQuery, missingRequests, acceptDirty));
|
|
140
|
+
data[fieldName] = relationResults.map((result) => result.data);
|
|
141
|
+
partKeys.push(...relationResults.flatMap((result) => result.partKeys));
|
|
142
|
+
if (relationResults.some((result) => !result.allPresent)) allPresent = false;
|
|
143
|
+
} else {
|
|
144
|
+
const relationResult = this.checkQueryRecursive(relatedModelDesc, relatedModel, cachedValue.value, relQuery, missingRequests, acceptDirty);
|
|
145
|
+
data[fieldName] = relationResult.data;
|
|
146
|
+
partKeys.push(...relationResult.partKeys);
|
|
147
|
+
if (!relationResult.allPresent) allPresent = false;
|
|
148
|
+
}
|
|
149
|
+
} else allPresent = false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
data,
|
|
154
|
+
partKeys,
|
|
155
|
+
allPresent
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
export { Store };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ModelDesc, RelationEntry } from "./query-type-MH8IxqbW.js";
|
|
2
|
+
|
|
3
|
+
//#region src/models/_root.d.ts
|
|
4
|
+
declare const _rootDesc: ModelDesc<"_root", {}, {
|
|
5
|
+
releases: RelationEntry<"release", {
|
|
6
|
+
readonly type: "hasMany";
|
|
7
|
+
}>;
|
|
8
|
+
accountOnboardingSteps: RelationEntry<"accountOnboardingStep", {
|
|
9
|
+
readonly type: "hasMany";
|
|
10
|
+
}>;
|
|
11
|
+
apps: RelationEntry<"app", {
|
|
12
|
+
readonly type: "hasMany";
|
|
13
|
+
}>;
|
|
14
|
+
account: RelationEntry<"account", {
|
|
15
|
+
readonly type: "hasOne";
|
|
16
|
+
}>;
|
|
17
|
+
loggedInUser: RelationEntry<"user", {
|
|
18
|
+
readonly type: "hasOne";
|
|
19
|
+
}>;
|
|
20
|
+
cardsStatusHistory: RelationEntry<"cardsStatusHistory", {
|
|
21
|
+
readonly type: "hasMany";
|
|
22
|
+
}>;
|
|
23
|
+
cardsEffortHistory: RelationEntry<"cardsEffortHistory", {
|
|
24
|
+
readonly type: "hasMany";
|
|
25
|
+
}>;
|
|
26
|
+
cardsFinishedHistory: RelationEntry<"cardsFinishedHistory", {
|
|
27
|
+
readonly type: "hasMany";
|
|
28
|
+
}>;
|
|
29
|
+
cardsTimeToFinished: RelationEntry<"cardsTimeToFinished", {
|
|
30
|
+
readonly type: "hasMany";
|
|
31
|
+
}>;
|
|
32
|
+
}, []>;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { _rootDesc as _rootDesc$1 };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/loaders/loader-utils.ts
|
|
2
|
+
const configuredFetch = async (opts, url, init = {}) => {
|
|
3
|
+
const fetchImpl = opts.fetch || globalThis.fetch;
|
|
4
|
+
const headers = new Headers(init.headers);
|
|
5
|
+
if (opts.accessToken) headers.set("X-Auth-Token", opts.accessToken);
|
|
6
|
+
if (opts.subdomain) headers.set("X-Account", opts.subdomain);
|
|
7
|
+
if (opts.headers) Object.entries(opts.headers).forEach(([key, value]) => {
|
|
8
|
+
headers.set(key, value);
|
|
9
|
+
});
|
|
10
|
+
const fullUrl = opts.baseUrl ? `${opts.baseUrl}${url}` : url;
|
|
11
|
+
return fetchImpl(fullUrl, {
|
|
12
|
+
...init,
|
|
13
|
+
headers
|
|
14
|
+
}).then(async (r) => {
|
|
15
|
+
const content = await r.json();
|
|
16
|
+
if (r.status !== 200) throw new Error(`[${r.status}] ${JSON.stringify(content)}`);
|
|
17
|
+
return content;
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/collection-utils.ts
|
|
23
|
+
const ensureMapValue = (map, key, fallback) => {
|
|
24
|
+
const exist = map.get(key);
|
|
25
|
+
if (exist) return exist;
|
|
26
|
+
const newValue = fallback();
|
|
27
|
+
map.set(key, newValue);
|
|
28
|
+
return newValue;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { configuredFetch, ensureMapValue };
|