@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
@@ -0,0 +1,237 @@
1
+ //#region src/_exploration/optimistic-manager.ts
2
+ /**
3
+ * Represents a single optimistic update layer
4
+ * Contains field updates for one mutation
5
+ * Internal implementation - wrapped by ImmediateCache to provide full OptimisticUpdater interface
6
+ */
7
+ var OptimisticLayer = class {
8
+ data = new Map();
9
+ affectedFields = new Set();
10
+ setField(model, id, field, value) {
11
+ const fieldKey = getFieldKey(model, id, field);
12
+ this.data.set(fieldKey, value);
13
+ this.affectedFields.add(fieldKey);
14
+ }
15
+ hasChanges() {
16
+ return this.affectedFields.size > 0;
17
+ }
18
+ getAffectedFieldKeys() {
19
+ return this.affectedFields;
20
+ }
21
+ entries() {
22
+ return this.data.entries();
23
+ }
24
+ };
25
+ /**
26
+ * Manages a stack of optimistic layers with O(1) lookups
27
+ *
28
+ * Design:
29
+ * - Maintains a stack of layers (one per in-flight mutation)
30
+ * - Rebuilds flattened view when layers added/removed for fast reads
31
+ * - Later layers override earlier ones (parallel updates supported)
32
+ * - Auto-cleanup via promise lifecycle
33
+ */
34
+ var OptimisticManager = class {
35
+ layers = [];
36
+ flatView = new Map();
37
+ /**
38
+ * Add a new optimistic layer
39
+ * @returns Object with the layer and a dispose function that returns affected field keys
40
+ */
41
+ addLayer() {
42
+ const layer = new OptimisticLayer();
43
+ this.layers.push(layer);
44
+ const dispose = () => this.removeLayer(layer);
45
+ return {
46
+ layer,
47
+ dispose
48
+ };
49
+ }
50
+ /**
51
+ * Get optimistic value for a field (O(1) via flat view)
52
+ * Returns undefined if no optimistic update exists for this field
53
+ */
54
+ get(model, id, field) {
55
+ const fieldKey = getFieldKey(model, id, field);
56
+ const entry = this.flatView.get(fieldKey);
57
+ if (!entry) return void 0;
58
+ const meta = { state: "optimistic" };
59
+ return {
60
+ value: entry.value,
61
+ partKeys: [fieldKey],
62
+ meta
63
+ };
64
+ }
65
+ /**
66
+ * Remove a layer from the stack
67
+ * Returns affected field keys for notification
68
+ */
69
+ removeLayer(layer) {
70
+ const layerIndex = this.layers.indexOf(layer);
71
+ if (layerIndex === -1) return;
72
+ this.layers.splice(layerIndex, 1);
73
+ this.rebuildFlatView();
74
+ }
75
+ /**
76
+ * Rebuild the flattened view from the layer stack
77
+ * Later layers override earlier ones for the same field
78
+ */
79
+ rebuildFlatView() {
80
+ this.flatView.clear();
81
+ for (let i = 0; i < this.layers.length; i++) {
82
+ const layer = this.layers[i];
83
+ for (const [fieldKey, value] of layer.entries()) this.flatView.set(fieldKey, {
84
+ value,
85
+ layerIndex: i
86
+ });
87
+ }
88
+ }
89
+ };
90
+
91
+ //#endregion
92
+ //#region src/_exploration/immediate-cache.ts
93
+ const getFieldKey = (model, id, field) => `${model}:${id}:${field}`;
94
+ /**
95
+ * Wrapper that provides full OptimisticUpdater interface
96
+ * Delegates writes to OptimisticLayer and reads to ImmediateCache
97
+ */
98
+ var OptimisticUpdater = class {
99
+ constructor(layer, cache) {
100
+ this.layer = layer;
101
+ this.cache = cache;
102
+ }
103
+ setField(model, id, field, value) {
104
+ this.layer.setField(model, id, field, value);
105
+ }
106
+ getField(model, id, field) {
107
+ return this.cache.get(model, id, field);
108
+ }
109
+ };
110
+ var ImmediateCache = class {
111
+ cache = new Map();
112
+ partKeyToFields = new Map();
113
+ fieldSubscriptions = new Map();
114
+ optimisticManager = new OptimisticManager();
115
+ getInstanceKey(model, id) {
116
+ return `${model}:${id}`;
117
+ }
118
+ get(model, key, field) {
119
+ const optimistic = this.optimisticManager.get(model, key, field);
120
+ if (optimistic) return optimistic;
121
+ return this.cache.get(this.getInstanceKey(model, key))?.get(field);
122
+ }
123
+ subscribe(fieldKey, notifyFn) {
124
+ const exists = this.fieldSubscriptions.get(fieldKey);
125
+ if (exists) exists.add(notifyFn);
126
+ else this.fieldSubscriptions.set(fieldKey, new Set([notifyFn]));
127
+ return () => {
128
+ const subs = this.fieldSubscriptions.get(fieldKey);
129
+ if (!subs) return;
130
+ subs.delete(notifyFn);
131
+ if (subs.size === 0) this.fieldSubscriptions.delete(fieldKey);
132
+ };
133
+ }
134
+ set(model, key, partialInstance, meta) {
135
+ const instanceKey = this.getInstanceKey(model, key);
136
+ let entry = this.cache.get(instanceKey);
137
+ if (!entry) {
138
+ entry = new Map();
139
+ this.cache.set(instanceKey, entry);
140
+ }
141
+ const subs = [];
142
+ for (const field in partialInstance) {
143
+ const f = partialInstance[field];
144
+ const oldEntry = entry.get(field);
145
+ const oldValue = oldEntry?.value;
146
+ const newValue = f.value;
147
+ const fieldEntry = {
148
+ value: newValue,
149
+ partKeys: f.partKeys,
150
+ meta
151
+ };
152
+ const fieldKey = getFieldKey(model, key, field);
153
+ entry.set(field, fieldEntry);
154
+ for (const partKey of f.partKeys) {
155
+ const location = {
156
+ model,
157
+ id: key,
158
+ field,
159
+ fieldKey,
160
+ cache: fieldEntry,
161
+ type: f.type
162
+ };
163
+ const maybeFields = this.partKeyToFields.get(partKey);
164
+ if (!maybeFields) this.partKeyToFields.set(partKey, [location]);
165
+ else maybeFields.push(location);
166
+ }
167
+ if (oldValue !== newValue) {
168
+ const fieldSubs = this.fieldSubscriptions.get(fieldKey);
169
+ if (fieldSubs) subs.push(...fieldSubs);
170
+ }
171
+ }
172
+ return subs;
173
+ }
174
+ markDirty(partKeys) {
175
+ const subscribedFields = [];
176
+ for (const partKey of partKeys) {
177
+ const locations = this.partKeyToFields.get(partKey);
178
+ console.log(locations);
179
+ if (!locations) continue;
180
+ for (const location of locations) {
181
+ location.cache.meta = { state: "dirty" };
182
+ if (this.fieldSubscriptions.has(location.fieldKey)) if (location.type === "field") subscribedFields.push({
183
+ type: "field",
184
+ model: location.model,
185
+ id: location.id,
186
+ field: location.field
187
+ });
188
+ else subscribedFields.push({
189
+ type: "relation",
190
+ model: location.model,
191
+ id: location.id,
192
+ relKey: location.fieldKey,
193
+ contents: {
194
+ asField: false,
195
+ fields: []
196
+ }
197
+ });
198
+ }
199
+ }
200
+ return subscribedFields;
201
+ }
202
+ /**
203
+ * Create a new optimistic layer bound to a promise and apply updates
204
+ * The layer will be automatically removed when the promise settles
205
+ * @param promise - Promise to bind the layer lifecycle to
206
+ * @param updateFn - Function to apply optimistic updates to the layer
207
+ * @returns The updater object with both read and write capabilities
208
+ */
209
+ createOptimisticLayer(promise, updateFn) {
210
+ const { layer, dispose } = this.optimisticManager.addLayer();
211
+ const updater = new OptimisticUpdater(layer, this);
212
+ updateFn(updater);
213
+ if (layer.hasChanges()) {
214
+ this.optimisticManager.rebuildFlatView();
215
+ const affectedFields = layer.getAffectedFieldKeys();
216
+ const notify = () => {
217
+ const subs = [];
218
+ for (const fieldKey of affectedFields) {
219
+ const fieldSubs = this.fieldSubscriptions.get(fieldKey);
220
+ if (fieldSubs) subs.push(...fieldSubs);
221
+ }
222
+ new Set(subs).forEach((fn) => fn());
223
+ };
224
+ notify();
225
+ promise.finally(() => {
226
+ dispose();
227
+ notify();
228
+ });
229
+ } else promise.finally(() => {
230
+ dispose();
231
+ });
232
+ return updater;
233
+ }
234
+ };
235
+
236
+ //#endregion
237
+ export { ImmediateCache, getFieldKey };
@@ -1,4 +1,4 @@
1
- import { ModelDesc, Nominal, RelationEntry, TypedField } from "./query-type-Dx5ZCv5N.js";
1
+ import { ModelDesc, Nominal, RelationEntry, TypedField } from "./query-type-B0L3hwCo.js";
2
2
 
