@supersoniks/concorde 4.9.91 → 4.9.93

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 (48) hide show
  1. package/ai/cursor/rules/concorde.mdc +7 -0
  2. package/ai/jetbrains/rules/concorde.md +6 -0
  3. package/ai/skills/concorde/SKILL.md +48 -0
  4. package/build-infos.json +1 -1
  5. package/concorde-core.bundle.js +257 -257
  6. package/concorde-core.es.js +3174 -3086
  7. package/dist/concorde-core.bundle.js +257 -257
  8. package/dist/concorde-core.es.js +3174 -3086
  9. package/dist/js/core/decorators/api.js +168 -63
  10. package/dist/js/core/decorators/api.js.map +1 -1
  11. package/dist/js/core/utils/api.js +29 -8
  12. package/dist/js/core/utils/api.js.map +1 -1
  13. package/dist/types/core/decorators/api.d.ts +30 -16
  14. package/dist/types/core/decorators/api.d.ts.map +1 -1
  15. package/dist/types/core/utils/api.d.ts +1 -1
  16. package/dist/types/core/utils/api.d.ts.map +1 -1
  17. package/docs/assets/{index-BL6WVF7g.css → index-D2Q3JyQ6.css} +1 -1
  18. package/docs/assets/{index-DVn8AeyA.js → index-DURr6O71.js} +466 -568
  19. package/docs/crawl/docs/_decorators/get.html +35 -10
  20. package/docs/crawl/docs/_decorators/patch.html +13 -5
  21. package/docs/crawl/docs/_decorators/post.html +39 -10
  22. package/docs/crawl/docs/_decorators/put.html +13 -5
  23. package/docs/crawl/hubs/decorators.html +43 -40
  24. package/docs/crawl/hubs/remote-data.html +43 -40
  25. package/docs/index.html +2 -2
  26. package/docs/llms-full.txt +98 -23
  27. package/docs/llms.txt +1 -1
  28. package/docs/src/docs/_decorators/get.md +39 -5
  29. package/docs/src/docs/_decorators/patch.md +8 -3
  30. package/docs/src/docs/_decorators/post.md +39 -6
  31. package/docs/src/docs/_decorators/put.md +8 -3
  32. package/docs/src/docs/search/docs-search.json +103 -183
  33. package/docs/src/tsconfig.json +268 -268
  34. package/package.json +1 -1
  35. package/src/core/decorators/api.post.spec.ts +101 -6
  36. package/src/core/decorators/api.spec.ts +137 -6
  37. package/src/core/decorators/api.ts +304 -148
  38. package/src/core/utils/api.spec.ts +73 -0
  39. package/src/core/utils/api.ts +44 -11
  40. package/src/docs/_decorators/get.md +43 -5
  41. package/src/docs/_decorators/patch.md +16 -2
  42. package/src/docs/_decorators/post.md +23 -7
  43. package/src/docs/_decorators/put.md +16 -2
  44. package/src/docs/components/docs-demo-sources.ts +22 -8
  45. package/src/docs/example/decorators-demo-post.ts +70 -0
  46. package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +61 -2
  47. package/src/docs/search/docs-search.json +122 -7
  48. package/src/tsconfig.tsbuildinfo +1 -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(run: () => void): () => void {
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
- if (frameId !== null) return;
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
- run();
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
- component: object;
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: () => void): () => void {
186
- const binding: OperationBinding = { component, run };
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,48 +218,94 @@ class ApiOperationController {
201
218
  };
202
219
  }
203
220
 
