@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.
Files changed (114) hide show
  1. package/README.md +317 -10
  2. package/dist/_exploration/api-requester.d.ts +4 -4
  3. package/dist/_exploration/api-requester.js +5 -4
  4. package/dist/_exploration/create-hooks.d.ts +5 -5
  5. package/dist/_exploration/create-hooks.js +193 -88
  6. package/dist/_exploration/loader.d.ts +2 -2
  7. package/dist/_exploration/loader.js +7 -2
  8. package/dist/_exploration/store.d.ts +4 -4
  9. package/dist/_exploration/store.js +84 -73
  10. package/dist/{_root-D1Faf8cg.d.ts → _root-rBEjvjCJ.d.ts} +1 -1
  11. package/dist/immediate-cache-DiH8tpJM.js +237 -0
  12. package/dist/{index-DUBsesYl.d.ts → index-obQtSRRg.d.ts} +1 -1
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +7 -6
  15. package/dist/{loader-types-Dp0CSVTf.d.ts → loader-types-Y0OHlOEK.d.ts} +4 -8
  16. package/dist/{loader-utils-Du-CTKH-.d.ts → loader-utils-rQFSDhwf.d.ts} +2 -2
  17. package/dist/{query-helpers-DsMMOtSO.js → query-helpers-DgC42MEH.js} +13 -9
  18. package/dist/{query-type-Dx5ZCv5N.d.ts → query-type-B0L3hwCo.d.ts} +2 -2
  19. package/dist/{store-B6JWZnAO.d.ts → store-LSWKTuHv.d.ts} +97 -6
  20. package/package.json +7 -6
  21. package/schema/models/account.md +104 -0
  22. package/schema/models/accountOnboarding.md +15 -0
  23. package/schema/models/accountOnboardingStep.md +14 -0
  24. package/schema/models/accountRole.md +19 -0
  25. package/schema/models/accountUserAchievement.md +17 -0
  26. package/schema/models/accountUserSetting.md +20 -0
  27. package/schema/models/activeTimeTracker.md +19 -0
  28. package/schema/models/activity.md +30 -0
  29. package/schema/models/affiliateCode.md +30 -0
  30. package/schema/models/affiliateCodeStat.md +19 -0
  31. package/schema/models/app.md +10 -0
  32. package/schema/models/appInstallation.md +19 -0
  33. package/schema/models/assigneeAssignment.md +18 -0
  34. package/schema/models/assigneeDeckAssignment.md +20 -0
  35. package/schema/models/attachment.md +23 -0
  36. package/schema/models/autoFinishedTimeTrackingSegment.md +20 -0
  37. package/schema/models/card.md +82 -0
  38. package/schema/models/cardDiffNotification.md +22 -0
  39. package/schema/models/cardHistory.md +20 -0
  40. package/schema/models/cardOrder.md +17 -0
  41. package/schema/models/cardOrderInDeck.md +18 -0
  42. package/schema/models/cardPreset.md +19 -0
  43. package/schema/models/cardSubscription.md +19 -0
  44. package/schema/models/cardUpvote.md +21 -0
  45. package/schema/models/cardsEffortHistory.md +9 -0
  46. package/schema/models/cardsFinishedHistory.md +16 -0
  47. package/schema/models/cardsStatusHistory.md +9 -0
  48. package/schema/models/cardsTimeToFinished.md +18 -0
  49. package/schema/models/dailyDiscordGuildVoteMembership.md +15 -0
  50. package/schema/models/dailyPublicProjectMembership.md +15 -0
  51. package/schema/models/deck.md +53 -0
  52. package/schema/models/deckAssignment.md +18 -0
  53. package/schema/models/deckGuardian.md +17 -0
  54. package/schema/models/deckSubscription.md +18 -0
  55. package/schema/models/discordGuild.md +30 -0
  56. package/schema/models/discordMember.md +21 -0
  57. package/schema/models/discordProjectNotification.md +19 -0
  58. package/schema/models/discordSlashCommand.md +28 -0
  59. package/schema/models/dueCard.md +18 -0
  60. package/schema/models/file.md +26 -0
  61. package/schema/models/handCard.md +18 -0
  62. package/schema/models/integration.md +23 -0
  63. package/schema/models/invoice.md +21 -0
  64. package/schema/models/lastSeenCardUpvote.md +17 -0
  65. package/schema/models/milestone.md +39 -0
  66. package/schema/models/milestoneProgress.md +14 -0
  67. package/schema/models/milestoneProject.md +17 -0
  68. package/schema/models/pinnedMilestone.md +22 -0
  69. package/schema/models/project.md +58 -0
  70. package/schema/models/projectOrder.md +18 -0
  71. package/schema/models/projectSelection.md +18 -0
  72. package/schema/models/projectTag.md +19 -0
  73. package/schema/models/projectUser.md +18 -0
  74. package/schema/models/projectUserSetting.md +15 -0
  75. package/schema/models/publicProjectInfo.md +20 -0
  76. package/schema/models/publicProjectMembership.md +17 -0
  77. package/schema/models/publicProjectVisit.md +16 -0
  78. package/schema/models/queueEntry.md +21 -0
  79. package/schema/models/queueSelection.md +19 -0
  80. package/schema/models/release.md +12 -0
  81. package/schema/models/resolvable.md +32 -0
  82. package/schema/models/resolvableEntry.md +28 -0
  83. package/schema/models/resolvableEntryHistory.md +20 -0
  84. package/schema/models/resolvableEntryReaction.md +23 -0
  85. package/schema/models/resolvableNotification.md +30 -0
  86. package/schema/models/resolvableParticipant.md +25 -0
  87. package/schema/models/resolvableParticipantHistory.md +23 -0
  88. package/schema/models/savedSearch.md +18 -0
  89. package/schema/models/sprint.md +42 -0
  90. package/schema/models/sprintConfig.md +38 -0
  91. package/schema/models/sprintConfigProgress.md +14 -0
  92. package/schema/models/sprintProgress.md +14 -0
  93. package/schema/models/sprintProject.md +17 -0
  94. package/schema/models/stripeAccountSync.md +29 -0
  95. package/schema/models/timeTrackingSegment.md +24 -0
  96. package/schema/models/timeTrackingSum.md +18 -0
  97. package/schema/models/user.md +67 -0
  98. package/schema/models/userDismissedHint.md +16 -0
  99. package/schema/models/userEmail.md +18 -0
  100. package/schema/models/userInvitation.md +20 -0
  101. package/schema/models/userInviteCode.md +23 -0
  102. package/schema/models/userOnboarding.md +14 -0
  103. package/schema/models/userProjectAccess.md +17 -0
  104. package/schema/models/userReportEmail.md +19 -0
  105. package/schema/models/userReportSetting.md +23 -0
  106. package/schema/models/userReportToken.md +18 -0
  107. package/schema/models/userTag.md +18 -0
  108. package/schema/models/visionBoard.md +21 -0
  109. package/schema/models/visionBoardQuery.md +22 -0
  110. package/schema/models/wizard.md +19 -0
  111. package/schema/models/workflowItem.md +45 -0
  112. package/schema/models/workflowItemHistory.md +18 -0
  113. package/schema/overview.md +110 -0
  114. package/schema/query-syntax.md +155 -0
