@supersoniks/concorde 4.9.91 → 4.9.92
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/build-infos.json +1 -1
- package/concorde-core.bundle.js +156 -156
- package/concorde-core.es.js +2348 -2331
- package/dist/concorde-core.bundle.js +156 -156
- package/dist/concorde-core.es.js +2348 -2331
- package/dist/js/core/decorators/api.js +64 -51
- package/dist/js/core/decorators/api.js.map +1 -1
- package/dist/types/core/decorators/api.d.ts +10 -5
- package/dist/types/core/decorators/api.d.ts.map +1 -1
- package/docs/assets/{index-BL6WVF7g.css → index-D2Q3JyQ6.css} +1 -1
- package/docs/assets/{index-DVn8AeyA.js → index-DURr6O71.js} +466 -568
- package/docs/crawl/docs/_decorators/get.html +35 -10
- package/docs/crawl/docs/_decorators/patch.html +13 -5
- package/docs/crawl/docs/_decorators/post.html +39 -10
- package/docs/crawl/docs/_decorators/put.html +13 -5
- package/docs/crawl/hubs/decorators.html +43 -40
- package/docs/crawl/hubs/remote-data.html +43 -40
- package/docs/index.html +2 -2
- package/docs/llms-full.txt +98 -23
- package/docs/llms.txt +1 -1
- package/docs/src/docs/_decorators/get.md +39 -5
- package/docs/src/docs/_decorators/patch.md +8 -3
- package/docs/src/docs/_decorators/post.md +39 -6
- package/docs/src/docs/_decorators/put.md +8 -3
- package/docs/src/docs/search/docs-search.json +103 -183
- package/docs/src/tsconfig.json +268 -268
- package/package.json +1 -1
- package/src/core/decorators/api.post.spec.ts +2 -2
- package/src/core/decorators/api.spec.ts +47 -6
- package/src/core/decorators/api.ts +100 -64
- package/src/docs/_decorators/get.md +12 -5
- package/src/docs/_decorators/patch.md +7 -2
- package/src/docs/_decorators/post.md +12 -7
- package/src/docs/_decorators/put.md +7 -2
- package/src/docs/components/docs-demo-sources.ts +8 -8
- package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +2 -2
- package/src/docs/search/docs-search.json +2 -2
package/package.json
CHANGED
|
@@ -95,14 +95,14 @@ describe("post", () => {
|
|
|
95
95
|
vi.mocked(API.prototype.postDetailed).mockClear();
|
|
96
96
|
|
|
97
97
|
pub.set({ value: "second" });
|
|
98
|
-
localPost.send(el);
|
|
99
|
-
await flushSendFrames();
|
|
98
|
+
const payload = await localPost.send(el);
|
|
100
99
|
|
|
101
100
|
expect(API.prototype.postDetailed).toHaveBeenCalledTimes(1);
|
|
102
101
|
expect(API.prototype.postDetailed).toHaveBeenCalledWith(
|
|
103
102
|
"items/local-send",
|
|
104
103
|
{ value: "second" },
|
|
105
104
|
);
|
|
105
|
+
expect(payload?.result).toEqual({ ok: true });
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
it("peut décorer une méthode avec send après sa résolution", async () => {
|
|
@@ -209,16 +209,18 @@ describe("get", () => {
|
|
|
209
209
|
const el = document.createElement(tag) as InstanceType<typeof C>;
|
|
210
210
|
document.body.appendChild(el);
|
|
211
211
|
|
|
212
|
-
localGet.refetch(el);
|
|
213
|
-
|
|
212
|
+
const firstRefetch = localGet.refetch(el);
|
|
213
|
+
expect(firstRefetch).toBeInstanceOf(Promise);
|
|
214
|
+
const firstPayload = await firstRefetch;
|
|
214
215
|
expect(API.prototype.getDetailed).toHaveBeenCalledTimes(1);
|
|
216
|
+
expect(firstPayload?.result).toEqual({ id: "loaded" });
|
|
215
217
|
|
|
216
218
|
vi.mocked(API.prototype.getDetailed).mockClear();
|
|
217
219
|
el.payload = mockPayload({ id: "manual" });
|
|
218
|
-
localGet.refetch(el);
|
|
219
|
-
await flushGetFrames();
|
|
220
|
+
const secondPayload = await localGet.refetch(el);
|
|
220
221
|
|
|
221
222
|
expect(API.prototype.getDetailed).toHaveBeenCalledTimes(1);
|
|
223
|
+
expect(secondPayload?.result).toEqual({ id: "loaded" });
|
|
222
224
|
expect(el.payload?.result).toEqual({ id: "loaded" });
|
|
223
225
|
});
|
|
224
226
|
|
|
@@ -246,8 +248,7 @@ describe("get", () => {
|
|
|
246
248
|
vi.mocked(API.prototype.getDetailed).mockClear();
|
|
247
249
|
|
|
248
250
|
el.remove();
|
|
249
|
-
localGet.refetch(el);
|
|
250
|
-
await flushGetFrames();
|
|
251
|
+
await localGet.refetch(el);
|
|
251
252
|
|
|
252
253
|
expect(API.prototype.getDetailed).not.toHaveBeenCalled();
|
|
253
254
|
});
|
|
@@ -289,6 +290,46 @@ describe("get", () => {
|
|
|
289
290
|
expect(API.prototype.getDetailed).toHaveBeenCalledWith("rows/84");
|
|
290
291
|
});
|
|
291
292
|
|
|
293
|
+
it("attend le refetch avant la méthode avec when before", async () => {
|
|
294
|
+
const pubId = "getSpecMethodRefetchBefore";
|
|
295
|
+
PublisherManager.get(pubId).set(staticConfig);
|
|
296
|
+
const confKey = new DataProviderKey<APIConfiguration>(pubId);
|
|
297
|
+
const localGet = get(
|
|
298
|
+
new Endpoint<{ id: string }>("items/before"),
|
|
299
|
+
confKey,
|
|
300
|
+
);
|
|
301
|
+
const events: string[] = [];
|
|
302
|
+
const tag = "test-api-get-method-refetch-before";
|
|
303
|
+
|
|
304
|
+
class C extends HTMLElement {
|
|
305
|
+
@localGet
|
|
306
|
+
payload: ApiResult<{ id: string }> | null = null;
|
|
307
|
+
|
|
308
|
+
@refetch(localGet, { when: "before" })
|
|
309
|
+
reload() {
|
|
310
|
+
events.push("method");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (!customElements.get(tag)) {
|
|
314
|
+
customElements.define(tag, C);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const el = document.createElement(tag) as InstanceType<typeof C>;
|
|
318
|
+
document.body.appendChild(el);
|
|
319
|
+
await flushGetFrames();
|
|
320
|
+
vi.mocked(API.prototype.getDetailed).mockClear();
|
|
321
|
+
events.length = 0;
|
|
322
|
+
vi.mocked(API.prototype.getDetailed).mockImplementation(async () => {
|
|
323
|
+
events.push("request");
|
|
324
|
+
return mockPayload({ id: "loaded" }) as never;
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
await el.reload();
|
|
328
|
+
|
|
329
|
+
expect(events).toEqual(["request", "method"]);
|
|
330
|
+
expect(API.prototype.getDetailed).toHaveBeenCalledWith("items/before");
|
|
331
|
+
});
|
|
332
|
+
|
|
292
333
|
it("refetch automatique avec refetchEveryMs", async () => {
|
|
293
334
|
vi.useFakeTimers();
|
|
294
335
|
const itemEp = new Endpoint<{ id: string }>("items/1");
|
|
@@ -83,14 +83,32 @@ function isLitHost(component: unknown): component is LitHost {
|
|
|
83
83
|
* `rebindAll`, etc.) en un seul appel par frame — même principe que
|
|
84
84
|
* `dynamicPropertyWatch.ts` (`scheduleObservedPropertyChanges`).
|
|
85
85
|
*/
|
|
86
|
-
function createAnimationFrameCoalescedRunner(
|
|
86
|
+
function createAnimationFrameCoalescedRunner<T>(
|
|
87
|
+
run: () => Promise<T>,
|
|
88
|
+
): () => Promise<T> {
|
|
87
89
|
let frameId: number | null = null;
|
|
90
|
+
let pending: Array<{
|
|
91
|
+
resolve: (value: T) => void;
|
|
92
|
+
reject: (reason: unknown) => void;
|
|
93
|
+
}> = [];
|
|
94
|
+
|
|
88
95
|
return () => {
|
|
89
|
-
|
|
96
|
+
const promise = new Promise<T>((resolve, reject) => {
|
|
97
|
+
pending.push({ resolve, reject });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (frameId !== null) return promise;
|
|
90
101
|
frameId = requestAnimationFrame(() => {
|
|
91
102
|
frameId = null;
|
|
92
|
-
|
|
103
|
+
const current = pending;
|
|
104
|
+
pending = [];
|
|
105
|
+
void run().then(
|
|
106
|
+
(value) => current.forEach(({ resolve }) => resolve(value)),
|
|
107
|
+
(reason) => current.forEach(({ reject }) => reject(reason)),
|
|
108
|
+
);
|
|
93
109
|
});
|
|
110
|
+
|
|
111
|
+
return promise;
|
|
94
112
|
};
|
|
95
113
|
}
|
|
96
114
|
|
|
@@ -168,8 +186,7 @@ type TriggerPublisherBinding = {
|
|
|
168
186
|
};
|
|
169
187
|
|
|
170
188
|
type OperationBinding = {
|
|
171
|
-
|
|
172
|
-
run: () => void;
|
|
189
|
+
run: () => Promise<unknown>;
|
|
173
190
|
};
|
|
174
191
|
|
|
175
192
|
/**
|
|
@@ -182,8 +199,8 @@ type OperationBinding = {
|
|
|
182
199
|
class ApiOperationController {
|
|
183
200
|
private readonly bindings = new WeakMap<object, Set<OperationBinding>>();
|
|
184
201
|
|
|
185
|
-
bind(component: object, run: () =>
|
|
186
|
-
const binding: OperationBinding = {
|
|
202
|
+
bind(component: object, run: () => Promise<unknown>): () => void {
|
|
203
|
+
const binding: OperationBinding = { run };
|
|
187
204
|
let componentBindings = this.bindings.get(component);
|
|
188
205
|
if (!componentBindings) {
|
|
189
206
|
componentBindings = new Set();
|
|
@@ -201,8 +218,13 @@ class ApiOperationController {
|
|
|
201
218
|
};
|
|
202
219
|
}
|
|
203
220
|
|
|
204
|
-
run(component: object):
|
|
205
|
-
this.bindings.get(component)
|
|
221
|
+
run(component: object): Promise<unknown> {
|
|
222
|
+
const runs = [...(this.bindings.get(component) ?? [])].map(({ run }) =>
|
|
223
|
+
run(),
|
|
224
|
+
);
|
|
225
|
+
if (runs.length === 0) return Promise.resolve(undefined);
|
|
226
|
+
if (runs.length === 1) return runs[0];
|
|
227
|
+
return Promise.all(runs).then(() => undefined);
|
|
206
228
|
}
|
|
207
229
|
}
|
|
208
230
|
|
|
@@ -220,8 +242,10 @@ export type GetDecoratorHandle<T, Host extends object = object> =
|
|
|
220
242
|
*
|
|
221
243
|
* Le handle peut être conservé :
|
|
222
244
|
* `const usersGet = get(new Endpoint<User[]>("users"))`.
|
|
245
|
+
*
|
|
246
|
+
* La Promise est résolue après la fin du GET.
|
|
223
247
|
*/
|
|
224
|
-
refetch(component: Host):
|
|
248
|
+
refetch(component: Host): Promise<ApiResult<T> | undefined>;
|
|
225
249
|
};
|
|
226
250
|
|
|
227
251
|
export type ApiSendDecoratorHandle<T, Host extends object = object> =
|
|
@@ -230,19 +254,22 @@ export type ApiSendDecoratorHandle<T, Host extends object = object> =
|
|
|
230
254
|
* Relance l'opération d'écriture avec le body courant.
|
|
231
255
|
*
|
|
232
256
|
* Disponible avec `post`, `put` et `patch`.
|
|
257
|
+
* La Promise est résolue après la fin de l'opération.
|
|
233
258
|
*/
|
|
234
|
-
send(component: Host):
|
|
259
|
+
send(component: Host): Promise<ApiResult<T> | undefined>;
|
|
235
260
|
};
|
|
236
261
|
|
|
237
262
|
export type ApiMethodActionOptions = {
|
|
238
263
|
/**
|
|
239
264
|
* Moment du déclenchement par rapport à la méthode.
|
|
240
|
-
* `after` est la valeur par défaut ;
|
|
265
|
+
* `after` est la valeur par défaut ; l'action attend la résolution d'une
|
|
266
|
+
* Promise retournée par la méthode.
|
|
267
|
+
* Avec `before`, l'action est terminée avant l'appel de la méthode.
|
|
241
268
|
*/
|
|
242
269
|
when?: "before" | "after";
|
|
243
270
|
};
|
|
244
271
|
|
|
245
|
-
type ApiMethod = (this: object, ...args:
|
|
272
|
+
type ApiMethod = (this: object, ...args: unknown[]) => unknown;
|
|
246
273
|
|
|
247
274
|
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
248
275
|
return (
|
|
@@ -253,7 +280,7 @@ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
|
253
280
|
}
|
|
254
281
|
|
|
255
282
|
function createApiMethodActionDecorator(
|
|
256
|
-
action: (component: object) =>
|
|
283
|
+
action: (component: object) => Promise<unknown>,
|
|
257
284
|
options?: ApiMethodActionOptions,
|
|
258
285
|
): MethodDecorator {
|
|
259
286
|
const when = options?.when ?? "after";
|
|
@@ -262,22 +289,19 @@ function createApiMethodActionDecorator(
|
|
|
262
289
|
const original = descriptor.value as ApiMethod | undefined;
|
|
263
290
|
if (!original) return;
|
|
264
291
|
|
|
265
|
-
const wrapped = function (this: object, ...args:
|
|
292
|
+
const wrapped = function (this: object, ...args: unknown[]) {
|
|
266
293
|
if (when === "before") {
|
|
267
|
-
action(this);
|
|
268
|
-
return original.apply(this, args);
|
|
294
|
+
return action(this).then(() => original.apply(this, args));
|
|
269
295
|
}
|
|
270
296
|
|
|
271
297
|
const result = original.apply(this, args);
|
|
272
298
|
if (isPromiseLike(result)) {
|
|
273
299
|
return result.then((value) => {
|
|
274
|
-
action(this);
|
|
275
|
-
return value;
|
|
300
|
+
return action(this).then(() => value);
|
|
276
301
|
});
|
|
277
302
|
}
|
|
278
303
|
|
|
279
|
-
action(this);
|
|
280
|
-
return result;
|
|
304
|
+
return action(this).then(() => result);
|
|
281
305
|
};
|
|
282
306
|
descriptor.value = wrapped as unknown as typeof descriptor.value;
|
|
283
307
|
};
|
|
@@ -362,7 +386,7 @@ function detachConfigPublisher(state: ApiGetState): void {
|
|
|
362
386
|
* payload?: ApiResult<User[]>;
|
|
363
387
|
*
|
|
364
388
|
* refresh() {
|
|
365
|
-
* usersGet.refetch(this);
|
|
389
|
+
* await usersGet.refetch(this);
|
|
366
390
|
* }
|
|
367
391
|
*
|
|
368
392
|
* @example
|
|
@@ -467,18 +491,18 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
|
|
|
467
491
|
if (!state.refetchEveryMs || state.refetchEveryMs <= 0) return;
|
|
468
492
|
state.refetchTimeoutId = setTimeout(() => {
|
|
469
493
|
state.refetchTimeoutId = null;
|
|
470
|
-
runFetch();
|
|
494
|
+
void runFetch().catch(() => {});
|
|
471
495
|
}, state.refetchEveryMs);
|
|
472
496
|
};
|
|
473
497
|
|
|
474
|
-
const runFetch = () => {
|
|
498
|
+
const runFetch = async (): Promise<ApiResult<T> | undefined> => {
|
|
475
499
|
const resolution = isDynamicPath
|
|
476
500
|
? resolveDynamicPath(component, pathTemplate, pathOptions)
|
|
477
501
|
: { ready: true, path: pathTemplate };
|
|
478
502
|
if (!resolution.ready || !resolution.path) {
|
|
479
503
|
comp[propertyKey] = undefined;
|
|
480
504
|
scheduleRefetch();
|
|
481
|
-
return;
|
|
505
|
+
return undefined;
|
|
482
506
|
}
|
|
483
507
|
let config: APIConfiguration | null = null;
|
|
484
508
|
if (usesPublisherConfig && configurationKeyPath) {
|
|
@@ -490,7 +514,7 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
|
|
|
490
514
|
if (!configRes.ready || !configRes.path) {
|
|
491
515
|
comp[propertyKey] = undefined;
|
|
492
516
|
scheduleRefetch();
|
|
493
|
-
return;
|
|
517
|
+
return undefined;
|
|
494
518
|
}
|
|
495
519
|
const configPublisher = getPublisherFromPath(configRes.path);
|
|
496
520
|
config = readApiConfigurationFromPublisher(configPublisher);
|
|
@@ -499,7 +523,10 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
|
|
|
499
523
|
}
|
|
500
524
|
if (!isScopedConfigurationReady(config)) {
|
|
501
525
|
if (!usesPublisherConfig && !state.scopeWatchCleanup) {
|
|
502
|
-
const scopeWatch = watchScopedConfiguration(
|
|
526
|
+
const scopeWatch = watchScopedConfiguration(
|
|
527
|
+
component,
|
|
528
|
+
scheduleFetchAndIgnoreErrors,
|
|
529
|
+
);
|
|
503
530
|
state.scopeWatchCleanup = scopeWatch;
|
|
504
531
|
state.cleanupWatchers.push(() => {
|
|
505
532
|
scopeWatch();
|
|
@@ -507,20 +534,21 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
|
|
|
507
534
|
});
|
|
508
535
|
}
|
|
509
536
|
scheduleRefetch();
|
|
510
|
-
return;
|
|
537
|
+
return undefined;
|
|
511
538
|
}
|
|
512
539
|
const generation = ++state.requestGeneration;
|
|
513
540
|
const api = new API(config);
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
scheduleRefetch();
|
|
520
|
-
});
|
|
541
|
+
const payload = await api.getDetailed<T>(resolution.path);
|
|
542
|
+
if (generation !== state.requestGeneration) return payload;
|
|
543
|
+
comp[propertyKey] = payload;
|
|
544
|
+
scheduleRefetch();
|
|
545
|
+
return payload;
|
|
521
546
|
};
|
|
522
547
|
|
|
523
548
|
const scheduleFetch = createAnimationFrameCoalescedRunner(runFetch);
|
|
549
|
+
const scheduleFetchAndIgnoreErrors = () => {
|
|
550
|
+
void scheduleFetch().catch(() => {});
|
|
551
|
+
};
|
|
524
552
|
state.cleanupOperation = operationController.bind(
|
|
525
553
|
component,
|
|
526
554
|
scheduleFetch,
|
|
@@ -539,7 +567,7 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
|
|
|
539
567
|
}
|
|
540
568
|
const publisher = getPublisherFromPath(triggerResolution.path);
|
|
541
569
|
if (!publisher) return;
|
|
542
|
-
const invalidateHandler =
|
|
570
|
+
const invalidateHandler = scheduleFetchAndIgnoreErrors;
|
|
543
571
|
publisher.onInvalidate(invalidateHandler);
|
|
544
572
|
state.triggerPublisher = publisher;
|
|
545
573
|
state.triggerInvalidateHandler = invalidateHandler;
|
|
@@ -562,7 +590,7 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
|
|
|
562
590
|
comp[propertyKey] = undefined;
|
|
563
591
|
return;
|
|
564
592
|
}
|
|
565
|
-
const mutationHandler =
|
|
593
|
+
const mutationHandler = scheduleFetchAndIgnoreErrors;
|
|
566
594
|
publisher.onInternalMutation(mutationHandler);
|
|
567
595
|
state.configPublisher = publisher;
|
|
568
596
|
state.configMutationHandler = mutationHandler;
|
|
@@ -572,7 +600,7 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
|
|
|
572
600
|
rebindTriggerPublisher();
|
|
573
601
|
rebindPublisherConfig();
|
|
574
602
|
if (!usesPublisherConfig) {
|
|
575
|
-
|
|
603
|
+
scheduleFetchAndIgnoreErrors();
|
|
576
604
|
}
|
|
577
605
|
};
|
|
578
606
|
|
|
@@ -597,13 +625,13 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
|
|
|
597
625
|
getDynamicWatchKeys.hooked,
|
|
598
626
|
component,
|
|
599
627
|
dependency,
|
|
600
|
-
|
|
628
|
+
scheduleFetchAndIgnoreErrors,
|
|
601
629
|
);
|
|
602
630
|
state.cleanupWatchers.push(unsubscribe);
|
|
603
631
|
}
|
|
604
632
|
}
|
|
605
633
|
rebindTriggerPublisher();
|
|
606
|
-
runFetch();
|
|
634
|
+
void runFetch().catch(() => {});
|
|
607
635
|
};
|
|
608
636
|
state.cleanupWatchers.push(
|
|
609
637
|
scheduleAfterHostReady(component, startScopedFetch),
|
|
@@ -628,7 +656,9 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
|
|
|
628
656
|
} as GetDecoratorHandle<T, object>;
|
|
629
657
|
|
|
630
658
|
decorator.refetch = (component: object) => {
|
|
631
|
-
operationController.run(component)
|
|
659
|
+
return operationController.run(component) as Promise<
|
|
660
|
+
ApiResult<T> | undefined
|
|
661
|
+
>;
|
|
632
662
|
};
|
|
633
663
|
|
|
634
664
|
return decorator;
|
|
@@ -823,18 +853,18 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
823
853
|
if (!state.refetchEveryMs || state.refetchEveryMs <= 0) return;
|
|
824
854
|
state.refetchTimeoutId = setTimeout(() => {
|
|
825
855
|
state.refetchTimeoutId = null;
|
|
826
|
-
runSend();
|
|
856
|
+
void runSend().catch(() => {});
|
|
827
857
|
}, state.refetchEveryMs);
|
|
828
858
|
};
|
|
829
859
|
|
|
830
|
-
const runSend = () => {
|
|
860
|
+
const runSend = async (): Promise<ApiResult<unknown> | undefined> => {
|
|
831
861
|
const resolution = isDynamicPath
|
|
832
862
|
? resolveDynamicPath(component, pathTemplate, pathOptions)
|
|
833
863
|
: { ready: true, path: pathTemplate };
|
|
834
864
|
if (!resolution.ready || !resolution.path) {
|
|
835
865
|
comp[propertyKey] = undefined;
|
|
836
866
|
scheduleRefetch();
|
|
837
|
-
return;
|
|
867
|
+
return undefined;
|
|
838
868
|
}
|
|
839
869
|
|
|
840
870
|
const bodyResolution = resolveDynamicPath(
|
|
@@ -845,7 +875,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
845
875
|
if (!bodyResolution.ready || !bodyResolution.path) {
|
|
846
876
|
comp[propertyKey] = undefined;
|
|
847
877
|
scheduleRefetch();
|
|
848
|
-
return;
|
|
878
|
+
return undefined;
|
|
849
879
|
}
|
|
850
880
|
|
|
851
881
|
const bodyPublisher = getPublisherFromPath(bodyResolution.path);
|
|
@@ -855,7 +885,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
855
885
|
(body === null || body === undefined)
|
|
856
886
|
) {
|
|
857
887
|
scheduleRefetch();
|
|
858
|
-
return;
|
|
888
|
+
return undefined;
|
|
859
889
|
}
|
|
860
890
|
|
|
861
891
|
let config: APIConfiguration | null = null;
|
|
@@ -868,7 +898,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
868
898
|
if (!configRes.ready || !configRes.path) {
|
|
869
899
|
comp[propertyKey] = undefined;
|
|
870
900
|
scheduleRefetch();
|
|
871
|
-
return;
|
|
901
|
+
return undefined;
|
|
872
902
|
}
|
|
873
903
|
const configPublisher = getPublisherFromPath(configRes.path);
|
|
874
904
|
config = readApiConfigurationFromPublisher(configPublisher);
|
|
@@ -878,7 +908,10 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
878
908
|
|
|
879
909
|
if (!isScopedConfigurationReady(config)) {
|
|
880
910
|
if (!usesPublisherConfig && !state.scopeWatchCleanup) {
|
|
881
|
-
const scopeWatch = watchScopedConfiguration(
|
|
911
|
+
const scopeWatch = watchScopedConfiguration(
|
|
912
|
+
component,
|
|
913
|
+
scheduleSendAndIgnoreErrors,
|
|
914
|
+
);
|
|
882
915
|
state.scopeWatchCleanup = scopeWatch;
|
|
883
916
|
state.cleanupWatchers.push(() => {
|
|
884
917
|
scopeWatch();
|
|
@@ -886,21 +919,22 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
886
919
|
});
|
|
887
920
|
}
|
|
888
921
|
scheduleRefetch();
|
|
889
|
-
return;
|
|
922
|
+
return undefined;
|
|
890
923
|
}
|
|
891
924
|
|
|
892
925
|
const generation = ++state.requestGeneration;
|
|
893
926
|
const api = new API(config);
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
},
|
|
900
|
-
);
|
|
927
|
+
const payload = await callDetailed(api, resolution.path, body);
|
|
928
|
+
if (generation !== state.requestGeneration) return payload;
|
|
929
|
+
comp[propertyKey] = payload;
|
|
930
|
+
scheduleRefetch();
|
|
931
|
+
return payload;
|
|
901
932
|
};
|
|
902
933
|
|
|
903
934
|
const scheduleSend = createAnimationFrameCoalescedRunner(runSend);
|
|
935
|
+
const scheduleSendAndIgnoreErrors = () => {
|
|
936
|
+
void scheduleSend().catch(() => {});
|
|
937
|
+
};
|
|
904
938
|
state.cleanupOperation = operationController.bind(
|
|
905
939
|
component,
|
|
906
940
|
scheduleSend,
|
|
@@ -921,7 +955,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
921
955
|
}
|
|
922
956
|
const publisher = getPublisherFromPath(bodyResolution.path);
|
|
923
957
|
if (!publisher) return;
|
|
924
|
-
const mutationHandler =
|
|
958
|
+
const mutationHandler = scheduleSendAndIgnoreErrors;
|
|
925
959
|
publisher.onInternalMutation(mutationHandler);
|
|
926
960
|
state.bodyPublisher = publisher;
|
|
927
961
|
state.bodyMutationHandler = mutationHandler;
|
|
@@ -940,7 +974,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
940
974
|
}
|
|
941
975
|
const publisher = getPublisherFromPath(triggerResolution.path);
|
|
942
976
|
if (!publisher) return;
|
|
943
|
-
const invalidateHandler =
|
|
977
|
+
const invalidateHandler = scheduleSendAndIgnoreErrors;
|
|
944
978
|
publisher.onInvalidate(invalidateHandler);
|
|
945
979
|
state.triggerPublisher = publisher;
|
|
946
980
|
state.triggerInvalidateHandler = invalidateHandler;
|
|
@@ -963,7 +997,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
963
997
|
comp[propertyKey] = undefined;
|
|
964
998
|
return;
|
|
965
999
|
}
|
|
966
|
-
const mutationHandler =
|
|
1000
|
+
const mutationHandler = scheduleSendAndIgnoreErrors;
|
|
967
1001
|
publisher.onInternalMutation(mutationHandler);
|
|
968
1002
|
state.configPublisher = publisher;
|
|
969
1003
|
state.configMutationHandler = mutationHandler;
|
|
@@ -974,7 +1008,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
974
1008
|
rebindTriggerPublisher();
|
|
975
1009
|
rebindPublisherConfig();
|
|
976
1010
|
if (state.autoPostOnBodyMutation) {
|
|
977
|
-
|
|
1011
|
+
scheduleSendAndIgnoreErrors();
|
|
978
1012
|
}
|
|
979
1013
|
};
|
|
980
1014
|
|
|
@@ -1003,7 +1037,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
1003
1037
|
getDynamicWatchKeys.hooked,
|
|
1004
1038
|
component,
|
|
1005
1039
|
dependency,
|
|
1006
|
-
|
|
1040
|
+
scheduleSendAndIgnoreErrors,
|
|
1007
1041
|
);
|
|
1008
1042
|
state.cleanupWatchers.push(unsubscribe);
|
|
1009
1043
|
}
|
|
@@ -1011,7 +1045,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
1011
1045
|
rebindBodyPublisher();
|
|
1012
1046
|
rebindTriggerPublisher();
|
|
1013
1047
|
if (state.autoPostOnBodyMutation) {
|
|
1014
|
-
|
|
1048
|
+
scheduleSendAndIgnoreErrors();
|
|
1015
1049
|
}
|
|
1016
1050
|
};
|
|
1017
1051
|
state.cleanupWatchers.push(
|
|
@@ -1038,7 +1072,9 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
1038
1072
|
} as ApiSendDecoratorHandle<T, object>;
|
|
1039
1073
|
|
|
1040
1074
|
decorator.send = (component: object) => {
|
|
1041
|
-
operationController.run(component)
|
|
1075
|
+
return operationController.run(component) as Promise<
|
|
1076
|
+
ApiResult<T> | undefined
|
|
1077
|
+
>;
|
|
1042
1078
|
};
|
|
1043
1079
|
|
|
1044
1080
|
return decorator;
|
|
@@ -1061,7 +1097,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
|
|
|
1061
1097
|
* @save
|
|
1062
1098
|
* payload?: ApiResult<User>;
|
|
1063
1099
|
* // Dans un handler du composant :
|
|
1064
|
-
* save.send(this);
|
|
1100
|
+
* await save.send(this);
|
|
1065
1101
|
*/
|
|
1066
1102
|
export function post<T, B, Ue = any>(
|
|
1067
1103
|
endpoint: Endpoint<T, Ue>,
|
|
@@ -37,15 +37,20 @@ const usersGet = get(new Endpoint<User[]>("users"));
|
|
|
37
37
|
@state()
|
|
38
38
|
payload?: ApiResult<User[]>;
|
|
39
39
|
|
|
40
|
-
refresh() {
|
|
41
|
-
usersGet.refetch(this);
|
|
40
|
+
async refresh() {
|
|
41
|
+
const payload = await usersGet.refetch(this);
|
|
42
|
+
console.log(payload?.result);
|
|
42
43
|
}
|
|
43
44
|
```
|
|
44
45
|
|
|
46
|
+
`refetch(component)` returns a `Promise<ApiResult<T> | undefined>` that resolves
|
|
47
|
+
after the GET has completed.
|
|
48
|
+
|
|
45
49
|
### From a method decorator
|
|
46
50
|
|
|
47
51
|
The refetch can decorate a local method. The default is `after`; if the
|
|
48
|
-
method returns a Promise, the GET waits for its resolution.
|
|
52
|
+
method returns a Promise, the GET waits for its resolution. The decorated method
|
|
53
|
+
call also waits for the refetch to complete.
|
|
49
54
|
|
|
50
55
|
```typescript
|
|
51
56
|
import { refetch } from "@supersoniks/concorde/decorators";
|
|
@@ -56,12 +61,12 @@ async reload() {
|
|
|
56
61
|
}
|
|
57
62
|
```
|
|
58
63
|
|
|
59
|
-
Use `{ when: "before" }` when the GET must
|
|
64
|
+
Use `{ when: "before" }` when the GET must complete before the method runs:
|
|
60
65
|
|
|
61
66
|
```typescript
|
|
62
67
|
@refetch(usersGet, { when: "before" })
|
|
63
68
|
reloadBefore() {
|
|
64
|
-
// The refetch
|
|
69
|
+
// The refetch has completed before this method runs.
|
|
65
70
|
}
|
|
66
71
|
```
|
|
67
72
|
|
|
@@ -76,6 +81,7 @@ configuration key is provided.
|
|
|
76
81
|
- `skipEmptyPlaceholder`: blocks the GET when `${prop}` is `""`.
|
|
77
82
|
- `refetchEveryMs`: automatically runs the GET again after this interval
|
|
78
83
|
(milliseconds). Omit the option, or use `0`, to disable polling.
|
|
84
|
+
- `triggerKey`: a publisher whose `invalidate()` runs the GET again.
|
|
79
85
|
|
|
80
86
|
With a configuration key, use:
|
|
81
87
|
|
|
@@ -88,6 +94,7 @@ With a configuration key, use:
|
|
|
88
94
|
- when the component connects and configuration is available;
|
|
89
95
|
- when a property used in `${...}` changes;
|
|
90
96
|
- when a configuration publisher changes;
|
|
97
|
+
- when the `triggerKey` publisher is invalidated;
|
|
91
98
|
- after each response when `refetchEveryMs` is set.
|
|
92
99
|
|
|
93
100
|
See [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path) for
|
|
@@ -19,11 +19,15 @@ payload?: ApiResult<User>;
|
|
|
19
19
|
To run the operation again from the component:
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
|
-
savePatch() {
|
|
23
|
-
patchUser.send(this);
|
|
22
|
+
async savePatch() {
|
|
23
|
+
await patchUser.send(this);
|
|
24
24
|
}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
`send(component)` returns a Promise resolved after the PATCH completes. See
|
|
28
|
+
[@post](#docs/_decorators/post.md/post) for the method-decorator form and the
|
|
29
|
+
`when: "before"` option.
|
|
30
|
+
|
|
27
31
|
The PATCH is also sent automatically when the body mutates, unless
|
|
28
32
|
`autoPostOnBodyMutation: false` is set. The available options are described in
|
|
29
33
|
[@post](#docs/_decorators/post.md/post):
|
|
@@ -32,6 +36,7 @@ The PATCH is also sent automatically when the body mutates, unless
|
|
|
32
36
|
- `skipIfBodyMissing`
|
|
33
37
|
- `refetchEveryMs`
|
|
34
38
|
- `skipEmptyPlaceholder`
|
|
39
|
+
- `triggerKey`
|
|
35
40
|
|
|
36
41
|
Pass explicit API configuration as the third argument:
|
|
37
42
|
|
|
@@ -32,18 +32,21 @@ Keep the decorator to access `send(component)`. The body is read again when the
|
|
|
32
32
|
operation runs.
|
|
33
33
|
|
|
34
34
|
```typescript
|
|
35
|
-
saveAgain() {
|
|
36
|
-
createUser.send(this);
|
|
35
|
+
async saveAgain() {
|
|
36
|
+
const payload = await createUser.send(this);
|
|
37
|
+
console.log(payload?.result);
|
|
37
38
|
}
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
`send()`
|
|
41
|
-
|
|
41
|
+
`send()` returns a `Promise<ApiResult<T> | undefined>` that resolves after the
|
|
42
|
+
operation completes. It is useful for “Save” or “Retry” buttons and can repeat
|
|
43
|
+
an operation with side effects.
|
|
42
44
|
|
|
43
45
|
### From a method decorator
|
|
44
46
|
|
|
45
47
|
The send action can decorate a local method. It runs after the method by
|
|
46
|
-
default
|
|
48
|
+
default, waits for an eventual Promise to resolve, and the decorated method
|
|
49
|
+
call waits for the send to complete:
|
|
47
50
|
|
|
48
51
|
```typescript
|
|
49
52
|
import { send } from "@supersoniks/concorde/decorators";
|
|
@@ -54,12 +57,12 @@ async save() {
|
|
|
54
57
|
}
|
|
55
58
|
```
|
|
56
59
|
|
|
57
|
-
Use `{ when: "before" }` when the
|
|
60
|
+
Use `{ when: "before" }` when the send must complete before the method runs:
|
|
58
61
|
|
|
59
62
|
```typescript
|
|
60
63
|
@send(createUser, { when: "before" })
|
|
61
64
|
saveBefore() {
|
|
62
|
-
// The POST
|
|
65
|
+
// The POST has completed before this method runs.
|
|
63
66
|
}
|
|
64
67
|
```
|
|
65
68
|
|
|
@@ -80,6 +83,7 @@ is provided.
|
|
|
80
83
|
repeating the operation is intended.
|
|
81
84
|
- `skipEmptyPlaceholder`: block the request when a `${...}` placeholder is
|
|
82
85
|
`""`.
|
|
86
|
+
- `triggerKey`: a publisher whose `invalidate()` sends the POST again.
|
|
83
87
|
|
|
84
88
|
With a configuration key:
|
|
85
89
|
|
|
@@ -96,6 +100,7 @@ dynamic path details.
|
|
|
96
100
|
- after a body mutation, unless `autoPostOnBodyMutation: false`;
|
|
97
101
|
- when a property used in `${...}` changes;
|
|
98
102
|
- when a configuration publisher changes;
|
|
103
|
+
- when the `triggerKey` publisher is invalidated;
|
|
99
104
|
- when `send()` is called;
|
|
100
105
|
- after each response when `refetchEveryMs` is set.
|
|
101
106
|
|