204
- run(component: object): void {
205
- this.bindings.get(component)?.forEach(({ run }) => run());
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
 
209
- type ApiPropertyDecorator<T, Host extends object> = <K extends string>(
210
- target: Host & {
211
- [P in K]?: ApiResult<T> | null | undefined;
212
- },
213
- propertyKey: K,
214
- ) => void;
215
-
216
- export type GetDecoratorHandle<T, Host extends object = object> =
217
- ApiPropertyDecorator<T, Host> & {
218
- /**
219
- * Relance le GET pour cette instance connectée.
220
- *
231
+ type ApiHandlerMethod<T> = (this: object, payload?: ApiResult<T>) => void;
232
+
233
+ export interface GetDecoratorHandle<T, Host extends object = object> {
234
+ (
235
+ target: object,
236
+ propertyKey: string | symbol,
237
+ descriptor: TypedPropertyDescriptor<ApiHandlerMethod<T>>,
238
+ ): void;
239
+ <K extends string>(
240
+ target: Host & {
241
+ [P in K]?: ApiResult<T> | null | undefined;
242
+ },
243
+ propertyKey: K,
244
+ ): void;
245
+ /**
246
+ * Relance le GET pour cette instance connectée.
247
+ *
221
248
  * Le handle peut être conservé :
222
249
  * `const usersGet = get(new Endpoint<User[]>("users"))`.
223
- */
224
- refetch(component: Host): void;
225
- };
226
-
227
- export type ApiSendDecoratorHandle<T, Host extends object = object> =
228
- ApiPropertyDecorator<T, Host> & {
229
- /**
230
- * Relance l'opération d'écriture avec le body courant.
231
250
  *
251
+ * La Promise est résolue après la fin du GET.
252
+ */
253
+ refetch(component: Host): Promise<ApiResult<T> | undefined>;
254
+ }
255
+
256
+ type ApiResultHandler<T> = (
257
+ component: object,
258
+ payload: ApiResult<T> | undefined,
259
+ ) => void;
260
+
261
+ type GetDecoratorHandleInternals<
262
+ T,
263
+ Host extends object = object,
264
+ > = GetDecoratorHandle<T, Host> & {
265
+ registerResultHandler(target: object, handler: ApiResultHandler<T>): void;
266
+ };
267
+
268
+ type ApiSendDecoratorHandleInternals<
269
+ T,
270
+ Host extends object = object,
271
+ > = ApiSendDecoratorHandle<T, Host> & {
272
+ registerResultHandler(target: object, handler: ApiResultHandler<T>): void;
273
+ };
274
+
275
+ let nextApiHandlerPropertyId = 0;
276
+
277
+ export interface ApiSendDecoratorHandle<T, Host extends object = object> {
278
+ (
279
+ target: object,
280
+ propertyKey: string | symbol,
281
+ descriptor: TypedPropertyDescriptor<ApiHandlerMethod<T>>,
282
+ ): void;
283
+ <K extends string>(
284
+ target: Host & {
285
+ [P in K]?: ApiResult<T> | null | undefined;
286
+ },
287
+ propertyKey: K,
288
+ ): void;
289
+ /**
290
+ * Relance l'opération d'écriture avec le body courant.
291
+ *
232
292
  * Disponible avec `post`, `put` et `patch`.
233
- */
234
- send(component: Host): void;
235
- };
293
+ * La Promise est résolue après la fin de l'opération.
294
+ */
295
+ send(component: Host): Promise<ApiResult<T> | undefined>;
296
+ }
236
297
 
237
298
  export type ApiMethodActionOptions = {
238
299
  /**
239
300
  * Moment du déclenchement par rapport à la méthode.
240
- * `after` est la valeur par défaut ; avec une Promise, l'action attend sa résolution.
301
+ * `after` est la valeur par défaut ; l'action attend la résolution d'une
302
+ * Promise retournée par la méthode.
303
+ * Avec `before`, l'action est terminée avant l'appel de la méthode.
241
304
  */
242
305
  when?: "before" | "after";
243
306
  };
244
307
 
245
- type ApiMethod = (this: object, ...args: any[]) => unknown;
308
+ type ApiMethod = (this: object, ...args: unknown[]) => unknown;
246
309
 
247
310
  function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
248
311
  return (
@@ -253,7 +316,7 @@ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
253
316
  }
254
317
 
