@spoosh/angular 0.1.0
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/LICENSE +21 -0
- package/README.md +229 -0
- package/dist/index.d.mts +358 -0
- package/dist/index.d.ts +358 -0
- package/dist/index.js +692 -0
- package/dist/index.mjs +697 -0
- package/package.json +50 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
// src/injectRead/index.ts
|
|
2
|
+
import {
|
|
3
|
+
signal,
|
|
4
|
+
effect,
|
|
5
|
+
DestroyRef,
|
|
6
|
+
inject,
|
|
7
|
+
untracked
|
|
8
|
+
} from "@angular/core";
|
|
9
|
+
import {
|
|
10
|
+
createOperationController,
|
|
11
|
+
createSelectorProxy,
|
|
12
|
+
resolvePath,
|
|
13
|
+
resolveTags
|
|
14
|
+
} from "@spoosh/core";
|
|
15
|
+
function createInjectRead(options) {
|
|
16
|
+
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
17
|
+
return function injectRead(readFn, readOptions) {
|
|
18
|
+
const destroyRef = inject(DestroyRef);
|
|
19
|
+
const {
|
|
20
|
+
enabled: enabledOption = true,
|
|
21
|
+
tags = void 0,
|
|
22
|
+
additionalTags = void 0,
|
|
23
|
+
...pluginOpts
|
|
24
|
+
} = readOptions ?? {};
|
|
25
|
+
const getEnabled = () => typeof enabledOption === "function" ? enabledOption() : enabledOption;
|
|
26
|
+
const dataSignal = signal(void 0);
|
|
27
|
+
const errorSignal = signal(void 0);
|
|
28
|
+
const loadingSignal = signal(true);
|
|
29
|
+
const fetchingSignal = signal(false);
|
|
30
|
+
const inputSignal = signal({});
|
|
31
|
+
const metaSignal = signal({});
|
|
32
|
+
let currentController = null;
|
|
33
|
+
let currentQueryKey = null;
|
|
34
|
+
let currentSubscription = null;
|
|
35
|
+
let prevContext = null;
|
|
36
|
+
let initialized = false;
|
|
37
|
+
let isMounted = false;
|
|
38
|
+
const captureSelector = () => {
|
|
39
|
+
const selectorResult = {
|
|
40
|
+
call: null,
|
|
41
|
+
selector: null
|
|
42
|
+
};
|
|
43
|
+
const selectorProxy = createSelectorProxy(
|
|
44
|
+
(result2) => {
|
|
45
|
+
selectorResult.call = result2.call;
|
|
46
|
+
selectorResult.selector = result2.selector;
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
readFn(selectorProxy);
|
|
50
|
+
return selectorResult;
|
|
51
|
+
};
|
|
52
|
+
const executeWithTracking = async (controller, force = false) => {
|
|
53
|
+
const hasData = dataSignal() !== void 0;
|
|
54
|
+
loadingSignal.set(!hasData);
|
|
55
|
+
fetchingSignal.set(true);
|
|
56
|
+
try {
|
|
57
|
+
const response = await controller.execute(void 0, { force });
|
|
58
|
+
if (response.error) {
|
|
59
|
+
errorSignal.set(response.error);
|
|
60
|
+
} else {
|
|
61
|
+
errorSignal.set(void 0);
|
|
62
|
+
}
|
|
63
|
+
if (response.data !== void 0) {
|
|
64
|
+
dataSignal.set(response.data);
|
|
65
|
+
}
|
|
66
|
+
return response;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
errorSignal.set(err);
|
|
69
|
+
throw err;
|
|
70
|
+
} finally {
|
|
71
|
+
loadingSignal.set(false);
|
|
72
|
+
fetchingSignal.set(false);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
let wasEnabled = false;
|
|
76
|
+
effect(
|
|
77
|
+
() => {
|
|
78
|
+
const isEnabled = getEnabled();
|
|
79
|
+
const selectorResult = captureSelector();
|
|
80
|
+
const capturedCall = selectorResult.call;
|
|
81
|
+
if (!capturedCall) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"injectRead requires calling an HTTP method ($get). Example: injectRead((api) => api.posts.$get())"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const requestOptions = capturedCall.options;
|
|
87
|
+
const resolvedPath = resolvePath(
|
|
88
|
+
capturedCall.path,
|
|
89
|
+
requestOptions?.params
|
|
90
|
+
);
|
|
91
|
+
const resolvedTags = resolveTags(
|
|
92
|
+
{ tags, additionalTags },
|
|
93
|
+
resolvedPath
|
|
94
|
+
);
|
|
95
|
+
const queryKey = stateManager.createQueryKey({
|
|
96
|
+
path: capturedCall.path,
|
|
97
|
+
method: capturedCall.method,
|
|
98
|
+
options: capturedCall.options
|
|
99
|
+
});
|
|
100
|
+
const opts = capturedCall.options;
|
|
101
|
+
const inputInner = {};
|
|
102
|
+
if (opts?.query !== void 0) {
|
|
103
|
+
inputInner.query = opts.query;
|
|
104
|
+
}
|
|
105
|
+
if (opts?.body !== void 0) {
|
|
106
|
+
inputInner.body = opts.body;
|
|
107
|
+
}
|
|
108
|
+
if (opts?.formData !== void 0) {
|
|
109
|
+
inputInner.formData = opts.formData;
|
|
110
|
+
}
|
|
111
|
+
if (opts?.params !== void 0) {
|
|
112
|
+
inputInner.params = opts.params;
|
|
113
|
+
}
|
|
114
|
+
inputSignal.set(inputInner);
|
|
115
|
+
const queryKeyChanged = queryKey !== currentQueryKey;
|
|
116
|
+
const enabledChanged = isEnabled !== wasEnabled;
|
|
117
|
+
wasEnabled = isEnabled;
|
|
118
|
+
if (queryKeyChanged) {
|
|
119
|
+
if (currentController && initialized) {
|
|
120
|
+
prevContext = currentController.getContext();
|
|
121
|
+
}
|
|
122
|
+
if (currentSubscription) {
|
|
123
|
+
currentSubscription();
|
|
124
|
+
}
|
|
125
|
+
const controller = createOperationController({
|
|
126
|
+
operationType: "read",
|
|
127
|
+
path: capturedCall.path,
|
|
128
|
+
method: capturedCall.method,
|
|
129
|
+
tags: resolvedTags,
|
|
130
|
+
requestOptions: capturedCall.options,
|
|
131
|
+
stateManager,
|
|
132
|
+
eventEmitter,
|
|
133
|
+
pluginExecutor,
|
|
134
|
+
hookId: `angular-${Math.random().toString(36).slice(2)}`,
|
|
135
|
+
fetchFn: async (fetchOpts) => {
|
|
136
|
+
let current = api;
|
|
137
|
+
for (const segment of resolvedPath) {
|
|
138
|
+
current = current[segment];
|
|
139
|
+
}
|
|
140
|
+
const method = current[capturedCall.method];
|
|
141
|
+
return method(fetchOpts);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
controller.setPluginOptions(pluginOpts);
|
|
145
|
+
currentSubscription = controller.subscribe(() => {
|
|
146
|
+
const state = controller.getState();
|
|
147
|
+
dataSignal.set(state.data);
|
|
148
|
+
errorSignal.set(state.error);
|
|
149
|
+
const entry = stateManager.getCache(queryKey);
|
|
150
|
+
const newMeta = entry?.pluginResult ? Object.fromEntries(entry.pluginResult) : {};
|
|
151
|
+
metaSignal.set(newMeta);
|
|
152
|
+
});
|
|
153
|
+
currentController = controller;
|
|
154
|
+
currentQueryKey = queryKey;
|
|
155
|
+
if (!initialized) {
|
|
156
|
+
initialized = true;
|
|
157
|
+
} else if (prevContext) {
|
|
158
|
+
controller.update(prevContext);
|
|
159
|
+
prevContext = null;
|
|
160
|
+
}
|
|
161
|
+
if (isEnabled) {
|
|
162
|
+
controller.mount();
|
|
163
|
+
isMounted = true;
|
|
164
|
+
untracked(() => {
|
|
165
|
+
executeWithTracking(controller, false);
|
|
166
|
+
});
|
|
167
|
+
} else {
|
|
168
|
+
loadingSignal.set(false);
|
|
169
|
+
}
|
|
170
|
+
} else if (enabledChanged && currentController) {
|
|
171
|
+
if (isEnabled && !isMounted) {
|
|
172
|
+
currentController.mount();
|
|
173
|
+
isMounted = true;
|
|
174
|
+
untracked(() => {
|
|
175
|
+
executeWithTracking(currentController, false);
|
|
176
|
+
});
|
|
177
|
+
} else if (!isEnabled && isMounted) {
|
|
178
|
+
currentController.unmount();
|
|
179
|
+
isMounted = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!isEnabled) {
|
|
183
|
+
loadingSignal.set(false);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const unsubRefetch = eventEmitter.on(
|
|
187
|
+
"refetch",
|
|
188
|
+
(event) => {
|
|
189
|
+
if (event.queryKey === currentQueryKey && currentController) {
|
|
190
|
+
untracked(() => {
|
|
191
|
+
executeWithTracking(currentController, true);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
const unsubInvalidate = eventEmitter.on(
|
|
197
|
+
"invalidate",
|
|
198
|
+
(invalidatedTags) => {
|
|
199
|
+
const hasMatch = invalidatedTags.some(
|
|
200
|
+
(tag) => resolvedTags.includes(tag)
|
|
201
|
+
);
|
|
202
|
+
if (hasMatch && currentController) {
|
|
203
|
+
untracked(() => {
|
|
204
|
+
executeWithTracking(currentController, true);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
return () => {
|
|
210
|
+
unsubRefetch();
|
|
211
|
+
unsubInvalidate();
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
{ allowSignalWrites: true }
|
|
215
|
+
);
|
|
216
|
+
destroyRef.onDestroy(() => {
|
|
217
|
+
if (currentSubscription) {
|
|
218
|
+
currentSubscription();
|
|
219
|
+
}
|
|
220
|
+
if (currentController && isMounted) {
|
|
221
|
+
currentController.unmount();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
const abort = () => {
|
|
225
|
+
currentController?.abort();
|
|
226
|
+
};
|
|
227
|
+
const refetch = () => {
|
|
228
|
+
if (currentController) {
|
|
229
|
+
return executeWithTracking(currentController, true);
|
|
230
|
+
}
|
|
231
|
+
return Promise.resolve({ data: void 0, error: void 0 });
|
|
232
|
+
};
|
|
233
|
+
const result = {
|
|
234
|
+
meta: metaSignal,
|
|
235
|
+
get input() {
|
|
236
|
+
return inputSignal();
|
|
237
|
+
},
|
|
238
|
+
data: dataSignal,
|
|
239
|
+
error: errorSignal,
|
|
240
|
+
loading: loadingSignal,
|
|
241
|
+
fetching: fetchingSignal,
|
|
242
|
+
abort,
|
|
243
|
+
refetch
|
|
244
|
+
};
|
|
245
|
+
return result;
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/injectWrite/index.ts
|
|
250
|
+
import { signal as signal2, effect as effect2, DestroyRef as DestroyRef2, inject as inject2 } from "@angular/core";
|
|
251
|
+
import {
|
|
252
|
+
createOperationController as createOperationController2,
|
|
253
|
+
createSelectorProxy as createSelectorProxy2,
|
|
254
|
+
resolvePath as resolvePath2,
|
|
255
|
+
resolveTags as resolveTags2
|
|
256
|
+
} from "@spoosh/core";
|
|
257
|
+
function createInjectWrite(options) {
|
|
258
|
+
const { api, stateManager, pluginExecutor, eventEmitter } = options;
|
|
259
|
+
return function injectWrite(writeFn) {
|
|
260
|
+
const destroyRef = inject2(DestroyRef2);
|
|
261
|
+
const selectorResult = {
|
|
262
|
+
call: null,
|
|
263
|
+
selector: null
|
|
264
|
+
};
|
|
265
|
+
const selectorProxy = createSelectorProxy2(
|
|
266
|
+
(result2) => {
|
|
267
|
+
selectorResult.call = result2.call;
|
|
268
|
+
selectorResult.selector = result2.selector;
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
writeFn(selectorProxy);
|
|
272
|
+
const selectedEndpoint = selectorResult.selector;
|
|
273
|
+
if (!selectedEndpoint) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
"injectWrite requires selecting an HTTP method ($post, $put, $patch, $delete). Example: injectWrite((api) => api.posts.$post)"
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const queryKey = stateManager.createQueryKey({
|
|
279
|
+
path: selectedEndpoint.path,
|
|
280
|
+
method: selectedEndpoint.method,
|
|
281
|
+
options: void 0
|
|
282
|
+
});
|
|
283
|
+
const controller = createOperationController2({
|
|
284
|
+
operationType: "write",
|
|
285
|
+
path: selectedEndpoint.path,
|
|
286
|
+
method: selectedEndpoint.method,
|
|
287
|
+
tags: [],
|
|
288
|
+
stateManager,
|
|
289
|
+
eventEmitter,
|
|
290
|
+
pluginExecutor,
|
|
291
|
+
hookId: `angular-${Math.random().toString(36).slice(2)}`,
|
|
292
|
+
fetchFn: async (fetchOpts) => {
|
|
293
|
+
const params = fetchOpts?.params;
|
|
294
|
+
const resolvedPath = resolvePath2(selectedEndpoint.path, params);
|
|
295
|
+
let current = api;
|
|
296
|
+
for (const segment of resolvedPath) {
|
|
297
|
+
current = current[segment];
|
|
298
|
+
}
|
|
299
|
+
const method = current[selectedEndpoint.method];
|
|
300
|
+
return method(fetchOpts);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
const dataSignal = signal2(void 0);
|
|
304
|
+
const errorSignal = signal2(void 0);
|
|
305
|
+
const loadingSignal = signal2(false);
|
|
306
|
+
const lastTriggerOptionsSignal = signal2(void 0);
|
|
307
|
+
const metaSignal = signal2({});
|
|
308
|
+
const subscription = controller.subscribe(() => {
|
|
309
|
+
const state = controller.getState();
|
|
310
|
+
dataSignal.set(state.data);
|
|
311
|
+
errorSignal.set(state.error);
|
|
312
|
+
const entry = stateManager.getCache(queryKey);
|
|
313
|
+
const newMeta = entry?.pluginResult ? Object.fromEntries(entry.pluginResult) : {};
|
|
314
|
+
metaSignal.set(newMeta);
|
|
315
|
+
});
|
|
316
|
+
destroyRef.onDestroy(() => {
|
|
317
|
+
subscription();
|
|
318
|
+
});
|
|
319
|
+
const reset = () => {
|
|
320
|
+
stateManager.deleteCache(queryKey);
|
|
321
|
+
errorSignal.set(void 0);
|
|
322
|
+
loadingSignal.set(false);
|
|
323
|
+
};
|
|
324
|
+
const abort = () => {
|
|
325
|
+
controller.abort();
|
|
326
|
+
};
|
|
327
|
+
const trigger = async (triggerOptions) => {
|
|
328
|
+
lastTriggerOptionsSignal.set(triggerOptions);
|
|
329
|
+
loadingSignal.set(true);
|
|
330
|
+
const params = triggerOptions?.params;
|
|
331
|
+
const resolvedPath = resolvePath2(selectedEndpoint.path, params);
|
|
332
|
+
const tags = resolveTags2(triggerOptions, resolvedPath);
|
|
333
|
+
controller.setPluginOptions({ ...triggerOptions, tags });
|
|
334
|
+
try {
|
|
335
|
+
const response = await controller.execute(triggerOptions, {
|
|
336
|
+
force: true
|
|
337
|
+
});
|
|
338
|
+
if (response.error) {
|
|
339
|
+
errorSignal.set(response.error);
|
|
340
|
+
} else {
|
|
341
|
+
errorSignal.set(void 0);
|
|
342
|
+
}
|
|
343
|
+
return response;
|
|
344
|
+
} catch (err) {
|
|
345
|
+
errorSignal.set(err);
|
|
346
|
+
throw err;
|
|
347
|
+
} finally {
|
|
348
|
+
loadingSignal.set(false);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const inputSignal = signal2({});
|
|
352
|
+
effect2(
|
|
353
|
+
() => {
|
|
354
|
+
const opts = lastTriggerOptionsSignal();
|
|
355
|
+
const inputInner = {};
|
|
356
|
+
if (opts?.query !== void 0) {
|
|
357
|
+
inputInner.query = opts.query;
|
|
358
|
+
}
|
|
359
|
+
if (opts?.body !== void 0) {
|
|
360
|
+
inputInner.body = opts.body;
|
|
361
|
+
}
|
|
362
|
+
if (opts?.formData !== void 0) {
|
|
363
|
+
inputInner.formData = opts.formData;
|
|
364
|
+
}
|
|
365
|
+
if (opts?.params !== void 0) {
|
|
366
|
+
inputInner.params = opts.params;
|
|
367
|
+
}
|
|
368
|
+
inputSignal.set(inputInner);
|
|
369
|
+
},
|
|
370
|
+
{ allowSignalWrites: true }
|
|
371
|
+
);
|
|
372
|
+
const result = {
|
|
373
|
+
trigger,
|
|
374
|
+
meta: metaSignal,
|
|
375
|
+
input: inputSignal,
|
|
376
|
+
data: dataSignal,
|
|
377
|
+
error: errorSignal,
|
|
378
|
+
loading: loadingSignal,
|
|
379
|
+
reset,
|
|
380
|
+
abort
|
|
381
|
+
};
|
|
382
|
+
return result;
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/injectInfiniteRead/index.ts
|
|
387
|
+
import {
|
|
388
|
+
signal as signal3,
|
|
389
|
+
computed,
|
|
390
|
+
effect as effect3,
|
|
391
|
+
DestroyRef as DestroyRef3,
|
|
392
|
+
inject as inject3,
|
|
393
|
+
untracked as untracked2
|
|
394
|
+
} from "@angular/core";
|
|
395
|
+
import {
|
|
396
|
+
createInfiniteReadController,
|
|
397
|
+
createSelectorProxy as createSelectorProxy3,
|
|
398
|
+
resolvePath as resolvePath3,
|
|
399
|
+
resolveTags as resolveTags3
|
|
400
|
+
} from "@spoosh/core";
|
|
401
|
+
function createInjectInfiniteRead(options) {
|
|
402
|
+
const { api, stateManager, eventEmitter, pluginExecutor } = options;
|
|
403
|
+
return function injectInfiniteRead(readFn, readOptions) {
|
|
404
|
+
const destroyRef = inject3(DestroyRef3);
|
|
405
|
+
const {
|
|
406
|
+
enabled: enabledOption = true,
|
|
407
|
+
tags,
|
|
408
|
+
additionalTags,
|
|
409
|
+
canFetchNext,
|
|
410
|
+
nextPageRequest,
|
|
411
|
+
merger,
|
|
412
|
+
canFetchPrev,
|
|
413
|
+
prevPageRequest,
|
|
414
|
+
...pluginOpts
|
|
415
|
+
} = readOptions;
|
|
416
|
+
const getEnabled = () => typeof enabledOption === "function" ? enabledOption() : enabledOption;
|
|
417
|
+
const callbackRefs = {
|
|
418
|
+
canFetchNext,
|
|
419
|
+
canFetchPrev,
|
|
420
|
+
nextPageRequest,
|
|
421
|
+
prevPageRequest,
|
|
422
|
+
merger
|
|
423
|
+
};
|
|
424
|
+
const selectorResult = {
|
|
425
|
+
call: null,
|
|
426
|
+
selector: null
|
|
427
|
+
};
|
|
428
|
+
const selectorProxy = createSelectorProxy3(
|
|
429
|
+
(result2) => {
|
|
430
|
+
selectorResult.call = result2.call;
|
|
431
|
+
selectorResult.selector = result2.selector;
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
readFn(selectorProxy);
|
|
435
|
+
const capturedCall = selectorResult.call;
|
|
436
|
+
if (!capturedCall) {
|
|
437
|
+
throw new Error(
|
|
438
|
+
"injectInfiniteRead requires calling an HTTP method ($get). Example: injectInfiniteRead((api) => api.posts.$get())"
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
const requestOptions = capturedCall.options;
|
|
442
|
+
const initialRequest = {
|
|
443
|
+
query: requestOptions?.query,
|
|
444
|
+
params: requestOptions?.params,
|
|
445
|
+
body: requestOptions?.body
|
|
446
|
+
};
|
|
447
|
+
const baseOptionsForKey = {
|
|
448
|
+
...capturedCall.options,
|
|
449
|
+
query: void 0,
|
|
450
|
+
params: void 0,
|
|
451
|
+
body: void 0
|
|
452
|
+
};
|
|
453
|
+
const resolvedPath = resolvePath3(capturedCall.path, requestOptions?.params);
|
|
454
|
+
const resolvedTags = resolveTags3({ tags, additionalTags }, resolvedPath);
|
|
455
|
+
const controller = createInfiniteReadController({
|
|
456
|
+
path: capturedCall.path,
|
|
457
|
+
method: capturedCall.method,
|
|
458
|
+
tags: resolvedTags,
|
|
459
|
+
initialRequest,
|
|
460
|
+
baseOptionsForKey,
|
|
461
|
+
canFetchNext: (ctx) => callbackRefs.canFetchNext(ctx),
|
|
462
|
+
canFetchPrev: canFetchPrev ? (ctx) => callbackRefs.canFetchPrev?.(ctx) ?? false : void 0,
|
|
463
|
+
nextPageRequest: (ctx) => callbackRefs.nextPageRequest(ctx),
|
|
464
|
+
prevPageRequest: prevPageRequest ? (ctx) => callbackRefs.prevPageRequest?.(ctx) ?? {} : void 0,
|
|
465
|
+
merger: (responses) => callbackRefs.merger(responses),
|
|
466
|
+
stateManager,
|
|
467
|
+
eventEmitter,
|
|
468
|
+
pluginExecutor,
|
|
469
|
+
hookId: `angular-${Math.random().toString(36).slice(2)}`,
|
|
470
|
+
fetchFn: async (opts, abortSignal) => {
|
|
471
|
+
const fetchPath = resolvePath3(capturedCall.path, opts.params);
|
|
472
|
+
let current = api;
|
|
473
|
+
for (const segment of fetchPath) {
|
|
474
|
+
current = current[segment];
|
|
475
|
+
}
|
|
476
|
+
const method = current[capturedCall.method];
|
|
477
|
+
const fetchOptions = {
|
|
478
|
+
...capturedCall.options,
|
|
479
|
+
query: opts.query,
|
|
480
|
+
params: opts.params,
|
|
481
|
+
body: opts.body,
|
|
482
|
+
signal: abortSignal
|
|
483
|
+
};
|
|
484
|
+
return method(fetchOptions);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
controller.setPluginOptions(pluginOpts);
|
|
488
|
+
const dataSignal = signal3(void 0);
|
|
489
|
+
const allResponsesSignal = signal3(void 0);
|
|
490
|
+
const errorSignal = signal3(void 0);
|
|
491
|
+
const loadingSignal = signal3(false);
|
|
492
|
+
const canFetchNextSignal = signal3(false);
|
|
493
|
+
const canFetchPrevSignal = signal3(false);
|
|
494
|
+
const metaSignal = signal3({});
|
|
495
|
+
const queryKey = stateManager.createQueryKey({
|
|
496
|
+
path: capturedCall.path,
|
|
497
|
+
method: capturedCall.method,
|
|
498
|
+
options: baseOptionsForKey
|
|
499
|
+
});
|
|
500
|
+
const subscription = controller.subscribe(() => {
|
|
501
|
+
const state = controller.getState();
|
|
502
|
+
dataSignal.set(state.data);
|
|
503
|
+
allResponsesSignal.set(state.allResponses);
|
|
504
|
+
errorSignal.set(state.error);
|
|
505
|
+
canFetchNextSignal.set(state.canFetchNext);
|
|
506
|
+
canFetchPrevSignal.set(state.canFetchPrev);
|
|
507
|
+
const entry = stateManager.getCache(queryKey);
|
|
508
|
+
const newMeta = entry?.pluginResult ? Object.fromEntries(entry.pluginResult) : {};
|
|
509
|
+
metaSignal.set(newMeta);
|
|
510
|
+
});
|
|
511
|
+
const fetchingNextSignal = signal3(false);
|
|
512
|
+
const fetchingPrevSignal = signal3(false);
|
|
513
|
+
let prevContext = null;
|
|
514
|
+
let hasDoneInitialFetch = false;
|
|
515
|
+
let isMounted = false;
|
|
516
|
+
const updateSignalsFromState = () => {
|
|
517
|
+
const state = controller.getState();
|
|
518
|
+
dataSignal.set(state.data);
|
|
519
|
+
allResponsesSignal.set(state.allResponses);
|
|
520
|
+
errorSignal.set(state.error);
|
|
521
|
+
canFetchNextSignal.set(state.canFetchNext);
|
|
522
|
+
canFetchPrevSignal.set(state.canFetchPrev);
|
|
523
|
+
};
|
|
524
|
+
const triggerFetch = () => {
|
|
525
|
+
const currentState = controller.getState();
|
|
526
|
+
const isFetching = untracked2(
|
|
527
|
+
() => fetchingNextSignal() || fetchingPrevSignal()
|
|
528
|
+
);
|
|
529
|
+
if (currentState.data === void 0 && !isFetching) {
|
|
530
|
+
loadingSignal.set(true);
|
|
531
|
+
fetchingNextSignal.set(true);
|
|
532
|
+
controller.fetchNext().finally(() => {
|
|
533
|
+
updateSignalsFromState();
|
|
534
|
+
loadingSignal.set(false);
|
|
535
|
+
fetchingNextSignal.set(false);
|
|
536
|
+
});
|
|
537
|
+
} else if (currentState.data !== void 0) {
|
|
538
|
+
updateSignalsFromState();
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
const unsubInvalidate = eventEmitter.on(
|
|
542
|
+
"invalidate",
|
|
543
|
+
(invalidatedTags) => {
|
|
544
|
+
if (!getEnabled()) return;
|
|
545
|
+
const hasMatch = invalidatedTags.some(
|
|
546
|
+
(tag) => resolvedTags.includes(tag)
|
|
547
|
+
);
|
|
548
|
+
if (hasMatch) {
|
|
549
|
+
loadingSignal.set(true);
|
|
550
|
+
controller.refetch().finally(() => {
|
|
551
|
+
updateSignalsFromState();
|
|
552
|
+
loadingSignal.set(false);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
);
|
|
557
|
+
effect3(
|
|
558
|
+
() => {
|
|
559
|
+
const isEnabled = getEnabled();
|
|
560
|
+
if (!isEnabled) {
|
|
561
|
+
if (isMounted) {
|
|
562
|
+
controller.unmount();
|
|
563
|
+
isMounted = false;
|
|
564
|
+
}
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
if (!isMounted) {
|
|
568
|
+
controller.mount();
|
|
569
|
+
isMounted = true;
|
|
570
|
+
}
|
|
571
|
+
if (!hasDoneInitialFetch) {
|
|
572
|
+
hasDoneInitialFetch = true;
|
|
573
|
+
untracked2(() => {
|
|
574
|
+
triggerFetch();
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
if (prevContext) {
|
|
578
|
+
untracked2(() => {
|
|
579
|
+
controller.update(prevContext);
|
|
580
|
+
prevContext = null;
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
{ allowSignalWrites: true }
|
|
585
|
+
);
|
|
586
|
+
destroyRef.onDestroy(() => {
|
|
587
|
+
unsubInvalidate();
|
|
588
|
+
});
|
|
589
|
+
destroyRef.onDestroy(() => {
|
|
590
|
+
subscription();
|
|
591
|
+
if (isMounted) {
|
|
592
|
+
controller.unmount();
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
const fetchNext = async () => {
|
|
596
|
+
if (!getEnabled()) return;
|
|
597
|
+
fetchingNextSignal.set(true);
|
|
598
|
+
try {
|
|
599
|
+
await controller.fetchNext();
|
|
600
|
+
updateSignalsFromState();
|
|
601
|
+
} finally {
|
|
602
|
+
fetchingNextSignal.set(false);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
const fetchPrev = async () => {
|
|
606
|
+
if (!getEnabled()) return;
|
|
607
|
+
fetchingPrevSignal.set(true);
|
|
608
|
+
try {
|
|
609
|
+
await controller.fetchPrev();
|
|
610
|
+
updateSignalsFromState();
|
|
611
|
+
} finally {
|
|
612
|
+
fetchingPrevSignal.set(false);
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
const refetch = async () => {
|
|
616
|
+
if (!getEnabled()) return;
|
|
617
|
+
loadingSignal.set(true);
|
|
618
|
+
try {
|
|
619
|
+
await controller.refetch();
|
|
620
|
+
updateSignalsFromState();
|
|
621
|
+
} finally {
|
|
622
|
+
loadingSignal.set(false);
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
const abort = () => {
|
|
626
|
+
controller.abort();
|
|
627
|
+
};
|
|
628
|
+
const fetchingSignal = computed(
|
|
629
|
+
() => fetchingNextSignal() || fetchingPrevSignal()
|
|
630
|
+
);
|
|
631
|
+
const result = {
|
|
632
|
+
meta: metaSignal,
|
|
633
|
+
data: dataSignal,
|
|
634
|
+
allResponses: allResponsesSignal,
|
|
635
|
+
error: errorSignal,
|
|
636
|
+
loading: loadingSignal,
|
|
637
|
+
fetching: fetchingSignal,
|
|
638
|
+
fetchingNext: fetchingNextSignal,
|
|
639
|
+
fetchingPrev: fetchingPrevSignal,
|
|
640
|
+
canFetchNext: canFetchNextSignal,
|
|
641
|
+
canFetchPrev: canFetchPrevSignal,
|
|
642
|
+
fetchNext,
|
|
643
|
+
fetchPrev,
|
|
644
|
+
refetch,
|
|
645
|
+
abort
|
|
646
|
+
};
|
|
647
|
+
return result;
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// src/createAngularSpoosh/index.ts
|
|
652
|
+
function createAngularSpoosh(instance) {
|
|
653
|
+
const { api, stateManager, eventEmitter, pluginExecutor } = instance;
|
|
654
|
+
const injectRead = createInjectRead({
|
|
655
|
+
api,
|
|
656
|
+
stateManager,
|
|
657
|
+
eventEmitter,
|
|
658
|
+
pluginExecutor
|
|
659
|
+
});
|
|
660
|
+
const injectWrite = createInjectWrite({
|
|
661
|
+
api,
|
|
662
|
+
stateManager,
|
|
663
|
+
eventEmitter,
|
|
664
|
+
pluginExecutor
|
|
665
|
+
});
|
|
666
|
+
const injectInfiniteRead = createInjectInfiniteRead({
|
|
667
|
+
api,
|
|
668
|
+
stateManager,
|
|
669
|
+
eventEmitter,
|
|
670
|
+
pluginExecutor
|
|
671
|
+
});
|
|
672
|
+
const instanceApiContext = {
|
|
673
|
+
api,
|
|
674
|
+
stateManager,
|
|
675
|
+
eventEmitter,
|
|
676
|
+
pluginExecutor
|
|
677
|
+
};
|
|
678
|
+
const plugins = pluginExecutor.getPlugins();
|
|
679
|
+
const instanceApis = plugins.reduce(
|
|
680
|
+
(acc, plugin) => {
|
|
681
|
+
if (plugin.instanceApi) {
|
|
682
|
+
return { ...acc, ...plugin.instanceApi(instanceApiContext) };
|
|
683
|
+
}
|
|
684
|
+
return acc;
|
|
685
|
+
},
|
|
686
|
+
{}
|
|
687
|
+
);
|
|
688
|
+
return {
|
|
689
|
+
injectRead,
|
|
690
|
+
injectWrite,
|
|
691
|
+
injectInfiniteRead,
|
|
692
|
+
...instanceApis
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
export {
|
|
696
|
+
createAngularSpoosh
|
|
697
|
+
};
|