@codecks/fetch 0.1.5 → 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 +76 -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 +3 -3
- 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 +4 -3
- 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,11 +53,17 @@ 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
|
|
@@ -102,7 +74,6 @@ var Store = class {
|
|
|
102
74
|
"~model": model,
|
|
103
75
|
"~key": id
|
|
104
76
|
};
|
|
105
|
-
const partKeys = [];
|
|
106
77
|
let allPresent = true;
|
|
107
78
|
if (query.fields) for (const field of query.fields) {
|
|
108
79
|
const cachedValue = this.cache.get(model, id, field);
|
|
@@ -114,10 +85,8 @@ var Store = class {
|
|
|
114
85
|
field
|
|
115
86
|
});
|
|
116
87
|
const acceptsData = cachedValue && (cachedValue.meta.state === "fresh" || acceptDirty);
|
|
117
|
-
if (acceptsData)
|
|
118
|
-
|
|
119
|
-
partKeys.push(...cachedValue.partKeys);
|
|
120
|
-
} else allPresent = false;
|
|
88
|
+
if (acceptsData) data[field] = cachedValue.value;
|
|
89
|
+
else allPresent = false;
|
|
121
90
|
}
|
|
122
91
|
if (query.relations) for (const [relationName, rawRelationQuery] of Object.entries(query.relations)) {
|
|
123
92
|
if (!rawRelationQuery) continue;
|
|
@@ -126,33 +95,33 @@ var Store = class {
|
|
|
126
95
|
const relationQueryList = Array.isArray(rawRelationQuery) ? rawRelationQuery : [rawRelationQuery];
|
|
127
96
|
for (const relQuery of relationQueryList) {
|
|
128
97
|
const relKey = getRelKey(relQuery, relationName);
|
|
98
|
+
this.requestedRelationKeys.set(`${model}:${relKey}`, getQueryDep(relQuery));
|
|
129
99
|
const cachedValue = this.cache.get(model, id, relKey);
|
|
130
100
|
const needsRequest = !cachedValue || cachedValue.meta.state === "dirty";
|
|
131
|
-
if (needsRequest)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
101
|
+
if (needsRequest) {
|
|
102
|
+
const contents = makeRelQuerySerializable(relQuery);
|
|
103
|
+
missingRequests.push({
|
|
104
|
+
type: "relation",
|
|
105
|
+
model,
|
|
106
|
+
id,
|
|
107
|
+
relKey,
|
|
108
|
+
contents
|
|
109
|
+
});
|
|
110
|
+
}
|
|
138
111
|
const acceptsData = cachedValue && (cachedValue.meta.state === "fresh" || acceptDirty);
|
|
139
112
|
if (acceptsData) {
|
|
140
113
|
if (!relatedModelDesc) throw new Error(`Model ${relatedModel} not found`);
|
|
141
114
|
const fieldName = relQuery.as || relationName;
|
|
142
|
-
if (relQuery.type === "count" || relQuery.type === "exists")
|
|
143
|
-
|
|
144
|
-
partKeys.push(...cachedValue.partKeys);
|
|
145
|
-
} else if (Array.isArray(cachedValue.value)) {
|
|
115
|
+
if (relQuery.type === "count" || relQuery.type === "exists") data[fieldName] = cachedValue.value;
|
|
116
|
+
else if (Array.isArray(cachedValue.value)) {
|
|
146
117
|
const relationResults = cachedValue.value.map((key) => this.checkQueryRecursive(relatedModelDesc, relatedModel, `${key}`, relQuery, missingRequests, acceptDirty));
|
|
147
118
|
data[`~${fieldName}`] = cachedValue.value.map((v) => `${v}`);
|
|
148
119
|
data[fieldName] = relationResults.map((result) => result.data);
|
|
149
|
-
partKeys.push(...relationResults.flatMap((result) => result.partKeys));
|
|
150
120
|
if (relationResults.some((result) => !result.allPresent)) allPresent = false;
|
|
151
121
|
} else {
|
|
152
122
|
const relationResult = this.checkQueryRecursive(relatedModelDesc, relatedModel, `${cachedValue.value}`, relQuery, missingRequests, acceptDirty);
|
|
153
123
|
data[`~${fieldName}`] = cachedValue.value != null ? `${cachedValue.value}` : null;
|
|
154
124
|
data[fieldName] = relationResult.data;
|
|
155
|
-
partKeys.push(...relationResult.partKeys);
|
|
156
125
|
if (!relationResult.allPresent) allPresent = false;
|
|
157
126
|
}
|
|
158
127
|
} else allPresent = false;
|
|
@@ -160,10 +129,44 @@ var Store = class {
|
|
|
160
129
|
}
|
|
161
130
|
return {
|
|
162
131
|
data,
|
|
163
|
-
partKeys,
|
|
164
132
|
allPresent
|
|
165
133
|
};
|
|
166
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
|
+
}
|
|
167
170
|
};
|
|
168
171
|
|
|
169
172
|
//#endregion
|