255
318
  function createApiMethodActionDecorator(
256
- action: (component: object) => void,
319
+ action: (component: object) => Promise<unknown>,
257
320
  options?: ApiMethodActionOptions,
258
321
  ): MethodDecorator {
259
322
  const when = options?.when ?? "after";
@@ -262,22 +325,19 @@ function createApiMethodActionDecorator(
262
325
  const original = descriptor.value as ApiMethod | undefined;
263
326
  if (!original) return;
264
327
 
265
- const wrapped = function (this: object, ...args: any[]) {
328
+ const wrapped = function (this: object, ...args: unknown[]) {
266
329
  if (when === "before") {
267
- action(this);
268
- return original.apply(this, args);
330
+ return action(this).then(() => original.apply(this, args));
269
331
  }
270
332
 
271
333
  const result = original.apply(this, args);
272
334
  if (isPromiseLike(result)) {
273
335
  return result.then((value) => {
274
- action(this);
275
- return value;
336
+ return action(this).then(() => value);
276
337
  });
277
338
  }
278
339
 
279
- action(this);
280
- return result;
340
+ return action(this).then(() => result);
281
341
  };
282
342
  descriptor.value = wrapped as unknown as typeof descriptor.value;
283
343
  };
@@ -328,7 +388,7 @@ function detachConfigPublisher(state: ApiGetState): void {
328
388
 
329
389
  /**
330
390
  * Décorateur **`@get`** : charge des données via `API.getDetailed` et assigne un
331
- * `ApiResult<T>` (`request`, `response`, `result` typé `T`) ou `null`.
391
+ * `ApiResult<T>` (`request`, `response`, `result` typé `T`) ou `undefined`.
332
392
  * Le path est un `Endpoint<T, Ue>` ; les placeholders `${nomPropriété}` sont résolus sur l'instance (`Ue` contraint l’hôte).
333
393
  *
334
394
  * **Scoped (défaut)** : `HTML.getApiConfiguration(host)` avec `host` = l’élément connecté
@@ -350,6 +410,9 @@ function detachConfigPublisher(state: ApiGetState): void {
350
410
  * @get(new Endpoint<User, { userId: string }>("users/${userId}"))
351
411
  * payload?: ApiResult<User>;
352
412
  *
413
+ * @get(new Endpoint<User[]>("users"))
414
+ * onUsers(payload?: ApiResult<User[]>) {}
415
+ *
353
416
  * @example
354
417
  * const apiConf = new DataProviderKey<APIConfiguration>("myApiConf");
355
418
  * PublisherManager.get("myApiConf").set({ serviceURL: "...", token: null, ... });
@@ -362,7 +425,7 @@ function detachConfigPublisher(state: ApiGetState): void {
362
425
  * payload?: ApiResult<User[]>;
363
426
  *
364
427
  * refresh() {
365
- * usersGet.refetch(this);
428
+ * await usersGet.refetch(this);
366
429
  * }
367
430
  *
368
431
  * @example
@@ -376,10 +439,7 @@ export function get<T, Ue = any>(
376
439
  export function get<T, Ue = any, Uk = any>(
377
440
  endpoint: Endpoint<T, Ue>,
378
441
  configurationKey: DataProviderKey<APIConfiguration, Uk>,
379
- ): GetDecoratorHandle<
380
- T,
381
- DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>
382
- >;
442
+ ): GetDecoratorHandle<T, DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>>;
383
443
  export function get<T, Ue = any, Uk = any>(
384
444
  endpoint: Endpoint<T, Ue>,
385
445
  options: GetOptions,
@@ -388,15 +448,11 @@ export function get<T, Ue = any, Uk = any, Ut = any>(
388
448
  endpoint: Endpoint<T, Ue>,
389
449
  configurationKey: DataProviderKey<APIConfiguration, Uk>,
390
450
  options: GetOptions,
391
- ): GetDecoratorHandle<
392
- T,
393
- DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>
394
- >;
451
+ ): GetDecoratorHandle<T, DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>>;
395
452
  export function get<T, Ue = any, Uk = any, _Ut = any>(
396
453
  endpoint: Endpoint<T, Ue>,
397
454
  configurationKeyOrOptions?:
398
- | DataProviderKey<APIConfiguration, Uk>
399
- | GetOptions,
455
+ DataProviderKey<APIConfiguration, Uk> | GetOptions,
400
456
  maybeOptions?: GetOptions,
401
457
  ): GetDecoratorHandle<T, object> {
402
458
  const pathTemplate = endpoint.path;
@@ -429,9 +485,33 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
429
485
  const usesPublisherConfig = Boolean(configurationKeyPath);
430
486
  const refetchEveryMs = options?.refetchEveryMs ?? 0;
431
487
  const operationController = new ApiOperationController();
432
-
433
- const decorator = function (target: object, propertyKey: string) {
488
+ const resultHandlers = new WeakMap<object, Set<ApiResultHandler<T>>>();
489
+ const decoratedTargets = new WeakSet<object>();
490
+ const operationPropertyKeys = new WeakMap<object, Set<string>>();
491
+ let handlerPropertyKey: string | undefined;
492
+
493
+ const decorator = function (
494
+ target: object,
495
+ propertyKey: string,
496
+ descriptor?: PropertyDescriptor,
497
+ ) {
498
+ if (descriptor) {
499
+ const handler = descriptor.value as ApiHandlerMethod<T> | undefined;
500
+ if (!handler) return;
501
+
502
+ (decorator as GetDecoratorHandleInternals<T>).registerResultHandler(
503
+ target,
504
+ (component, payload) => handler.call(component, payload),
505
+ );
506
+ return;
507
+ }
434
508
  if (!target) return;
509
+ if (decoratedTargets.has(target)) {
510
+ operationPropertyKeys.get(target)?.add(propertyKey);
511
+ return;
512
+ }
513
+ decoratedTargets.add(target);
514
+ operationPropertyKeys.set(target, new Set([propertyKey]));
435
515
  setSubscribable(target);
436
516
  const stateKey = `__get_state_${propertyKey}`;
437
517
 
@@ -467,18 +547,24 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
467
547
  if (!state.refetchEveryMs || state.refetchEveryMs <= 0) return;
468
548
  state.refetchTimeoutId = setTimeout(() => {
469
549
  state.refetchTimeoutId = null;
470
- runFetch();
550
+ void runFetch().catch(() => {});
471
551
  }, state.refetchEveryMs);
472
552
  };
473
553
 
474
- const runFetch = () => {
554
+ const assignPayload = (payload: ApiResult<T> | undefined) => {
555
+ operationPropertyKeys.get(target)?.forEach((key) => {
556
+ comp[key] = payload;
557
+ });
558
+ };
559
+
560
+ const runFetch = async (): Promise<ApiResult<T> | undefined> => {
475
561
  const resolution = isDynamicPath
476
562
  ? resolveDynamicPath(component, pathTemplate, pathOptions)
477
563
  : { ready: true, path: pathTemplate };
478
564
  if (!resolution.ready || !resolution.path) {
479
- comp[propertyKey] = undefined;
565
+ assignPayload(undefined);
480
566
  scheduleRefetch();
481
- return;
567
+ return undefined;
482
568
  }
483
569
  let config: APIConfiguration | null = null;
484
570
  if (usesPublisherConfig && configurationKeyPath) {
@@ -488,9 +574,9 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
488
574
  pathOptions,
489
575
  );
490
576
  if (!configRes.ready || !configRes.path) {
491
- comp[propertyKey] = undefined;
577
+ assignPayload(undefined);
492
578
  scheduleRefetch();
493
- return;
579
+ return undefined;
494
580
  }
495
581
  const configPublisher = getPublisherFromPath(configRes.path);
496
582
  config = readApiConfigurationFromPublisher(configPublisher);
@@ -499,7 +585,10 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
499
585
  }
500
586
  if (!isScopedConfigurationReady(config)) {
501
587
  if (!usesPublisherConfig && !state.scopeWatchCleanup) {
502
- const scopeWatch = watchScopedConfiguration(component, runFetch);
588
+ const scopeWatch = watchScopedConfiguration(
589
+ component,
590
+ scheduleFetchAndIgnoreErrors,
591
+ );
503
592
  state.scopeWatchCleanup = scopeWatch;
504
593
  state.cleanupWatchers.push(() => {
505
594
  scopeWatch();
@@ -507,20 +596,26 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
507
596
  });
508
597
  }
509
598
  scheduleRefetch();
510
- return;
599
+ return undefined;
511
600
  }
512
601
  const generation = ++state.requestGeneration;
513
602
  const api = new API(config);
514
- void api
515
- .getDetailed<T>(resolution.path)
516
- .then((payload?: ApiResult<T>) => {
517
- if (generation !== state.requestGeneration) return;
518
- comp[propertyKey] = payload;
519
- scheduleRefetch();
520
- });
603
+ const payload = await api.getDetailed<T>(resolution.path);
604
+ if (generation !== state.requestGeneration) return payload;
605
+ operationPropertyKeys.get(target)?.forEach((key) => {
606
+ comp[key] = payload;
607
+ });
608
+ scheduleRefetch();
609
+ resultHandlers
610
+ .get(target)
611
+ ?.forEach((handler) => handler(component, payload));
612
+ return payload;
521
613
  };
522
614
 
523
615
  const scheduleFetch = createAnimationFrameCoalescedRunner(runFetch);
616
+ const scheduleFetchAndIgnoreErrors = () => {
617
+ void scheduleFetch().catch(() => {});
618
+ };
524
619
  state.cleanupOperation = operationController.bind(
525
620
  component,
526
621
  scheduleFetch,
@@ -539,7 +634,7 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
539
634
  }
540
635
  const publisher = getPublisherFromPath(triggerResolution.path);
541
636
  if (!publisher) return;
542
- const invalidateHandler = () => scheduleFetch();
637
+ const invalidateHandler = scheduleFetchAndIgnoreErrors;
543
638
  publisher.onInvalidate(invalidateHandler);
544
639
  state.triggerPublisher = publisher;
545
640
  state.triggerInvalidateHandler = invalidateHandler;
@@ -554,15 +649,15 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
554
649
  pathOptions,
555
650
  );
556
651
  if (!configRes.ready || !configRes.path) {
557
- comp[propertyKey] = undefined;
652
+ assignPayload(undefined);
558
653
  return;
559
654
  }
560
655
  const publisher = getPublisherFromPath(configRes.path);
561
656
  if (!publisher) {
562
- comp[propertyKey] = undefined;
657
+ assignPayload(undefined);
563
658
  return;
564
659
  }
565
- const mutationHandler = () => scheduleFetch();
660
+ const mutationHandler = scheduleFetchAndIgnoreErrors;
566
661
  publisher.onInternalMutation(mutationHandler);
567
662
  state.configPublisher = publisher;
568
663
  state.configMutationHandler = mutationHandler;
@@ -572,7 +667,7 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
572
667
  rebindTriggerPublisher();
573
668
  rebindPublisherConfig();
574
669
  if (!usesPublisherConfig) {
575
- scheduleFetch();
670
+ scheduleFetchAndIgnoreErrors();
576
671
  }
577
672
  };
578
673
 
@@ -597,13 +692,13 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
597
692
  getDynamicWatchKeys.hooked,
598
693
  component,
599
694
  dependency,
600
- () => scheduleFetch(),
695
+ scheduleFetchAndIgnoreErrors,
601
696
  );
602
697
  state.cleanupWatchers.push(unsubscribe);
603
698
  }
604
699
  }
605
700
  rebindTriggerPublisher();
606
- runFetch();
701
+ void runFetch().catch(() => {});
607
702
  };
608
703
  state.cleanupWatchers.push(
609
704
  scheduleAfterHostReady(component, startScopedFetch),
@@ -623,12 +718,30 @@ export function get<T, Ue = any, Uk = any, _Ut = any>(
623
718
  state.cleanupWatchers.forEach((cleanup) => cleanup());
624
719
  state.cleanupWatchers = [];
625
720
  state.requestGeneration++;
626
- comp[propertyKey] = undefined;
721
+ operationPropertyKeys.get(target)?.forEach((key) => {
722
+ comp[key] = undefined;
723
+ });
627
724
  });
628
- } as GetDecoratorHandle<T, object>;
725
+ } as GetDecoratorHandleInternals<T>;
629
726
 
630
727
  decorator.refetch = (component: object) => {
631
- operationController.run(component);
728
+ return operationController.run(component) as Promise<
729
+ ApiResult<T> | undefined
730
+ >;
731
+ };
732
+
733
+ decorator.registerResultHandler = (target, handler) => {
734
+ let handlers = resultHandlers.get(target);
735
+ if (!handlers) {
736
+ handlers = new Set();
737
+ resultHandlers.set(target, handlers);
738
+ }
739
+ handlers.add(handler);
740
+
741
+ if (!decoratedTargets.has(target)) {
742
+ handlerPropertyKey ??= `__get_handler_${nextApiHandlerPropertyId++}`;
743
+ decorator(target, handlerPropertyKey);
744
+ }
632
745
  };
633
746
 
634
747
  return decorator;
@@ -724,22 +837,21 @@ function readBodyFromPublisher(publisher: DataProvider | null): unknown {
724
837
 
725
838
  type ApiSendMethod = "POST" | "PUT" | "PATCH";
726
839
 
727
- type ApiSendDetailedCaller = (
840
+ type ApiSendDetailedCaller<T> = (
728
841
  api: API,
729
842
  path: string,
730
843
  body: unknown,
731
- ) => Promise<ApiResult<unknown> | undefined>;
844
+ ) => Promise<ApiResult<T> | undefined>;
732
845
 
733
846
  function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
734
847
  method: ApiSendMethod,
735
- callDetailed: ApiSendDetailedCaller,
848
+ callDetailed: ApiSendDetailedCaller<T>,
736
849
  ) {
737
850
  return function (
738
851
  endpoint: Endpoint<T, Ue>,
739
852
  bodyKey: DataProviderKey<B>,
740
853
  configurationKeyOrOptions?:
741
- | DataProviderKey<APIConfiguration, Uk>
742
- | ApiSendOptions<Ut>,
854
+ DataProviderKey<APIConfiguration, Uk> | ApiSendOptions<Ut>,
743
855
  maybeOptions?: ApiSendOptions<Ut>,
744
856
  ): ApiSendDecoratorHandle<T, object> {
745
857
  const pathTemplate = endpoint.path;
@@ -779,9 +891,33 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
779
891
  };
780
892
  const stateKeyPrefix = `__${method.toLowerCase()}_state_`;
781
893
  const operationController = new ApiOperationController();
782
-
783
- const decorator = function (target: object, propertyKey: string) {
894
+ const resultHandlers = new WeakMap<object, Set<ApiResultHandler<T>>>();
895
+ const decoratedTargets = new WeakSet<object>();
896
+ const operationPropertyKeys = new WeakMap<object, Set<string>>();
897
+ let handlerPropertyKey: string | undefined;
898
+
899
+ const decorator = function (
900
+ target: object,
901
+ propertyKey: string,
902
+ descriptor?: PropertyDescriptor,
903
+ ) {
904
+ if (descriptor) {
905
+ const handler = descriptor.value as ApiHandlerMethod<T> | undefined;
906
+ if (!handler) return;
907
+
908
+ (decorator as ApiSendDecoratorHandleInternals<T>).registerResultHandler(
909
+ target,
910
+ (component, payload) => handler.call(component, payload),
911
+ );
912
+ return;
913
+ }
784
914
  if (!target) return;
915
+ if (decoratedTargets.has(target)) {
916
+ operationPropertyKeys.get(target)?.add(propertyKey);
917
+ return;
918
+ }
919
+ decoratedTargets.add(target);
920
+ operationPropertyKeys.set(target, new Set([propertyKey]));
785
921
  setSubscribable(target);
786
922
  const stateKey = `${stateKeyPrefix}${propertyKey}`;
787
923
 
@@ -823,18 +959,24 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
823
959
  if (!state.refetchEveryMs || state.refetchEveryMs <= 0) return;
824
960
  state.refetchTimeoutId = setTimeout(() => {
825
961
  state.refetchTimeoutId = null;
826
- runSend();
962
+ void runSend().catch(() => {});
827
963
  }, state.refetchEveryMs);
828
964
  };
829
965
 
830
- const runSend = () => {
966
+ const assignPayload = (payload: ApiResult<T> | undefined) => {
967
+ operationPropertyKeys.get(target)?.forEach((key) => {
968
+ comp[key] = payload;
969
+ });
970
+ };
971
+
972
+ const runSend = async (): Promise<ApiResult<T> | undefined> => {
831
973
  const resolution = isDynamicPath
832
974
  ? resolveDynamicPath(component, pathTemplate, pathOptions)
833
975
  : { ready: true, path: pathTemplate };
834
976
  if (!resolution.ready || !resolution.path) {
835
- comp[propertyKey] = undefined;
977
+ assignPayload(undefined);
836
978
  scheduleRefetch();
837
- return;
979
+ return undefined;
838
980
  }
839
981
 
840
982
  const bodyResolution = resolveDynamicPath(
@@ -843,9 +985,9 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
843
985
  pathOptions,
844
986
  );
845
987
  if (!bodyResolution.ready || !bodyResolution.path) {
846
- comp[propertyKey] = undefined;
988
+ assignPayload(undefined);
847
989
  scheduleRefetch();
848
- return;
990
+ return undefined;
849
991
  }
850
992
 
851
993
  const bodyPublisher = getPublisherFromPath(bodyResolution.path);
@@ -855,7 +997,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
855
997
  (body === null || body === undefined)
856
998
  ) {
857
999
  scheduleRefetch();
858
- return;
1000
+ return undefined;
859
1001
  }
860
1002
 
861
1003
  let config: APIConfiguration | null = null;
@@ -866,9 +1008,9 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
866
1008
  pathOptions,
867
1009
  );
868
1010
  if (!configRes.ready || !configRes.path) {
869
- comp[propertyKey] = undefined;
1011
+ assignPayload(undefined);
870
1012
  scheduleRefetch();
871
- return;
1013
+ return undefined;
872
1014
  }
873
1015
  const configPublisher = getPublisherFromPath(configRes.path);
874
1016
  config = readApiConfigurationFromPublisher(configPublisher);
@@ -878,7 +1020,10 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
878
1020
 
879
1021
  if (!isScopedConfigurationReady(config)) {
880
1022
  if (!usesPublisherConfig && !state.scopeWatchCleanup) {
881
- const scopeWatch = watchScopedConfiguration(component, runSend);
1023
+ const scopeWatch = watchScopedConfiguration(
1024
+ component,
1025
+ scheduleSendAndIgnoreErrors,
1026
+ );
882
1027
  state.scopeWatchCleanup = scopeWatch;
883
1028
  state.cleanupWatchers.push(() => {
884
1029
  scopeWatch();
@@ -886,21 +1031,35 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
886
1031
  });
887
1032
  }
888
1033
  scheduleRefetch();
889
- return;
1034
+ return undefined;
890
1035
  }
891
1036
 
892
1037
  const generation = ++state.requestGeneration;
893
1038
  const api = new API(config);
894
- void callDetailed(api, resolution.path, body).then(
895
- (payload?: ApiResult<unknown>) => {
896
- if (generation !== state.requestGeneration) return;
897
- comp[propertyKey] = payload;
898
- scheduleRefetch();
899
- },
900
- );
1039
+ let payload: ApiResult<T> | undefined;
1040
+ try {
1041
+ payload = await callDetailed(api, resolution.path, body);
1042
+ } catch (error) {
1043
+ if (generation === state.requestGeneration) {
1044
+ resultHandlers
1045
+ .get(target)
1046
+ ?.forEach((handler) => handler(component, undefined));
1047
+ }
1048
+ throw error;
1049
+ }
1050
+ if (generation !== state.requestGeneration) return payload;
1051
+ assignPayload(payload);
1052
+ scheduleRefetch();
1053
+ resultHandlers
1054
+ .get(target)
1055
+ ?.forEach((handler) => handler(component, payload));
1056
+ return payload;
901
1057
  };
902
1058
 
903
1059
  const scheduleSend = createAnimationFrameCoalescedRunner(runSend);
1060
+ const scheduleSendAndIgnoreErrors = () => {
1061
+ void scheduleSend().catch(() => {});
1062
+ };
904
1063
  state.cleanupOperation = operationController.bind(
905
1064
  component,
906
1065
  scheduleSend,
@@ -921,7 +1080,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
921
1080
  }
922
1081
  const publisher = getPublisherFromPath(bodyResolution.path);
923
1082
  if (!publisher) return;
924
- const mutationHandler = () => scheduleSend();
1083
+ const mutationHandler = scheduleSendAndIgnoreErrors;
925
1084
  publisher.onInternalMutation(mutationHandler);
926
1085
  state.bodyPublisher = publisher;
927
1086
  state.bodyMutationHandler = mutationHandler;
@@ -940,7 +1099,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
940
1099
  }
941
1100
  const publisher = getPublisherFromPath(triggerResolution.path);
942
1101
  if (!publisher) return;
943
- const invalidateHandler = () => scheduleSend();
1102
+ const invalidateHandler = scheduleSendAndIgnoreErrors;
944
1103
  publisher.onInvalidate(invalidateHandler);
945
1104
  state.triggerPublisher = publisher;
946
1105
  state.triggerInvalidateHandler = invalidateHandler;
@@ -955,15 +1114,15 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
955
1114
  pathOptions,
956
1115
  );
957
1116
  if (!configRes.ready || !configRes.path) {
958
- comp[propertyKey] = undefined;
1117
+ assignPayload(undefined);
959
1118
  return;
960
1119
  }
961
1120
  const publisher = getPublisherFromPath(configRes.path);
962
1121
  if (!publisher) {
963
- comp[propertyKey] = undefined;
1122
+ assignPayload(undefined);
964
1123
  return;
965
1124
  }
966
- const mutationHandler = () => scheduleSend();
1125
+ const mutationHandler = scheduleSendAndIgnoreErrors;
967
1126
  publisher.onInternalMutation(mutationHandler);
968
1127
  state.configPublisher = publisher;
969
1128
  state.configMutationHandler = mutationHandler;
@@ -974,7 +1133,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
974
1133
  rebindTriggerPublisher();
975
1134
  rebindPublisherConfig();
976
1135
  if (state.autoPostOnBodyMutation) {
977
- scheduleSend();
1136
+ scheduleSendAndIgnoreErrors();
978
1137
  }
979
1138
  };
980
1139
 
@@ -1003,7 +1162,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
1003
1162
  getDynamicWatchKeys.hooked,
1004
1163
  component,
1005
1164
  dependency,
1006
- () => scheduleSend(),
1165
+ scheduleSendAndIgnoreErrors,
1007
1166
  );
1008
1167
  state.cleanupWatchers.push(unsubscribe);
1009
1168
  }
@@ -1011,7 +1170,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
1011
1170
  rebindBodyPublisher();
1012
1171
  rebindTriggerPublisher();
1013
1172
  if (state.autoPostOnBodyMutation) {
1014
- scheduleSend();
1173
+ scheduleSendAndIgnoreErrors();
1015
1174
  }
1016
1175
  };
1017
1176
  state.cleanupWatchers.push(
@@ -1033,12 +1192,30 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
1033
1192
  state.cleanupWatchers.forEach((cleanup) => cleanup());
1034
1193
  state.cleanupWatchers = [];
1035
1194
  state.requestGeneration++;
1036
- comp[propertyKey] = undefined;
1195
+ operationPropertyKeys.get(target)?.forEach((key) => {
1196
+ comp[key] = undefined;
1197
+ });
1037
1198
  });
1038
- } as ApiSendDecoratorHandle<T, object>;
1199
+ } as ApiSendDecoratorHandleInternals<T>;
1039
1200
 
1040
1201
  decorator.send = (component: object) => {
1041
- operationController.run(component);
1202
+ return operationController.run(component) as Promise<
1203
+ ApiResult<T> | undefined
1204
+ >;
1205
+ };
1206
+
1207
+ decorator.registerResultHandler = (target, handler) => {
1208
+ let handlers = resultHandlers.get(target);
1209
+ if (!handlers) {
1210
+ handlers = new Set();
1211
+ resultHandlers.set(target, handlers);
1212
+ }
1213
+ handlers.add(handler);
1214
+
1215
+ if (!decoratedTargets.has(target)) {
1216
+ handlerPropertyKey ??= `__${method.toLowerCase()}_handler_${nextApiHandlerPropertyId++}`;
1217
+ decorator(target, handlerPropertyKey);
1218
+ }
1042
1219
  };
1043
1220
 
1044
1221
  return decorator;
@@ -1061,7 +1238,7 @@ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
1061
1238
  * @save
1062
1239
  * payload?: ApiResult<User>;
1063
1240
  * // Dans un handler du composant :
1064
- * save.send(this);
1241
+ * await save.send(this);
1065
1242
  */
1066
1243
  export function post<T, B, Ue = any>(
1067
1244
  endpoint: Endpoint<T, Ue>,
@@ -1071,10 +1248,7 @@ export function post<T, B, Ue = any, Uk = any>(
1071
1248
  endpoint: Endpoint<T, Ue>,
1072
1249
  bodyKey: DataProviderKey<B>,
1073
1250
  configurationKey: DataProviderKey<APIConfiguration, Uk>,
1074
- ): ApiSendDecoratorHandle<
1075
- T,
1076
- DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>
1077
- >;
1251
+ ): ApiSendDecoratorHandle<T, DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>>;
1078
1252
  export function post<T, B, Ue = any, Uk = any>(
1079
1253
  endpoint: Endpoint<T, Ue>,
1080
1254
  bodyKey: DataProviderKey<B>,
@@ -1085,16 +1259,12 @@ export function post<T, B, Ue = any, Uk = any, Ut = any>(
1085
1259
  bodyKey: DataProviderKey<B>,
1086
1260
  configurationKey: DataProviderKey<APIConfiguration, Uk>,
1087
1261
  options: PostOptions<Ut>,
1088
- ): ApiSendDecoratorHandle<
1089
- T,
1090
- DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>
1091
- >;
1262
+ ): ApiSendDecoratorHandle<T, DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>>;
1092
1263
  export function post<T, B, Ue = any, Uk = any, Ut = any>(
1093
1264
  endpoint: Endpoint<T, Ue>,
1094
1265
  bodyKey: DataProviderKey<B>,
1095
1266
  configurationKeyOrOptions?:
1096
- | DataProviderKey<APIConfiguration, Uk>
1097
- | PostOptions<Ut>,
1267
+ DataProviderKey<APIConfiguration, Uk> | PostOptions<Ut>,
1098
1268
  maybeOptions?: PostOptions<Ut>,
1099
1269
  ): ApiSendDecoratorHandle<T, object> {
1100
1270
  return createApiSendDecorator<T, B, Ue, Uk, Ut>("POST", (api, path, body) =>
@@ -1111,10 +1281,7 @@ export function put<T, B, Ue = any, Uk = any>(
1111
1281
  endpoint: Endpoint<T, Ue>,
1112
1282
  bodyKey: DataProviderKey<B>,
1113
1283
  configurationKey: DataProviderKey<APIConfiguration, Uk>,
1114
- ): ApiSendDecoratorHandle<
1115
- T,
1116
- DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>
1117
- >;
1284
+ ): ApiSendDecoratorHandle<T, DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>>;
1118
1285
  export function put<T, B, Ue = any, Uk = any>(
1119
1286
  endpoint: Endpoint<T, Ue>,
1120
1287
  bodyKey: DataProviderKey<B>,
@@ -1125,16 +1292,12 @@ export function put<T, B, Ue = any, Uk = any, Ut = any>(
1125
1292
  bodyKey: DataProviderKey<B>,
1126
1293
  configurationKey: DataProviderKey<APIConfiguration, Uk>,
1127
1294
  options: PutOptions<Ut>,
1128
- ): ApiSendDecoratorHandle<
1129
- T,
1130
- DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>
1131
- >;
1295
+ ): ApiSendDecoratorHandle<T, DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>>;
1132
1296
  export function put<T, B, Ue = any, Uk = any, Ut = any>(
1133
1297
  endpoint: Endpoint<T, Ue>,
1134
1298
  bodyKey: DataProviderKey<B>,
1135
1299
  configurationKeyOrOptions?:
1136
- | DataProviderKey<APIConfiguration, Uk>
1137
- | PutOptions<Ut>,
1300
+ DataProviderKey<APIConfiguration, Uk> | PutOptions<Ut>,
1138
1301
  maybeOptions?: PutOptions<Ut>,
1139
1302
  ): ApiSendDecoratorHandle<T, object> {
1140
1303
  return createApiSendDecorator<T, B, Ue, Uk, Ut>("PUT", (api, path, body) =>
@@ -1151,10 +1314,7 @@ export function patch<T, B, Ue = any, Uk = any>(
1151
1314
  endpoint: Endpoint<T, Ue>,
1152
1315
  bodyKey: DataProviderKey<B>,
1153
1316
  configurationKey: DataProviderKey<APIConfiguration, Uk>,
1154
- ): ApiSendDecoratorHandle<
1155
- T,
1156
- DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>
1157
- >;
1317
+ ): ApiSendDecoratorHandle<T, DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>>;
1158
1318
  export function patch<T, B, Ue = any, Uk = any>(
1159
1319
  endpoint: Endpoint<T, Ue>,
1160
1320
  bodyKey: DataProviderKey<B>,
@@ -1165,16 +1325,12 @@ export function patch<T, B, Ue = any, Uk = any, Ut = any>(
1165
1325
  bodyKey: DataProviderKey<B>,
1166
1326
  configurationKey: DataProviderKey<APIConfiguration, Uk>,
1167
1327
  options: PatchOptions<Ut>,
1168
- ): ApiSendDecoratorHandle<
1169
- T,
1170
- DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>
1171
- >;
1328
+ ): ApiSendDecoratorHandle<T, DataProviderKeyHost<Ue> & DataProviderKeyHost<Uk>>;
1172
1329
  export function patch<T, B, Ue = any, Uk = any, Ut = any>(
1173
1330
  endpoint: Endpoint<T, Ue>,
1174
1331
  bodyKey: DataProviderKey<B>,
1175
1332
  configurationKeyOrOptions?:
1176
- | DataProviderKey<APIConfiguration, Uk>
1177
- | PatchOptions<Ut>,
1333
+ DataProviderKey<APIConfiguration, Uk> | PatchOptions<Ut>,
1178
1334
  maybeOptions?: PatchOptions<Ut>,
1179
1335
  ): ApiSendDecoratorHandle<T, object> {
1180
1336
  return createApiSendDecorator<T, B, Ue, Uk, Ut>("PATCH", (api, path, body) =>