@codecks/fetch 0.1.5 → 0.1.7

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 +47 -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 +76 -73
  10. package/dist/{_root-D1Faf8cg.d.ts → _root-BW9uzB79.d.ts} +1 -1
  11. package/dist/immediate-cache-DiH8tpJM.js +237 -0
  12. package/dist/{index-DUBsesYl.d.ts → index-k-KPWV9R.d.ts} +8 -1
  13. package/dist/index.d.ts +4 -4
  14. package/dist/index.js +6 -5
  15. package/dist/{loader-types-Dp0CSVTf.d.ts → loader-types-fd5tV0FD.d.ts} +4 -8
  16. package/dist/{loader-utils-Du-CTKH-.d.ts → loader-utils-BqZKAn_v.d.ts} +2 -2
  17. package/dist/{query-helpers-DsMMOtSO.js → query-helpers-iX1fiqLv.js} +41 -16
  18. package/dist/{query-type-Dx5ZCv5N.d.ts → query-type-BqQ9oX_U.d.ts} +55 -10
  19. package/dist/{store-B6JWZnAO.d.ts → store-BvEJzUVp.d.ts} +104 -6
  20. package/package.json +4 -3
  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 +184 -0
@@ -1,85 +1,212 @@
1
- import { queryToKey } from "../query-helpers-DsMMOtSO.js";
1
+ import { queryToKey } from "../query-helpers-iX1fiqLv.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-BqQ9oX_U.js";
2
+ import { BaseLoader, BaseRequester, MissingDataRequest, OnLoadedFn } from "../loader-types-fd5tV0FD.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-iX1fiqLv.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-BqQ9oX_U.js";
2
+ import "../index-k-KPWV9R.js";
3
+ import "../loader-types-fd5tV0FD.js";
4
+ import { Store } from "../store-BvEJzUVp.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-iX1fiqLv.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]) => [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
@@ -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
- data[field] = cachedValue.value;
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) missingRequests.push({
132
- type: "relation",
133
- model,
134
- id,
135
- relKey,
136
- query: makeRelQuerySerializable(relQuery)
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
- data[fieldName] = cachedValue.value;
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
@@ -1,4 +1,4 @@
1
- import { ModelDesc, RelationEntry } from "./query-type-Dx5ZCv5N.js";
1
+ import { ModelDesc, RelationEntry } from "./query-type-BqQ9oX_U.js";
2
2
 
3
3
  //#region src/models/_root.d.ts
4
4
  declare const _rootDesc: ModelDesc<"_root", {}, {