3
3
  //#region src/models/AccountOnboardingStep.d.ts
4
4
  type AccountOnboardingStepId = Nominal<string, "accountOnboardingStep">;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { InferModelQuery, InferRelQuery, Instance, ModelQuery, RelQuery } from "./query-type-Dx5ZCv5N.js";
2
- import { modelMap$1 as modelMap } from "./index-DUBsesYl.js";
3
- import { DataLoader, FetchOptions } from "./loader-utils-Du-CTKH-.js";
4
- import { _rootDesc$1 as _rootDesc } from "./_root-D1Faf8cg.js";
1
+ import { InferModelQuery, InferRelQuery, Instance, ModelQuery, RelQuery } from "./query-type-B0L3hwCo.js";
2
+ import { modelMap$1 as modelMap } from "./index-obQtSRRg.js";
3
+ import { DataLoader, FetchOptions } from "./loader-utils-rQFSDhwf.js";
4
+ import { _rootDesc$1 as _rootDesc } from "./_root-rBEjvjCJ.js";
5
5
 
6
6
  //#region src/loaders/simple-loader.d.ts
7
7
  type SimpleLoaderOptions = FetchOptions;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { configuredFetch, ensureMapValue } from "./collection-utils-CYJaA1a9.js";
2
- import { _rootDesc, getRelKey, makeModelQuerySerializable, modelMap, serializeInstanceQuery } from "./query-helpers-DsMMOtSO.js";
2
+ import { _rootDesc, getRelKey, makeModelQuerySerializable, modelMap, serializeInstanceQuery } from "./query-helpers-DgC42MEH.js";
3
3
 
