@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.
@@ -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,4 @@
1
+ import "../query-type-MH8IxqbW.js";
2
+ import "../loader-types-DJdGWL4B.js";
3
+ import { Store } from "../store-mAj3G67A.js";
4
+ export { Store };
@@ -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 };