@codecks/fetch 0.1.4 → 0.1.6
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/README.md +317 -10
- package/dist/_exploration/api-requester.d.ts +4 -4
- package/dist/_exploration/api-requester.js +5 -4
- package/dist/_exploration/create-hooks.d.ts +5 -5
- package/dist/_exploration/create-hooks.js +193 -88
- package/dist/_exploration/loader.d.ts +2 -2
- package/dist/_exploration/loader.js +7 -2
- package/dist/_exploration/store.d.ts +4 -4
- package/dist/_exploration/store.js +84 -73
- package/dist/{_root-D1Faf8cg.d.ts → _root-rBEjvjCJ.d.ts} +1 -1
- package/dist/immediate-cache-DiH8tpJM.js +237 -0
- package/dist/{index-DUBsesYl.d.ts → index-obQtSRRg.d.ts} +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +7 -6
- package/dist/{loader-types-Dp0CSVTf.d.ts → loader-types-Y0OHlOEK.d.ts} +4 -8
- package/dist/{loader-utils-Du-CTKH-.d.ts → loader-utils-rQFSDhwf.d.ts} +2 -2
- package/dist/{query-helpers-DsMMOtSO.js → query-helpers-DgC42MEH.js} +13 -9
- package/dist/{query-type-Dx5ZCv5N.d.ts → query-type-B0L3hwCo.d.ts} +2 -2
- package/dist/{store-B6JWZnAO.d.ts → store-LSWKTuHv.d.ts} +97 -6
- package/package.json +7 -6
- package/schema/models/account.md +104 -0
- package/schema/models/accountOnboarding.md +15 -0
- package/schema/models/accountOnboardingStep.md +14 -0
- package/schema/models/accountRole.md +19 -0
- package/schema/models/accountUserAchievement.md +17 -0
- package/schema/models/accountUserSetting.md +20 -0
- package/schema/models/activeTimeTracker.md +19 -0
- package/schema/models/activity.md +30 -0
- package/schema/models/affiliateCode.md +30 -0
- package/schema/models/affiliateCodeStat.md +19 -0
- package/schema/models/app.md +10 -0
- package/schema/models/appInstallation.md +19 -0
- package/schema/models/assigneeAssignment.md +18 -0
- package/schema/models/assigneeDeckAssignment.md +20 -0
- package/schema/models/attachment.md +23 -0
- package/schema/models/autoFinishedTimeTrackingSegment.md +20 -0
- package/schema/models/card.md +82 -0
- package/schema/models/cardDiffNotification.md +22 -0
- package/schema/models/cardHistory.md +20 -0
- package/schema/models/cardOrder.md +17 -0
- package/schema/models/cardOrderInDeck.md +18 -0
- package/schema/models/cardPreset.md +19 -0
- package/schema/models/cardSubscription.md +19 -0
- package/schema/models/cardUpvote.md +21 -0
- package/schema/models/cardsEffortHistory.md +9 -0
- package/schema/models/cardsFinishedHistory.md +16 -0
- package/schema/models/cardsStatusHistory.md +9 -0
- package/schema/models/cardsTimeToFinished.md +18 -0
- package/schema/models/dailyDiscordGuildVoteMembership.md +15 -0
- package/schema/models/dailyPublicProjectMembership.md +15 -0
- package/schema/models/deck.md +53 -0
- package/schema/models/deckAssignment.md +18 -0
- package/schema/models/deckGuardian.md +17 -0
- package/schema/models/deckSubscription.md +18 -0
- package/schema/models/discordGuild.md +30 -0
- package/schema/models/discordMember.md +21 -0
- package/schema/models/discordProjectNotification.md +19 -0
- package/schema/models/discordSlashCommand.md +28 -0
- package/schema/models/dueCard.md +18 -0
- package/schema/models/file.md +26 -0
- package/schema/models/handCard.md +18 -0
- package/schema/models/integration.md +23 -0
- package/schema/models/invoice.md +21 -0
- package/schema/models/lastSeenCardUpvote.md +17 -0
- package/schema/models/milestone.md +39 -0
- package/schema/models/milestoneProgress.md +14 -0
- package/schema/models/milestoneProject.md +17 -0
- package/schema/models/pinnedMilestone.md +22 -0
- package/schema/models/project.md +58 -0
- package/schema/models/projectOrder.md +18 -0
- package/schema/models/projectSelection.md +18 -0
- package/schema/models/projectTag.md +19 -0
- package/schema/models/projectUser.md +18 -0
- package/schema/models/projectUserSetting.md +15 -0
- package/schema/models/publicProjectInfo.md +20 -0
- package/schema/models/publicProjectMembership.md +17 -0
- package/schema/models/publicProjectVisit.md +16 -0
- package/schema/models/queueEntry.md +21 -0
- package/schema/models/queueSelection.md +19 -0
- package/schema/models/release.md +12 -0
- package/schema/models/resolvable.md +32 -0
- package/schema/models/resolvableEntry.md +28 -0
- package/schema/models/resolvableEntryHistory.md +20 -0
- package/schema/models/resolvableEntryReaction.md +23 -0
- package/schema/models/resolvableNotification.md +30 -0
- package/schema/models/resolvableParticipant.md +25 -0
- package/schema/models/resolvableParticipantHistory.md +23 -0
- package/schema/models/savedSearch.md +18 -0
- package/schema/models/sprint.md +42 -0
- package/schema/models/sprintConfig.md +38 -0
- package/schema/models/sprintConfigProgress.md +14 -0
- package/schema/models/sprintProgress.md +14 -0
- package/schema/models/sprintProject.md +17 -0
- package/schema/models/stripeAccountSync.md +29 -0
- package/schema/models/timeTrackingSegment.md +24 -0
- package/schema/models/timeTrackingSum.md +18 -0
- package/schema/models/user.md +67 -0
- package/schema/models/userDismissedHint.md +16 -0
- package/schema/models/userEmail.md +18 -0
- package/schema/models/userInvitation.md +20 -0
- package/schema/models/userInviteCode.md +23 -0
- package/schema/models/userOnboarding.md +14 -0
- package/schema/models/userProjectAccess.md +17 -0
- package/schema/models/userReportEmail.md +19 -0
- package/schema/models/userReportSetting.md +23 -0
- package/schema/models/userReportToken.md +18 -0
- package/schema/models/userTag.md +18 -0
- package/schema/models/visionBoard.md +21 -0
- package/schema/models/visionBoardQuery.md +22 -0
- package/schema/models/wizard.md +19 -0
- package/schema/models/workflowItem.md +45 -0
- package/schema/models/workflowItemHistory.md +18 -0
- package/schema/overview.md +110 -0
- package/schema/query-syntax.md +155 -0
|
@@ -1,85 +1,212 @@
|
|
|
1
|
-
import { queryToKey } from "../query-helpers-
|
|
1
|
+
import { queryToKey } from "../query-helpers-DgC42MEH.js";
|
|
2
|
+
import { getFieldKey } from "../immediate-cache-DiH8tpJM.js";
|
|
2
3
|
import { use, useSyncExternalStore } from "react";
|
|
3
4
|
|
|
4
|
-
//#region src/_exploration/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
//#region src/_exploration/utils/deepUpdateTree.ts
|
|
6
|
+
/**
|
|
7
|
+
* Reconciles two arrays using key-based matching (similar to React's reconciliation).
|
|
8
|
+
* Assumes all elements have a `~key` property - caller must verify this before calling.
|
|
9
|
+
* Returns the new array with structural sharing for unchanged elements.
|
|
10
|
+
*/
|
|
11
|
+
function reconcileKeyedArray(prevArray, nextArray, update, added, removed) {
|
|
12
|
+
if (prevArray.length === nextArray.length) {
|
|
13
|
+
let keysUnchanged = true;
|
|
14
|
+
for (let i = 0; i < prevArray.length; i++) if (prevArray[i]["~key"] !== nextArray[i]["~key"]) {
|
|
15
|
+
keysUnchanged = false;
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
if (keysUnchanged) {
|
|
19
|
+
const result$1 = [];
|
|
20
|
+
let hasChanges = false;
|
|
21
|
+
for (let i = 0; i < nextArray.length; i++) {
|
|
22
|
+
const updated = update(prevArray[i], nextArray[i]);
|
|
23
|
+
result$1.push(updated);
|
|
24
|
+
if (updated !== prevArray[i]) hasChanges = true;
|
|
25
|
+
}
|
|
26
|
+
if (!hasChanges) return prevArray;
|
|
27
|
+
return result$1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const prevByKey = new Map(prevArray.map((i) => [i["~key"], i]));
|
|
31
|
+
const nextByKey = new Map(nextArray.map((i) => [i["~key"], i]));
|
|
32
|
+
const result = [];
|
|
33
|
+
for (const nextItem of nextArray) {
|
|
34
|
+
const key = nextItem["~key"];
|
|
35
|
+
if (prevByKey.has(key)) {
|
|
36
|
+
const prevItem = prevByKey.get(key);
|
|
37
|
+
const updated = update(prevItem, nextItem);
|
|
38
|
+
result.push(updated);
|
|
39
|
+
} else {
|
|
40
|
+
added.push(nextItem);
|
|
41
|
+
result.push(nextItem);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const [key, value] of prevByKey) if (!nextByKey.has(key)) removed.push(value);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
function isInstance(value) {
|
|
48
|
+
return typeof value === "object" && value !== null && "~key" in value;
|
|
49
|
+
}
|
|
50
|
+
function deepUpdateTree(prev, next) {
|
|
51
|
+
const added = [];
|
|
52
|
+
const removed = [];
|
|
53
|
+
function update(prevVal, nextVal) {
|
|
54
|
+
if (prevVal === nextVal) return prevVal;
|
|
55
|
+
if (prevVal["~key"] !== nextVal["~key"]) {
|
|
56
|
+
removed.push(prevVal);
|
|
57
|
+
added.push(nextVal);
|
|
58
|
+
return nextVal;
|
|
59
|
+
}
|
|
60
|
+
let hasChanges = false;
|
|
61
|
+
const result$1 = {
|
|
62
|
+
"~model": prevVal["~model"],
|
|
63
|
+
"~key": prevVal["~key"]
|
|
64
|
+
};
|
|
65
|
+
for (const key in nextVal) {
|
|
66
|
+
if (key === "~model" || key === "~key") continue;
|
|
67
|
+
const prevField = prevVal[key];
|
|
68
|
+
const nextField = nextVal[key];
|
|
69
|
+
if (prevField === nextField) {
|
|
70
|
+
result$1[key] = prevField;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(prevField) && Array.isArray(nextField)) {
|
|
74
|
+
if (prevField.length === 0 && nextField.length === 0) {
|
|
75
|
+
result$1[key] = prevField;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const isInstanceArray = isInstance(prevField[0]) && isInstance(nextField[0]);
|
|
79
|
+
if (isInstanceArray) {
|
|
80
|
+
const reconciled = reconcileKeyedArray(prevField, nextField, update, added, removed);
|
|
81
|
+
result$1[key] = reconciled;
|
|
82
|
+
if (reconciled !== prevField) hasChanges = true;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
result$1[key] = nextField;
|
|
86
|
+
if (prevField !== nextField) hasChanges = true;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (isInstance(prevField) && isInstance(nextField)) {
|
|
90
|
+
const updated = update(prevField, nextField);
|
|
91
|
+
result$1[key] = updated;
|
|
92
|
+
if (updated !== prevField) hasChanges = true;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
result$1[key] = nextField;
|
|
96
|
+
if (prevField !== nextField) hasChanges = true;
|
|
97
|
+
}
|
|
98
|
+
return hasChanges ? result$1 : prevVal;
|
|
99
|
+
}
|
|
100
|
+
const result = update(prev, next);
|
|
11
101
|
return {
|
|
102
|
+
result,
|
|
12
103
|
added,
|
|
13
|
-
removed
|
|
14
|
-
nextTrackedData: {
|
|
15
|
-
partKeys: nextPartKeys,
|
|
16
|
-
value: nextStorePayload.value
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
const createStoreQuery = (opts) => {
|
|
21
|
-
const { onDispose, updateDataListener, store, q, ids, model } = opts;
|
|
22
|
-
const queryListeners = new Set();
|
|
23
|
-
let trackedData = {
|
|
24
|
-
partKeys: [],
|
|
25
|
-
value: null
|
|
104
|
+
removed
|
|
26
105
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/_exploration/user-query-manager.ts
|
|
110
|
+
/**
|
|
111
|
+
* Extracts all field keys from a data tree for subscription tracking.
|
|
112
|
+
* Returns a set of field keys in the format "model:id:field"
|
|
113
|
+
*/
|
|
114
|
+
function extractFieldKeys(data) {
|
|
115
|
+
const fields = new Set();
|
|
116
|
+
function traverse(obj) {
|
|
117
|
+
if (!obj || typeof obj !== "object") return;
|
|
118
|
+
if (obj["~model"] && obj["~key"]) {
|
|
119
|
+
const model = obj["~model"];
|
|
120
|
+
const key = obj["~key"];
|
|
121
|
+
for (const field in obj) {
|
|
122
|
+
if (field.startsWith("~")) continue;
|
|
123
|
+
fields.add(getFieldKey(model, key, field));
|
|
124
|
+
const value = obj[field];
|
|
125
|
+
if (Array.isArray(value)) value.forEach(traverse);
|
|
126
|
+
else traverse(value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
traverse(data);
|
|
131
|
+
return fields;
|
|
132
|
+
}
|
|
133
|
+
var UserQuery = class {
|
|
134
|
+
queryListeners = new Set();
|
|
135
|
+
subscribedFields = new Map();
|
|
136
|
+
currentData = null;
|
|
137
|
+
userQueryResult;
|
|
138
|
+
constructor(opts) {
|
|
139
|
+
this.opts = opts;
|
|
140
|
+
this.loadData();
|
|
141
|
+
}
|
|
142
|
+
reconcileNextResult(nextData) {
|
|
143
|
+
if (!this.currentData) {
|
|
144
|
+
this.currentData = nextData;
|
|
145
|
+
const fieldKeys = extractFieldKeys(nextData);
|
|
146
|
+
for (const fieldKey of fieldKeys) this.subscribedFields.set(fieldKey, this.opts.store.subscribeToField(fieldKey, this.onFieldChanged));
|
|
147
|
+
} else {
|
|
148
|
+
const { result, added, removed } = deepUpdateTree(this.currentData, nextData);
|
|
149
|
+
if (result === this.currentData) return;
|
|
150
|
+
for (const instance of removed) {
|
|
151
|
+
const fieldKeys = extractFieldKeys(instance);
|
|
152
|
+
for (const fieldKey of fieldKeys) {
|
|
153
|
+
const unsubscribe = this.subscribedFields.get(fieldKey);
|
|
154
|
+
if (!unsubscribe) throw new Error(`Unsubscribe not found for field ${fieldKey}`);
|
|
155
|
+
unsubscribe();
|
|
156
|
+
this.subscribedFields.delete(fieldKey);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const instance of added) {
|
|
160
|
+
const fieldKeys = extractFieldKeys(instance);
|
|
161
|
+
for (const fieldKey of fieldKeys) this.subscribedFields.set(fieldKey, this.opts.store.subscribeToField(fieldKey, this.onFieldChanged));
|
|
162
|
+
}
|
|
163
|
+
this.currentData = result;
|
|
164
|
+
this.notify();
|
|
36
165
|
}
|
|
166
|
+
return this.currentData;
|
|
167
|
+
}
|
|
168
|
+
onFieldChanged = () => {
|
|
169
|
+
this.loadData();
|
|
37
170
|
};
|
|
38
|
-
|
|
39
|
-
|
|
171
|
+
notify = () => {
|
|
172
|
+
this.queryListeners.forEach((listener) => listener());
|
|
40
173
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const storeResult = store.loadData(model, ids, q);
|
|
44
|
-
if (storeResult.state === "pending") userQueryResult = {
|
|
174
|
+
loadData() {
|
|
175
|
+
const { store, model, ids, q } = this.opts;
|
|
176
|
+
const storeResult = store.loadData(model, ids, q, true);
|
|
177
|
+
if (storeResult.state === "pending") this.userQueryResult = {
|
|
45
178
|
state: "pending",
|
|
46
|
-
promise: storeResult.promise.then((
|
|
47
|
-
reconcileNextResult(
|
|
48
|
-
|
|
179
|
+
promise: storeResult.promise.then((value) => {
|
|
180
|
+
const result = this.reconcileNextResult(value);
|
|
181
|
+
this.userQueryResult = {
|
|
182
|
+
state: "resolved",
|
|
183
|
+
value: result
|
|
184
|
+
};
|
|
185
|
+
return result;
|
|
49
186
|
})
|
|
50
187
|
};
|
|
51
|
-
else {
|
|
52
|
-
|
|
53
|
-
reconcileNextResult(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
loadData();
|
|
61
|
-
const subscribe = (listener) => {
|
|
62
|
-
queryListeners.add(listener);
|
|
188
|
+
else this.userQueryResult = {
|
|
189
|
+
state: "resolved",
|
|
190
|
+
value: this.reconcileNextResult(storeResult.payload)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
subscribe = (listener) => {
|
|
194
|
+
this.queryListeners.add(listener);
|
|
63
195
|
return () => {
|
|
64
|
-
queryListeners.delete(listener);
|
|
65
|
-
if (queryListeners.size === 0)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
196
|
+
this.queryListeners.delete(listener);
|
|
197
|
+
if (this.queryListeners.size === 0) {
|
|
198
|
+
this.opts.onDispose();
|
|
199
|
+
for (const unsub of this.subscribedFields.values()) unsub();
|
|
200
|
+
this.subscribedFields.clear();
|
|
201
|
+
}
|
|
70
202
|
};
|
|
71
203
|
};
|
|
72
|
-
|
|
73
|
-
return userQueryResult;
|
|
74
|
-
};
|
|
75
|
-
return {
|
|
76
|
-
subscribe,
|
|
77
|
-
get
|
|
204
|
+
getSnapshot = () => {
|
|
205
|
+
return this.userQueryResult;
|
|
78
206
|
};
|
|
79
207
|
};
|
|
80
208
|
var UserQueryManager = class {
|
|
81
209
|
queryCache = new Map();
|
|
82
|
-
activeParts = new Map();
|
|
83
210
|
constructor(store) {
|
|
84
211
|
this.store = store;
|
|
85
212
|
}
|
|
@@ -91,41 +218,19 @@ var UserQueryManager = class {
|
|
|
91
218
|
});
|
|
92
219
|
let query = this.queryCache.get(cacheKey);
|
|
93
220
|
if (!query) {
|
|
94
|
-
query =
|
|
221
|
+
query = new UserQuery({
|
|
95
222
|
model,
|
|
96
223
|
ids,
|
|
97
224
|
q,
|
|
98
225
|
store: this.store,
|
|
99
226
|
onDispose: () => {
|
|
100
227
|
this.queryCache.delete(cacheKey);
|
|
101
|
-
},
|
|
102
|
-
updateDataListener: ({ added, removed }, notify) => {
|
|
103
|
-
added.forEach((partKey) => {
|
|
104
|
-
let partListeners = this.activeParts.get(partKey);
|
|
105
|
-
if (!partListeners) {
|
|
106
|
-
partListeners = new Set();
|
|
107
|
-
this.activeParts.set(partKey, partListeners);
|
|
108
|
-
}
|
|
109
|
-
partListeners.add(notify);
|
|
110
|
-
});
|
|
111
|
-
removed.forEach((partKey) => {
|
|
112
|
-
const partListeners = this.activeParts.get(partKey);
|
|
113
|
-
if (partListeners) {
|
|
114
|
-
partListeners.delete(notify);
|
|
115
|
-
if (partListeners.size === 0) this.activeParts.delete(partKey);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
228
|
}
|
|
119
229
|
});
|
|
120
230
|
this.queryCache.set(cacheKey, query);
|
|
121
231
|
}
|
|
122
232
|
return query;
|
|
123
233
|
}
|
|
124
|
-
onInvalidation(partKeys) {
|
|
125
|
-
const notifyFns = new Set();
|
|
126
|
-
for (const partKey of partKeys) this.activeParts.get(partKey)?.forEach((fn) => notifyFns.add(fn));
|
|
127
|
-
notifyFns.forEach((fn) => fn());
|
|
128
|
-
}
|
|
129
234
|
};
|
|
130
235
|
|
|
131
236
|
//#endregion
|
|
@@ -134,7 +239,7 @@ const createHooks = (store) => {
|
|
|
134
239
|
const queryManager = new UserQueryManager(store);
|
|
135
240
|
const useQueryManager = (model, ids, q) => {
|
|
136
241
|
const queryObj = queryManager.getQuery(model, ids, q);
|
|
137
|
-
const val = useSyncExternalStore(queryObj.subscribe, queryObj.
|
|
242
|
+
const val = useSyncExternalStore(queryObj.subscribe, queryObj.getSnapshot);
|
|
138
243
|
if (val.state === "pending") return use(val.promise);
|
|
139
244
|
return val.value;
|
|
140
245
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "../query-type-
|
|
2
|
-
import { BaseLoader, BaseRequester, MissingDataRequest, OnLoadedFn } from "../loader-types-
|
|
1
|
+
import "../query-type-B0L3hwCo.js";
|
|
2
|
+
import { BaseLoader, BaseRequester, MissingDataRequest, OnLoadedFn } from "../loader-types-Y0OHlOEK.js";
|
|
3
3
|
|
|
4
4
|
//#region src/_exploration/loader.d.ts
|
|
5
5
|
type DataRequestWithKey = {
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { serializeModel } from "../query-helpers-DgC42MEH.js";
|
|
2
2
|
|
|
3
3
|
//#region src/_exploration/loader.ts
|
|
4
4
|
const notYetLoaded = () => {
|
|
5
5
|
throw new Error("Not yet loaded");
|
|
6
6
|
};
|
|
7
7
|
const augmentRequest = (req) => {
|
|
8
|
-
const
|
|
8
|
+
const getFieldKey = () => {
|
|
9
|
+
if (req.type === "field") return req.field;
|
|
10
|
+
else if (req.contents.asField) return req.relKey;
|
|
11
|
+
else return JSON.stringify(serializeModel(req.contents));
|
|
12
|
+
};
|
|
13
|
+
const key = `${req.model}:${req.id}:${getFieldKey()}`;
|
|
9
14
|
return {
|
|
10
15
|
key,
|
|
11
16
|
request: req
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "../query-type-
|
|
2
|
-
import "../index-
|
|
3
|
-
import "../loader-types-
|
|
4
|
-
import { Store } from "../store-
|
|
1
|
+
import "../query-type-B0L3hwCo.js";
|
|
2
|
+
import "../index-obQtSRRg.js";
|
|
3
|
+
import "../loader-types-Y0OHlOEK.js";
|
|
4
|
+
import { Store } from "../store-LSWKTuHv.js";
|
|
5
5
|
export { Store };
|
|
@@ -1,80 +1,46 @@
|
|
|
1
|
-
import { getRelKey, makeRelQuerySerializable, modelMap } from "../query-helpers-
|
|
1
|
+
import { getRelKey, makeRelQuerySerializable, modelMap } from "../query-helpers-DgC42MEH.js";
|
|
2
|
+
import { ImmediateCache } from "../immediate-cache-DiH8tpJM.js";
|
|
2
3
|
|
|
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
4
|
//#region src/_exploration/store.ts
|
|
5
|
+
const getQueryDep = (_q) => {
|
|
6
|
+
return { keys: [] };
|
|
7
|
+
};
|
|
46
8
|
const calcPartKeys = (model, id, field) => {
|
|
47
9
|
return [`${model}:${id}:${field}`];
|
|
48
10
|
};
|
|
49
11
|
var Store = class {
|
|
50
12
|
cache = new ImmediateCache();
|
|
51
13
|
modelMap = modelMap;
|
|
14
|
+
requestedRelationKeys = new Map();
|
|
52
15
|
constructor(loader) {
|
|
53
16
|
this.loader = loader;
|
|
54
17
|
loader.setOnLoaded((model, key, res) => this.onLoaded(model, key, res));
|
|
55
18
|
}
|
|
19
|
+
subscribeToField(fieldKey, notifyFn) {
|
|
20
|
+
return this.cache.subscribe(fieldKey, notifyFn);
|
|
21
|
+
}
|
|
22
|
+
invalidate(partKeyPatterns) {
|
|
23
|
+
const subscribedFieldRequests = this.cache.markDirty(partKeyPatterns);
|
|
24
|
+
if (subscribedFieldRequests.length === 0) return;
|
|
25
|
+
this.loader.loadBatch(subscribedFieldRequests);
|
|
26
|
+
}
|
|
56
27
|
loadData(model, ids, q, acceptDirty, iteration = 0) {
|
|
57
28
|
const desc = this.modelMap[model];
|
|
58
29
|
if (!desc) throw new Error(`Model ${model} not found`);
|
|
59
30
|
const missingRequests = [];
|
|
60
31
|
const resultObj = {};
|
|
61
|
-
const allPartKeys = [];
|
|
62
32
|
let allPresent = true;
|
|
63
33
|
for (const id of ids) {
|
|
64
34
|
const result = this.checkQueryRecursive(desc, model, id, q, missingRequests, acceptDirty);
|
|
65
35
|
if (!result.allPresent) allPresent = false;
|
|
66
36
|
resultObj[id] = result.data;
|
|
67
|
-
allPartKeys.push(...result.partKeys);
|
|
68
37
|
}
|
|
69
38
|
if (!allPresent && iteration > 5) throw new Error(`Can't seem to load ${JSON.stringify(missingRequests)} after 5 iterations`);
|
|
70
39
|
if (allPresent) {
|
|
71
40
|
if (missingRequests.length > 0) this.loader.loadBatch(missingRequests);
|
|
72
41
|
return {
|
|
73
42
|
state: "resolved",
|
|
74
|
-
payload:
|
|
75
|
-
value: resultObj,
|
|
76
|
-
partKeys: allPartKeys
|
|
77
|
-
}
|
|
43
|
+
payload: resultObj
|
|
78
44
|
};
|
|
79
45
|
}
|
|
80
46
|
const promise = this.loader.loadBatch(missingRequests).then(() => {
|
|
@@ -87,19 +53,27 @@ var Store = class {
|
|
|
87
53
|
};
|
|
88
54
|
}
|
|
89
55
|
onLoaded(model, key, partialInstance) {
|
|
90
|
-
const withPartKeys = Object.fromEntries(Object.entries(partialInstance).map(([field, value]) =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
56
|
+
const withPartKeys = Object.fromEntries(Object.entries(partialInstance).map(([field, value]) => {
|
|
57
|
+
const relDeps = this.requestedRelationKeys.get(`${model}:${field}`);
|
|
58
|
+
console.log(`${model}:${field}`, relDeps);
|
|
59
|
+
return [field, {
|
|
60
|
+
value,
|
|
61
|
+
partKeys: relDeps ? [...relDeps.keys, ...calcPartKeys(model, key, field)] : calcPartKeys(model, key, field),
|
|
62
|
+
type: relDeps ? "relation" : "field"
|
|
63
|
+
}];
|
|
64
|
+
}));
|
|
65
|
+
const fieldSubs = this.cache.set(model, key, withPartKeys, { state: "fresh" });
|
|
66
|
+
new Set(fieldSubs).forEach((fn) => fn());
|
|
95
67
|
}
|
|
96
68
|
/**
|
|
97
69
|
* Recursively check if query data is in cache
|
|
98
70
|
* Accumulates missing requests in the missingRequests array
|
|
99
71
|
*/
|
|
100
72
|
checkQueryRecursive(desc, model, id, query, missingRequests, acceptDirty) {
|
|
101
|
-
const data = {}
|
|
102
|
-
|
|
73
|
+
const data = model === "_root" ? {} : {
|
|
74
|
+
"~model": model,
|
|
75
|
+
"~key": id
|
|
76
|
+
};
|
|
103
77
|
let allPresent = true;
|
|
104
78
|
if (query.fields) for (const field of query.fields) {
|
|
105
79
|
const cachedValue = this.cache.get(model, id, field);
|
|
@@ -111,10 +85,8 @@ var Store = class {
|
|
|
111
85
|
field
|
|
112
86
|
});
|
|
113
87
|
const acceptsData = cachedValue && (cachedValue.meta.state === "fresh" || acceptDirty);
|
|
114
|
-
if (acceptsData)
|
|
115
|
-
|
|
116
|
-
partKeys.push(...cachedValue.partKeys);
|
|
117
|
-
} else allPresent = false;
|
|
88
|
+
if (acceptsData) data[field] = cachedValue.value;
|
|
89
|
+
else allPresent = false;
|
|
118
90
|
}
|
|
119
91
|
if (query.relations) for (const [relationName, rawRelationQuery] of Object.entries(query.relations)) {
|
|
120
92
|
if (!rawRelationQuery) continue;
|
|
@@ -123,28 +95,33 @@ var Store = class {
|
|
|
123
95
|
const relationQueryList = Array.isArray(rawRelationQuery) ? rawRelationQuery : [rawRelationQuery];
|
|
124
96
|
for (const relQuery of relationQueryList) {
|
|
125
97
|
const relKey = getRelKey(relQuery, relationName);
|
|
98
|
+
this.requestedRelationKeys.set(`${model}:${relKey}`, getQueryDep(relQuery));
|
|
126
99
|
const cachedValue = this.cache.get(model, id, relKey);
|
|
127
100
|
const needsRequest = !cachedValue || cachedValue.meta.state === "dirty";
|
|
128
|
-
if (needsRequest)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
101
|
+
if (needsRequest) {
|
|
102
|
+
const contents = makeRelQuerySerializable(relQuery);
|
|
103
|
+
missingRequests.push({
|
|
104
|
+
type: "relation",
|
|
105
|
+
model,
|
|
106
|
+
id,
|
|
107
|
+
relKey,
|
|
108
|
+
contents
|
|
109
|
+
});
|
|
110
|
+
}
|
|
135
111
|
const acceptsData = cachedValue && (cachedValue.meta.state === "fresh" || acceptDirty);
|
|
136
112
|
if (acceptsData) {
|
|
137
113
|
if (!relatedModelDesc) throw new Error(`Model ${relatedModel} not found`);
|
|
138
114
|
const fieldName = relQuery.as || relationName;
|
|
139
|
-
if (
|
|
140
|
-
|
|
115
|
+
if (relQuery.type === "count" || relQuery.type === "exists") data[fieldName] = cachedValue.value;
|
|
116
|
+
else if (Array.isArray(cachedValue.value)) {
|
|
117
|
+
const relationResults = cachedValue.value.map((key) => this.checkQueryRecursive(relatedModelDesc, relatedModel, `${key}`, relQuery, missingRequests, acceptDirty));
|
|
118
|
+
data[`~${fieldName}`] = cachedValue.value.map((v) => `${v}`);
|
|
141
119
|
data[fieldName] = relationResults.map((result) => result.data);
|
|
142
|
-
partKeys.push(...relationResults.flatMap((result) => result.partKeys));
|
|
143
120
|
if (relationResults.some((result) => !result.allPresent)) allPresent = false;
|
|
144
121
|
} else {
|
|
145
|
-
const relationResult = this.checkQueryRecursive(relatedModelDesc, relatedModel, cachedValue.value
|
|
122
|
+
const relationResult = this.checkQueryRecursive(relatedModelDesc, relatedModel, `${cachedValue.value}`, relQuery, missingRequests, acceptDirty);
|
|
123
|
+
data[`~${fieldName}`] = cachedValue.value != null ? `${cachedValue.value}` : null;
|
|
146
124
|
data[fieldName] = relationResult.data;
|
|
147
|
-
partKeys.push(...relationResult.partKeys);
|
|
148
125
|
if (!relationResult.allPresent) allPresent = false;
|
|
149
126
|
}
|
|
150
127
|
} else allPresent = false;
|
|
@@ -152,10 +129,44 @@ var Store = class {
|
|
|
152
129
|
}
|
|
153
130
|
return {
|
|
154
131
|
data,
|
|
155
|
-
partKeys,
|
|
156
132
|
allPresent
|
|
157
133
|
};
|
|
158
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Execute a mutation with optimistic updates
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* await store.mutate(
|
|
141
|
+
* () => api.updateCard('123', {title: 'New Title'}),
|
|
142
|
+
* {
|
|
143
|
+
* onMutate: (layer) => {
|
|
144
|
+
* layer.setField('Card', '123', 'title', 'New Title');
|
|
145
|
+
* },
|
|
146
|
+
* onSuccess: () => {
|
|
147
|
+
* // Optional: invalidate related queries
|
|
148
|
+
* store.invalidate(['Card:123:title']);
|
|
149
|
+
* }
|
|
150
|
+
* }
|
|
151
|
+
* );
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
mutate(mutationFn, options = {}) {
|
|
155
|
+
const promise = mutationFn();
|
|
156
|
+
if (options.onMutate) try {
|
|
157
|
+
this.cache.createOptimisticLayer(promise, options.onMutate);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error("Error in onMutate:", error);
|
|
160
|
+
}
|
|
161
|
+
promise.then((data) => {
|
|
162
|
+
options.onSuccess?.(data);
|
|
163
|
+
return data;
|
|
164
|
+
}).catch((error) => {
|
|
165
|
+
options.onError?.(error);
|
|
166
|
+
throw error;
|
|
167
|
+
});
|
|
168
|
+
return promise;
|
|
169
|
+
}
|
|
159
170
|
};
|
|
160
171
|
|
|
161
172
|
//#endregion
|