4
4
  //#region src/model-pool.ts
5
5
  const ROOT_ID = "";
@@ -12,7 +12,7 @@ const dateStrToDay = (dateAsStr) => {
12
12
  };
13
13
  };
14
14
  const parseField = (v, field) => {
15
- if (!field) return v;
15
+ if (!field || v == null) return v;
16
16
  switch (field.type) {
17
17
  case "date": return new Date(v);
18
18
  case "day": return dateStrToDay(v);
@@ -75,10 +75,11 @@ const reconcileInstanceQuery = (query, response, instanceModel, key, store) => {
75
75
  switch (opts.type) {
76
76
  case "belongsTo":
77
77
  result[opts.fk] = instance[relName];
78
- result[relName] = opts.fk ? reconcileInstanceQuery(relEntry, response, relModel, `${instance[relName]}`, store) : null;
78
+ result[`~${relName}`] = instance[relName] != null ? `${instance[relName]}` : null;
79
+ result[relName] = instance[relName] != null ? reconcileInstanceQuery(relEntry, response, relModel, `${instance[relName]}`, store) : null;
79
80
  break;
80
81
  case "hasOne":
81
- result[`~${relName}`] = instance[relName];
82
+ result[`~${relName}`] = instance[relName] != null ? `${instance[relName]}` : null;
82
83
  result[relName] = reconcileInstanceQuery(relEntry, response, relModel, `${instance[relName]}`, store);
83
84
  break;
84
85
  case "hasMany":
@@ -92,11 +93,11 @@ const reconcileInstanceQuery = (query, response, instanceModel, key, store) => {
92
93
  break;
93
94
  }
94
95
  case "first":
95
- result[`~${asName}`] = val;
96
+ result[`~${asName}`] = val != null ? `${val}` : null;
96
97
  result[asName] = reconcileInstanceQuery(relEntry, response, relModel, `${val}`, store);
97
98
  break;
98
99
  default: {
99
- result[`~${asName}`] = val;
100
+ result[`~${asName}`] = val.map((v) => `${v}`);
100
101
  result[asName] = val.map((id) => reconcileInstanceQuery(relEntry, response, relModel, `${id}`, store));
101
102
  }
102
103
  }
@@ -1,4 +1,4 @@
1
- import { SerializableRelationQuery } from "./query-type-Dx5ZCv5N.js";
1
+ import { SerializableRelationQuery } from "./query-type-B0L3hwCo.js";
2
2
 
3
3
  //#region src/_exploration/loader-types.d.ts
4
4
  type OnLoadedFn = (model: string, key: string, partialInstance: Record<string, unknown>) => void;
@@ -6,16 +6,12 @@ interface BaseLoader {
6
6
  setOnLoaded(onLoaded: OnLoadedFn): void;
7
7
  loadBatch(requests: MissingDataRequest[]): Promise<unknown>;
8
8
  }
9
- type LoaderPayload = {
10
- value: unknown;
11
- partKeys: string[];
12
- };
13
9
  type LoaderResult = {
14
10
  state: "resolved";
15
- payload: LoaderPayload;
11
+ payload: unknown;
16
12
  } | {
17
13
  state: "pending";
18
- promise: Promise<LoaderPayload>;
14
+ promise: Promise<unknown>;
19
15
  };
20
16
  type MissingDataRequest = {
21
17
  type: "field";
@@ -27,7 +23,7 @@ type MissingDataRequest = {
27
23
  model: string;
28
24
  id: string;
29
25
  relKey: string;
30
- query: SerializableRelationQuery;
26
+ contents: SerializableRelationQuery;
31
27
  };
32
28
  interface BaseRequester {
33
29
  request: (requests: MissingDataRequest[]) => Promise<{
@@ -1,5 +1,5 @@
1
- import { InferModelQuery, ModelQuery } from "./query-type-Dx5ZCv5N.js";
2
- import { modelMap$1 as modelMap } from "./index-DUBsesYl.js";
1
+ import { InferModelQuery, ModelQuery } from "./query-type-B0L3hwCo.js";
2
+ import { modelMap$1 as modelMap } from "./index-obQtSRRg.js";
3
3
 
4
4
  //#region src/loaders/loader-utils.d.ts
5
5
  type ModelMap = typeof modelMap;
@@ -33,33 +33,37 @@ const _rootDesc = makeModel({
33
33
  const id = () => {
34
34
  return { type: "id" };
35
35
  };
36
+ const withOpts = (type, opts) => opts.optional ? {
37
+ type,
38
+ optional: true
39
+ } : { type };
36
40
  const string = (opts) => {
37
- return { type: "string" };
41
+ return withOpts("string", opts);
38
42
  };
39
43
  const int = (opts) => {
40
- return { type: "int" };
44
+ return withOpts("int", opts);
41
45
  };
42
46
  const bigint = (opts) => {
43
- return { type: "bigint" };
47
+ return withOpts("bigint", opts);
44
48
  };
45
49
  const bool = (opts) => {
46
- return { type: "bool" };
50
+ return withOpts("bool", opts);
47
51
  };
48
52
  const date = (opts) => {
49
- return { type: "date" };
53
+ return withOpts("date", opts);
50
54
  };
51
55
  const day = (opts) => {
52
- return { type: "day" };
56
+ return withOpts("day", opts);
53
57
  };
54
58
  const object = (opts) => {
55
- return { type: "obj" };
59
+ return withOpts("obj", opts);
56
60
  };
57
61
  const array = (opts) => {
58
- return { type: "array" };
62
+ return withOpts("array", opts);
59
63
  };
60
64
  const belongsTo = (opts) => {
61
65
  return { type: () => {
62
- return { type: "belongsTo" };
66
+ return withOpts("belongsTo", opts);
63
67
  } };
64
68
  };
65
69
 
@@ -16,7 +16,7 @@ type BaseOpts = {
16
16
  type FieldEntry = TypedField<string, any, any>;
17
17
  type InferSimpleFieldEntry<T extends FieldEntry> = T extends TypedField<any, infer X, infer Opts> ? Opts extends {
18
18
  optional: true;
19
- } ? X | null | "yes" : X : never;
19
+ } ? X | null : X : never;
20
20
  //#endregion
21
21
  //#region src/models/_desc.d.ts
22
22
  type ModelDesc<TName extends string, TFields extends FieldDesc, TRelations extends RelationsDesc, TKey extends (keyof TFields & string)[]> = {
@@ -165,7 +165,7 @@ type ExtractRelQueryArray<M extends AnyDesc, TMap extends ModelMap, Q extends Re
165
165
  type InferHasMany<M extends AnyDesc, TMap extends ModelMap, QM extends HasManyQuery<M, TMap>> = QM extends {
166
166
  type: "count";
167
167
  } ? number : QM extends {
168
- type: "exist";
168
+ type: "exists";
169
169
  } ? boolean : QM extends {
170
170
  type: "first";
171
171
  } ? InferModelQuery<M, EnsureModelQuery<QM>, TMap> | null : InferModelQuery<M, EnsureModelQuery<QM>, TMap>[];
@@ -1,9 +1,36 @@
1
- import { ModelDesc, ModelQuery, RelationEntry, TypedField } from "./query-type-Dx5ZCv5N.js";
2
- import { AccountId, AccountOnboardingStepId, ActiveTimeTrackerId, ActivityId, AffiliateCodeId, AppId, AppInstallationId, AttachmentId, CardId, CardPresetId, CardSubscriptionId, CardUpvoteId, DeckId, DeckSubscriptionId, DiscordGuildId, DiscordMemberId, DiscordProjectNotificationId, DiscordSlashCommandId, FileId, IntegrationId, InvoiceId, MilestoneId, PinnedMilestoneId, ProjectId, ProjectSelectionId, ProjectTagId, QueueEntryId, QueueSelectionId, ReleaseId, ResolvableEntryId, ResolvableEntryReactionId, ResolvableId, SavedSearchId, SprintConfigId, SprintId, TimeTrackingSegmentId, UserEmailId, UserId, UserInvitationId, UserInviteCodeId, UserReportEmailId, UserReportSettingId, UserReportTokenId, UserTagId, VisionBoardId, VisionBoardQueryId, WizardId, WorkflowItemId, modelMap$1 as modelMap } from "./index-DUBsesYl.js";
3
- import { BaseLoader, LoaderResult } from "./loader-types-Dp0CSVTf.js";
1
+ import { ModelDesc, ModelQuery, RelationEntry, TypedField } from "./query-type-B0L3hwCo.js";
2
+ import { AccountId, AccountOnboardingStepId, ActiveTimeTrackerId, ActivityId, AffiliateCodeId, AppId, AppInstallationId, AttachmentId, CardId, CardPresetId, CardSubscriptionId, CardUpvoteId, DeckId, DeckSubscriptionId, DiscordGuildId, DiscordMemberId, DiscordProjectNotificationId, DiscordSlashCommandId, FileId, IntegrationId, InvoiceId, MilestoneId, PinnedMilestoneId, ProjectId, ProjectSelectionId, ProjectTagId, QueueEntryId, QueueSelectionId, ReleaseId, ResolvableEntryId, ResolvableEntryReactionId, ResolvableId, SavedSearchId, SprintConfigId, SprintId, TimeTrackingSegmentId, UserEmailId, UserId, UserInvitationId, UserInviteCodeId, UserReportEmailId, UserReportSettingId, UserReportTokenId, UserTagId, VisionBoardId, VisionBoardQueryId, WizardId, WorkflowItemId, modelMap$1 as modelMap } from "./index-obQtSRRg.js";
3
+ import { BaseLoader, LoaderResult, MissingDataRequest } from "./loader-types-Y0OHlOEK.js";
4
4
 
5
+ //#region src/_exploration/optimistic-manager.d.ts
6
+
7
+ /**
8
+ * Represents a single optimistic update layer
9
+ * Contains field updates for one mutation
10
+ * Internal implementation - wrapped by ImmediateCache to provide full OptimisticUpdater interface
11
+ */
12
+ declare class OptimisticLayer {
13
+ private data;
14
+ private affectedFields;
15
+ setField(model: string, id: string, field: string, value: any): void;
16
+ hasChanges(): boolean;
17
+ getAffectedFieldKeys(): Iterable<string>;
18
+ entries(): IterableIterator<[string, any]>;
19
+ }
20
+ /**
21
+ * Manages a stack of optimistic layers with O(1) lookups
22
+ *
23
+ * Design:
24
+ * - Maintains a stack of layers (one per in-flight mutation)
25
+ * - Rebuilds flattened view when layers added/removed for fast reads
26
+ * - Later layers override earlier ones (parallel updates supported)
27
+ * - Auto-cleanup via promise lifecycle
28
+ */
29
+ //#endregion
5
30
  //#region src/_exploration/immediate-cache.d.ts
6
31
  type FieldMeta = {
32
+ state: "optimistic";
33
+ } | {
7
34
  state: "fresh";
8
35
  } | {
9
36
  state: "dirty";
@@ -13,21 +40,58 @@ interface FieldCache<T = any> {
13
40
  partKeys: string[];
14
41
  meta: FieldMeta;
15
42
  }
43
+ type FieldLocation = {
44
+ model: string;
45
+ id: string;
46
+ field: string;
47
+ fieldKey: string;
48
+ type: "field" | "relation";
49
+ cache: FieldCache;
50
+ };
16
51
  type ModelInstance = Map<string, FieldCache>;
17
52
  type ModelCache = Map<string, ModelInstance>;
53
+ type FieldSub = () => void;
54
+ /**
55
+ * Wrapper that provides full OptimisticUpdater interface
56
+ * Delegates writes to OptimisticLayer and reads to ImmediateCache
57
+ */
58
+ declare class OptimisticUpdater {
59
+ private layer;
60
+ private cache;
61
+ constructor(layer: OptimisticLayer,
62
+ // OptimisticLayer from optimistic-manager
63
+ cache: ImmediateCache);
64
+ setField(model: string, id: string, field: string, value: any): void;
65
+ getField(model: string, id: string, field: string): FieldCache | undefined;
66
+ }
18
67
  declare class ImmediateCache {
19
68
  cache: ModelCache;
20
- partKeyToFields: Map<string, FieldCache<any>[]>;
69
+ partKeyToFields: Map<string, FieldLocation[]>;
70
+ private fieldSubscriptions;
71
+ private optimisticManager;
21
72
  private getInstanceKey;
22
73
  get(model: string, key: string, field: string): FieldCache | undefined;
74
+ subscribe(fieldKey: string, notifyFn: FieldSub): () => void;
23
75
  set(model: string, key: string, partialInstance: Record<string, {
24
76
  value: unknown;
25
77
  partKeys: string[];
26
- }>, meta: FieldMeta): void;
27
- setMetaByPartKeys(partKeys: string[], meta: FieldMeta): void;
78
+ type: "field" | "relation";
79
+ }>, meta: FieldMeta): FieldSub[];
80
+ markDirty(partKeys: string[]): MissingDataRequest[];
81
+ /**
82
+ * Create a new optimistic layer bound to a promise and apply updates
83
+ * The layer will be automatically removed when the promise settles
84
+ * @param promise - Promise to bind the layer lifecycle to
85
+ * @param updateFn - Function to apply optimistic updates to the layer
86
+ * @returns The updater object with both read and write capabilities
87
+ */
88
+ createOptimisticLayer(promise: Promise<any>, updateFn: (updater: OptimisticUpdater) => void): OptimisticUpdater;
28
89
  }
29
90
  //#endregion
30
91
  //#region src/_exploration/store.d.ts
92
+ type QueryDep = {
93
+ keys: string[];
94
+ };
31
95
  type ModelMap = typeof modelMap;
32
96
  declare class Store {
33
97
  private loader;
@@ -2556,7 +2620,10 @@ declare class Store {
2556
2620
  }>;
2557
2621
  }, ["itemId"]>;
2558
2622
  };
2623
+ requestedRelationKeys: Map<string, QueryDep>;
2559
2624
  constructor(loader: BaseLoader);
2625
+ subscribeToField(fieldKey: string, notifyFn: () => void): () => void;
2626
+ invalidate(partKeyPatterns: string[]): void;
2560
2627
  loadData(model: keyof ModelMap, ids: string[], q: ModelQuery<any, any>, acceptDirty?: boolean, iteration?: number): LoaderResult;
2561
2628
  onLoaded(model: string, key: string, partialInstance: Record<string, unknown>): void;
2562
2629
  /**
@@ -2564,6 +2631,30 @@ declare class Store {
2564
2631
  * Accumulates missing requests in the missingRequests array
2565
2632
  */
2566
2633
  private checkQueryRecursive;
2634
+ /**
2635
+ * Execute a mutation with optimistic updates
2636
+ *
2637
+ * @example
2638
+ * ```typescript
2639
+ * await store.mutate(
2640
+ * () => api.updateCard('123', {title: 'New Title'}),
2641
+ * {
2642
+ * onMutate: (layer) => {
2643
+ * layer.setField('Card', '123', 'title', 'New Title');
2644
+ * },
2645
+ * onSuccess: () => {
2646
+ * // Optional: invalidate related queries
2647
+ * store.invalidate(['Card:123:title']);
2648
+ * }
2649
+ * }
2650
+ * );
2651
+ * ```
2652
+ */
2653
+ mutate<T>(mutationFn: () => Promise<T>, options?: {
2654
+ onMutate?: (updater: OptimisticUpdater) => void;
2655
+ onSuccess?: (data: T) => void;
2656
+ onError?: (error: unknown) => void;
2657
+ }): Promise<T>;
2567
2658
  }
2568
2659
  //#endregion
2569
2660
  export { Store };
package/package.json CHANGED
@@ -1,20 +1,21 @@
1
1
  {
2
2
  "name": "@codecks/fetch",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Quickly create deeply nested queries for the Codecks API with advanced TypeScript support",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "homepage": "https://github.com/codecks-io/cdx-fetch#readme",
7
+ "homepage": "https://github.com/codecks-io/fetch#readme",
8
8
  "bugs": {
9
- "url": "https://github.com/codecks-io/cdx-fetch/issues"
9
+ "url": "https://github.com/codecks-io/fetch/issues"
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "git+https://github.com/codecks-io/cdx-fetch.git"
13
+ "url": "git+https://github.com/codecks-io/fetch.git"
14
14
  },
15
15
  "author": "Daniel Berndt <daniel@codecks.io>",
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "schema"
18
19
  ],
19
20
  "main": "./dist/index.js",
20
21
  "module": "./dist/index.js",
@@ -31,7 +32,7 @@
31
32
  "access": "public"
32
33
  },
33
34
  "scripts": {
34
- "build": "tsdown",
35
+ "build": "tsdown && vite-node scripts/generate-schema-docs.ts",
35
36
  "format": "prettier --write .",
36
37
  "check-format": "prettier --check .",
37
38
  "dev": "tsdown --watch",