@@ -1,85 +1,212 @@
1
- import { queryToKey } from "../query-helpers-DsMMOtSO.js";
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/user-query-manager.ts
5
- const diffResults = (prevTrackedData, nextStorePayload) => {
6
- const nextPartKeys = nextStorePayload.partKeys;
7
- const prevSet = new Set(prevTrackedData.partKeys);
8
- const nextSet = new Set(nextPartKeys);
9
- const added = nextPartKeys.filter((key) => !prevSet.has(key));
10
- const removed = prevTrackedData.partKeys.filter((key) => !nextSet.has(key));
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
- const reconcileNextResult = (storePayload) => {
28
- const { added, removed, nextTrackedData } = diffResults(trackedData, storePayload);
29
- if (added.length || removed.length) {
30
- trackedData = nextTrackedData;
31
- updateDataListener({
32
- added,
33
- removed
34
- }, notify);
35
- queryListeners.forEach((listener) => listener());
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
- const notify = () => {
39
- loadData();
171
+ notify = () => {
172
+ this.queryListeners.forEach((listener) => listener());
40
173
  };
41
- let userQueryResult;
42
- const loadData = () => {
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((storePayload) => {
47
- reconcileNextResult(storePayload);
48
- return storePayload.value;
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
- const storePayload = storeResult.payload;
53
- reconcileNextResult(storePayload);
54
- userQueryResult = {
55
- state: "resolved",
56
- value: storePayload.value
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) onDispose();
66
- updateDataListener({
67
- added: [],
68
- removed: trackedData.partKeys
69
- }, notify);
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
- const get = () => {
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 = createStoreQuery({
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.get);
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-Dx5ZCv5N.js";
2
- import { BaseLoader, BaseRequester, MissingDataRequest, OnLoadedFn } from "../loader-types-Dp0CSVTf.js";
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 { queryToKey } from "../query-helpers-DsMMOtSO.js";
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 key = `${req.model}:${req.id}:${req.type === "field" ? req.field : `${req.relKey}:${queryToKey(req.query)}`}`;
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-Dx5ZCv5N.js";
2
- import "../index-DUBsesYl.js";
3
- import "../loader-types-Dp0CSVTf.js";
4
- import { Store } from "../store-B6JWZnAO.js";
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-DsMMOtSO.js";
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]) => [field, {
91
- value,
92
- partKeys: calcPartKeys(model, key, field)
93
- }]));
94
- this.cache.set(model, key, withPartKeys, { state: "fresh" });
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
- const partKeys = [];
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
- data[field] = cachedValue.value;
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) missingRequests.push({
129
- type: "relation",
130
- model,
131
- id,
132
- relKey,
133
- query: makeRelQuerySerializable(relQuery)
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 (Array.isArray(cachedValue.value)) {
140
- const relationResults = cachedValue.value.map((key) => this.checkQueryRecursive(relatedModelDesc, relatedModel, key, relQuery, missingRequests, acceptDirty));
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, relQuery, missingRequests, acceptDirty);
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
@@ -1,4 +1,4 @@
1
- import { ModelDesc, RelationEntry } from "./query-type-Dx5ZCv5N.js";
1
+ import { ModelDesc, RelationEntry } from "./query-type-B0L3hwCo.js";
2
2
 
3
3
  //#region src/models/_root.d.ts
4
4
  declare const _rootDesc: ModelDesc<"_root", {}, {