@supersoniks/concorde 4.7.4 → 4.8.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.
Files changed (143) hide show
  1. package/README.md +1 -1
  2. package/ai/cursor/rules/concorde.mdc +1 -1
  3. package/ai/skills/concorde-scope/SKILL.md +2 -2
  4. package/build-infos.json +1 -1
  5. package/concorde-core.bundle.js +289 -289
  6. package/concorde-core.es.js +4837 -4544
  7. package/dist/concorde-core.bundle.js +289 -289
  8. package/dist/concorde-core.es.js +4837 -4544
  9. package/dist/docs-mock-api-sw.js +19 -0
  10. package/dist/docs-mock-api-sw.js.map +2 -2
  11. package/docs/assets/index-wyNMyWT9.js +11196 -0
  12. package/docs/docs-mock-api-sw.js +19 -0
  13. package/docs/docs-mock-api-sw.js.map +2 -2
  14. package/docs/index.html +1 -1
  15. package/package.json +9 -1
  16. package/public/docs-mock-api-sw.js +19 -0
  17. package/public/docs-mock-api-sw.js.map +2 -2
  18. package/src/core/components/functional/example/example.ts +3 -3
  19. package/src/core/components/ui/icon/icon.ts +17 -2
  20. package/src/core/components/ui/menu/menu.ts +12 -3
  21. package/src/core/decorators/api.post.spec.ts +293 -0
  22. package/src/core/decorators/api.spec.ts +6 -6
  23. package/src/core/decorators/api.ts +643 -12
  24. package/src/core/decorators/subscriber/bind.ts +13 -5
  25. package/src/core/decorators/subscriber/dynamicPath.spec.ts +53 -0
  26. package/src/core/decorators/subscriber/dynamicPath.ts +23 -1
  27. package/src/core/decorators/subscriber/handle.ts +3 -1
  28. package/src/core/decorators/subscriber/onAssign.ts +10 -2
  29. package/src/core/decorators/subscriber/publish.ts +12 -2
  30. package/src/core/utils/PublisherProxy.ts +95 -11
  31. package/src/core/utils/api.ts +72 -3
  32. package/src/core/utils/dpOptions.spec.ts +56 -0
  33. package/src/core/utils/endpoint.ts +3 -3
  34. package/src/decorators.ts +17 -1
  35. package/src/docs/_core-concept/dataFlow.md +9 -3
  36. package/src/docs/_decorators/bind.md +2 -2
  37. package/src/docs/_decorators/get.md +13 -4
  38. package/src/docs/_decorators/handle.md +5 -1
  39. package/src/docs/_decorators/on-assign.md +2 -0
  40. package/src/docs/_decorators/patch.md +45 -0
  41. package/src/docs/_decorators/post.md +93 -0
  42. package/src/docs/_decorators/publish.md +1 -1
  43. package/src/docs/_decorators/put.md +43 -0
  44. package/src/docs/_decorators/subscribe.md +4 -1
  45. package/src/docs/_directives/sub.md +1 -1
  46. package/src/docs/_getting-started/my-first-component.md +1 -1
  47. package/src/docs/_misc/api-configuration.md +3 -1
  48. package/src/docs/_misc/dataProviderKey.md +2 -2
  49. package/src/docs/_misc/dynamic-path.md +71 -0
  50. package/src/docs/_misc/endpoint.md +5 -3
  51. package/src/docs/components/docs-demo-sources.ts +102 -3
  52. package/src/docs/components/docs-lit-demo-raw.ts +2 -26
  53. package/src/docs/components/docs-lit-demo.ts +9 -42
  54. package/src/docs/components/docs-source-excerpt.ts +53 -0
  55. package/src/docs/components/docs-source-link.ts +24 -8
  56. package/src/docs/components/docs-source-raw.ts +34 -0
  57. package/src/docs/example/decorators-demo-geo.ts +2 -2
  58. package/src/docs/example/decorators-demo-post.ts +249 -0
  59. package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +5 -5
  60. package/src/docs/example/decorators-demo.ts +1 -0
  61. package/src/docs/example/docs-api-config-demos.ts +5 -5
  62. package/src/docs/mock-api/router.ts +20 -0
  63. package/src/docs/navigation/navigation.ts +16 -0
  64. package/src/docs/search/docs-search.json +540 -15
  65. package/src/tsconfig.json +24 -0
  66. package/src/tsconfig.tsbuildinfo +1 -1
  67. package/vite.config.mts +1 -1
  68. package/docs/assets/index-CwtPzTFq.js +0 -7508
  69. package/docs/src/core/components/functional/date/date.md +0 -290
  70. package/docs/src/core/components/functional/fetch/fetch.md +0 -125
  71. package/docs/src/core/components/functional/if/if.md +0 -9
  72. package/docs/src/core/components/functional/list/list.md +0 -65
  73. package/docs/src/core/components/functional/mix/mix.md +0 -41
  74. package/docs/src/core/components/functional/queue/queue.md +0 -72
  75. package/docs/src/core/components/functional/router/router.md +0 -94
  76. package/docs/src/core/components/functional/sdui/default-library.json +0 -108
  77. package/docs/src/core/components/functional/sdui/example.json +0 -99
  78. package/docs/src/core/components/functional/sdui/sdui.md +0 -356
  79. package/docs/src/core/components/functional/states/states.md +0 -87
  80. package/docs/src/core/components/functional/submit/submit.md +0 -114
  81. package/docs/src/core/components/functional/subscriber/subscriber.md +0 -91
  82. package/docs/src/core/components/functional/value/value.md +0 -35
  83. package/docs/src/core/components/ui/alert/alert.md +0 -121
  84. package/docs/src/core/components/ui/alert-messages/alert-messages.md +0 -0
  85. package/docs/src/core/components/ui/badge/badge.md +0 -127
  86. package/docs/src/core/components/ui/button/button.md +0 -182
  87. package/docs/src/core/components/ui/captcha/captcha.md +0 -12
  88. package/docs/src/core/components/ui/card/card.md +0 -97
  89. package/docs/src/core/components/ui/divider/divider.md +0 -35
  90. package/docs/src/core/components/ui/form/checkbox/checkbox.md +0 -77
  91. package/docs/src/core/components/ui/form/fieldset/fieldset.md +0 -129
  92. package/docs/src/core/components/ui/form/form-actions/form-actions.md +0 -77
  93. package/docs/src/core/components/ui/form/form-layout/form-layout.md +0 -44
  94. package/docs/src/core/components/ui/form/input/input.md +0 -142
  95. package/docs/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +0 -133
  96. package/docs/src/core/components/ui/form/radio/radio.md +0 -57
  97. package/docs/src/core/components/ui/form/select/select.md +0 -71
  98. package/docs/src/core/components/ui/form/switch/switch.md +0 -57
  99. package/docs/src/core/components/ui/form/textarea/textarea.md +0 -65
  100. package/docs/src/core/components/ui/group/group.md +0 -75
  101. package/docs/src/core/components/ui/icon/icon.md +0 -125
  102. package/docs/src/core/components/ui/icon/icons.json +0 -1
  103. package/docs/src/core/components/ui/image/image.md +0 -107
  104. package/docs/src/core/components/ui/link/link.md +0 -43
  105. package/docs/src/core/components/ui/loader/loader.md +0 -55
  106. package/docs/src/core/components/ui/menu/menu.md +0 -329
  107. package/docs/src/core/components/ui/modal/modal.md +0 -119
  108. package/docs/src/core/components/ui/pop/pop.md +0 -96
  109. package/docs/src/core/components/ui/progress/progress.md +0 -63
  110. package/docs/src/core/components/ui/table/table.md +0 -455
  111. package/docs/src/core/components/ui/toast/toast.md +0 -166
  112. package/docs/src/core/components/ui/tooltip/tooltip.md +0 -82
  113. package/docs/src/docs/_core-concept/dataFlow.md +0 -73
  114. package/docs/src/docs/_core-concept/overview.md +0 -57
  115. package/docs/src/docs/_core-concept/subscriber.md +0 -75
  116. package/docs/src/docs/_decorators/ancestor-attribute.md +0 -79
  117. package/docs/src/docs/_decorators/auto-subscribe.md +0 -202
  118. package/docs/src/docs/_decorators/bind.md +0 -167
  119. package/docs/src/docs/_decorators/get.md +0 -68
  120. package/docs/src/docs/_decorators/handle.md +0 -171
  121. package/docs/src/docs/_decorators/on-assign.md +0 -388
  122. package/docs/src/docs/_decorators/publish.md +0 -55
  123. package/docs/src/docs/_decorators/subscribe.md +0 -97
  124. package/docs/src/docs/_decorators/wait-for-ancestors.md +0 -163
  125. package/docs/src/docs/_directives/sub.md +0 -91
  126. package/docs/src/docs/_getting-started/ai-agents.md +0 -56
  127. package/docs/src/docs/_getting-started/concorde-manual-install.md +0 -133
  128. package/docs/src/docs/_getting-started/concorde-outside.md +0 -33
  129. package/docs/src/docs/_getting-started/create-a-component.md +0 -139
  130. package/docs/src/docs/_getting-started/my-first-component.md +0 -236
  131. package/docs/src/docs/_getting-started/my-first-subscriber.md +0 -120
  132. package/docs/src/docs/_getting-started/pubsub.md +0 -37
  133. package/docs/src/docs/_getting-started/start.md +0 -47
  134. package/docs/src/docs/_getting-started/theming.md +0 -91
  135. package/docs/src/docs/_misc/api-configuration.md +0 -79
  136. package/docs/src/docs/_misc/dataProviderKey.md +0 -168
  137. package/docs/src/docs/_misc/docs-mock-api.md +0 -60
  138. package/docs/src/docs/_misc/endpoint.md +0 -43
  139. package/docs/src/docs/_misc/html-integration.md +0 -13
  140. package/docs/src/docs/search/docs-search.json +0 -8532
  141. package/docs/src/tag-list.json +0 -1
  142. package/docs/src/tsconfig-model.json +0 -23
  143. package/docs/src/tsconfig.json +0 -1095
@@ -8,11 +8,12 @@ import HTML, {
8
8
  } from "@supersoniks/concorde/core/utils/HTML";
9
9
  import API, {
10
10
  APIConfiguration,
11
- type ApiGetResult,
11
+ type ApiResult,
12
12
  } from "@supersoniks/concorde/core/utils/api";
13
13
  import DataProvider from "../utils/PublisherProxy";
14
14
  import { ConnectedComponent, setSubscribable } from "./subscriber/common";
15
15
  import {
16
+ type DynamicPathOptions,
16
17
  extractDynamicDependencies,
17
18
  resolveDynamicPath,
18
19
  } from "./subscriber/dynamicPath";
@@ -77,6 +78,22 @@ function isLitHost(component: unknown): component is LitHost {
77
78
  );
78
79
  }
79
80
 
81
+ /**
82
+ * Regroupe plusieurs déclencheurs synchrones (mutations `onInternalMutation`,
83
+ * `rebindAll`, etc.) en un seul appel par frame — même principe que
84
+ * `dynamicPropertyWatch.ts` (`scheduleObservedPropertyChanges`).
85
+ */
86
+ function createAnimationFrameCoalescedRunner(run: () => void): () => void {
87
+ let frameId: number | null = null;
88
+ return () => {
89
+ if (frameId !== null) return;
90
+ frameId = requestAnimationFrame(() => {
91
+ frameId = null;
92
+ run();
93
+ });
94
+ };
95
+ }
96
+
80
97
  function scheduleAfterHostReady(
81
98
  component: unknown,
82
99
  callback: () => void,
@@ -163,7 +180,7 @@ function detachConfigPublisher(state: ApiGetState): void {
163
180
 
164
181
  /**
165
182
  * Décorateur **`@get`** : charge des données via `API.getDetailed` et assigne un
166
- * `ApiGetResult<T>` (`request`, `response`, `result` typé `T`) ou `null`.
183
+ * `ApiResult<T>` (`request`, `response`, `result` typé `T`) ou `null`.
167
184
  * Le path est un `Endpoint<T, Ue>` ; les placeholders `${nomPropriété}` sont résolus sur l'instance (`Ue` contraint l’hôte).
168
185
  *
169
186
  * **Scoped (défaut)** : `HTML.getApiConfiguration(host)` avec `host` = l’élément connecté
@@ -176,19 +193,19 @@ function detachConfigPublisher(state: ApiGetState): void {
176
193
  *
177
194
  * @example
178
195
  * @get(new Endpoint<User, { userId: string }>("users/${userId}"))
179
- * payload?: ApiGetResult<User>;
196
+ * payload?: ApiResult<User>;
180
197
  *
181
198
  * @example
182
199
  * const apiConf = new DataProviderKey<APIConfiguration>("myApiConf");
183
200
  * PublisherManager.get("myApiConf").set({ serviceURL: "...", token: null, ... });
184
201
  * @get(new Endpoint<Thing, { id: string }>("things/${id}"), apiConf)
185
- * payload?: ApiGetResult<Thing>;
202
+ * payload?: ApiResult<Thing>;
186
203
  */
187
204
  export function get<T, Ue = any>(
188
205
  endpoint: Endpoint<T, Ue>,
189
206
  ): <K extends string>(
190
207
  target: DataProviderKeyHost<Ue> & {
191
- [P in K]?: ApiGetResult<T> | null | undefined;
208
+ [P in K]?: ApiResult<T> | null | undefined;
192
209
  },
193
210
  propertyKey: K,
194
211
  ) => void;
@@ -198,18 +215,50 @@ export function get<T, Ue = any, Uk = any>(
198
215
  ): <K extends string>(
199
216
  target: DataProviderKeyHost<Ue> &
200
217
  DataProviderKeyHost<Uk> & {
201
- [P in K]?: ApiGetResult<T> | null | undefined;
218
+ [P in K]?: ApiResult<T> | null | undefined;
202
219
  },
203
220
  propertyKey: K,
204
221
  ) => void;
205
222
  export function get<T, Ue = any, Uk = any>(
206
223
  endpoint: Endpoint<T, Ue>,
207
- configurationKey?: DataProviderKey<APIConfiguration, Uk>,
224
+ options: GetOptions,
208
225
  ): <K extends string>(
209
- target: object & { [P in K]?: ApiGetResult<T> | null | undefined },
226
+ target: DataProviderKeyHost<Ue> & {
227
+ [P in K]?: ApiResult<T> | null | undefined;
228
+ },
229
+ propertyKey: K,
230
+ ) => void;
231
+ export function get<T, Ue = any, Uk = any, Ut = any>(
232
+ endpoint: Endpoint<T, Ue>,
233
+ configurationKey: DataProviderKey<APIConfiguration, Uk>,
234
+ options: GetOptions,
235
+ ): <K extends string>(
236
+ target: DataProviderKeyHost<Ue> &
237
+ DataProviderKeyHost<Uk> & {
238
+ [P in K]?: ApiResult<T> | null | undefined;
239
+ },
240
+ propertyKey: K,
241
+ ) => void;
242
+ export function get<T, Ue = any, Uk = any, _Ut = any>(
243
+ endpoint: Endpoint<T, Ue>,
244
+ configurationKeyOrOptions?:
245
+ | DataProviderKey<APIConfiguration, Uk>
246
+ | GetOptions,
247
+ maybeOptions?: GetOptions,
248
+ ): <K extends string>(
249
+ target: object & { [P in K]?: ApiResult<T> | null | undefined },
210
250
  propertyKey: K,
211
251
  ) => void {
212
252
  const pathTemplate = endpoint.path;
253
+ const configurationKey = isGetOptions(configurationKeyOrOptions)
254
+ ? undefined
255
+ : configurationKeyOrOptions;
256
+ const options = isGetOptions(configurationKeyOrOptions)
257
+ ? configurationKeyOrOptions
258
+ : maybeOptions;
259
+ const pathOptions: DynamicPathOptions = {
260
+ skipEmptyPlaceholder: options?.skipEmptyPlaceholder,
261
+ };
213
262
  const configurationKeyPath = configurationKey?.path;
214
263
  const endpointDynamicDependencies = extractDynamicDependencies(pathTemplate);
215
264
  const configKeyDynamicDependencies = configurationKeyPath
@@ -249,7 +298,7 @@ export function get<T, Ue = any, Uk = any>(
249
298
 
250
299
  const runFetch = () => {
251
300
  const resolution = isDynamicPath
252
- ? resolveDynamicPath(component, pathTemplate)
301
+ ? resolveDynamicPath(component, pathTemplate, pathOptions)
253
302
  : { ready: true, path: pathTemplate };
254
303
  if (!resolution.ready || !resolution.path) {
255
304
  comp[propertyKey] = undefined;
@@ -257,7 +306,11 @@ export function get<T, Ue = any, Uk = any>(
257
306
  }
258
307
  let config: APIConfiguration | null = null;
259
308
  if (usesPublisherConfig && configurationKeyPath) {
260
- const configRes = resolveDynamicPath(component, configurationKeyPath);
309
+ const configRes = resolveDynamicPath(
310
+ component,
311
+ configurationKeyPath,
312
+ pathOptions,
313
+ );
261
314
  if (!configRes.ready || !configRes.path) {
262
315
  comp[propertyKey] = undefined;
263
316
  return;
@@ -282,7 +335,7 @@ export function get<T, Ue = any, Uk = any>(
282
335
  const api = new API(config);
283
336
  void api
284
337
  .getDetailed<T>(resolution.path)
285
- .then((payload?: ApiGetResult<T>) => {
338
+ .then((payload?: ApiResult<T>) => {
286
339
  if (generation !== state.requestGeneration) return;
287
340
  comp[propertyKey] = payload;
288
341
  });
@@ -291,7 +344,11 @@ export function get<T, Ue = any, Uk = any>(
291
344
  const rebindPublisherConfig = () => {
292
345
  if (!usesPublisherConfig || !configurationKeyPath) return;
293
346
  detachConfigPublisher(state);
294
- const configRes = resolveDynamicPath(component, configurationKeyPath);
347
+ const configRes = resolveDynamicPath(
348
+ component,
349
+ configurationKeyPath,
350
+ pathOptions,
351
+ );
295
352
  if (!configRes.ready || !configRes.path) {
296
353
  comp[propertyKey] = undefined;
297
354
  return;
@@ -355,3 +412,577 @@ export function get<T, Ue = any, Uk = any>(
355
412
  });
356
413
  };
357
414
  }
415
+
416
+ export type GetOptions = DynamicPathOptions;
417
+
418
+ export type ApiSendOptions<Uk = any> = DynamicPathOptions & {
419
+ /** Intervalle de renvoi automatique (ms). 0 ou absent = pas de polling. */
420
+ refetchEveryMs?: number;
421
+ /** Ne pas envoyer si le body publisher est null/undefined (défaut: true). */
422
+ skipIfBodyMissing?: boolean;
423
+ /**
424
+ * Relancer l'envoi quand le body publisher mute (défaut: true).
425
+ * `false` : le body est lu au moment de l'envoi ; déclencher via `triggerKey.invalidate()`.
426
+ */
427
+ autoPostOnBodyMutation?: boolean;
428
+ /** Publisher dont `invalidate()` relance l'envoi (relit le body courant). */
429
+ triggerKey?: DataProviderKey<unknown, Uk>;
430
+ };
431
+
432
+ /** @deprecated Alias — préférer `ApiSendOptions`. */
433
+ export type PostOptions<Uk = any> = ApiSendOptions<Uk>;
434
+ export type PutOptions<Uk = any> = ApiSendOptions<Uk>;
435
+ export type PatchOptions<Uk = any> = ApiSendOptions<Uk>;
436
+
437
+ function isGetOptions(value: unknown): value is GetOptions {
438
+ if (!value || typeof value !== "object") return false;
439
+ return "skipEmptyPlaceholder" in value;
440
+ }
441
+
442
+ function isApiSendOptions(value: unknown): value is ApiSendOptions {
443
+ if (!value || typeof value !== "object") return false;
444
+ return (
445
+ "refetchEveryMs" in value ||
446
+ "skipIfBodyMissing" in value ||
447
+ "autoPostOnBodyMutation" in value ||
448
+ "skipEmptyPlaceholder" in value ||
449
+ "triggerKey" in value
450
+ );
451
+ }
452
+
453
+ type ApiSendState = ApiGetState & {
454
+ bodyPublisher: DataProvider | null;
455
+ bodyMutationHandler: (() => void) | null;
456
+ triggerPublisher: DataProvider | null;
457
+ triggerInvalidateHandler: (() => void) | null;
458
+ refetchTimeoutId: ReturnType<typeof setTimeout> | null;
459
+ refetchEveryMs: number;
460
+ skipIfBodyMissing: boolean;
461
+ autoPostOnBodyMutation: boolean;
462
+ };
463
+
464
+ function detachBodyPublisher(state: ApiSendState): void {
465
+ if (state.bodyPublisher && state.bodyMutationHandler) {
466
+ state.bodyPublisher.offInternalMutation(state.bodyMutationHandler);
467
+ }
468
+ state.bodyPublisher = null;
469
+ state.bodyMutationHandler = null;
470
+ }
471
+
472
+ function detachTriggerPublisher(state: ApiSendState): void {
473
+ if (state.triggerPublisher && state.triggerInvalidateHandler) {
474
+ state.triggerPublisher.offInvalidate(state.triggerInvalidateHandler);
475
+ }
476
+ state.triggerPublisher = null;
477
+ state.triggerInvalidateHandler = null;
478
+ }
479
+
480
+ function clearSendRefetch(state: ApiSendState): void {
481
+ if (state.refetchTimeoutId) {
482
+ clearTimeout(state.refetchTimeoutId);
483
+ state.refetchTimeoutId = null;
484
+ }
485
+ }
486
+
487
+ function readBodyFromPublisher(publisher: DataProvider | null): unknown {
488
+ if (!publisher || typeof publisher.get !== "function") return undefined;
489
+ return publisher.get();
490
+ }
491
+
492
+ type ApiSendMethod = "POST" | "PUT" | "PATCH";
493
+
494
+ type ApiSendDetailedCaller = (
495
+ api: API,
496
+ path: string,
497
+ body: unknown,
498
+ ) => Promise<ApiResult<unknown> | undefined>;
499
+
500
+ function createApiSendDecorator<T, B, Ue = any, Uk = any, Ut = any>(
501
+ method: ApiSendMethod,
502
+ callDetailed: ApiSendDetailedCaller,
503
+ ) {
504
+ return function (
505
+ endpoint: Endpoint<T, Ue>,
506
+ bodyKey: DataProviderKey<B>,
507
+ configurationKeyOrOptions?:
508
+ | DataProviderKey<APIConfiguration, Uk>
509
+ | ApiSendOptions<Ut>,
510
+ maybeOptions?: ApiSendOptions<Ut>,
511
+ ): <K extends string>(
512
+ target: object & { [P in K]?: ApiResult<T> | null | undefined },
513
+ propertyKey: K,
514
+ ) => void {
515
+ const pathTemplate = endpoint.path;
516
+ const bodyKeyPath = bodyKey.path;
517
+ const configurationKey = isApiSendOptions(configurationKeyOrOptions)
518
+ ? undefined
519
+ : configurationKeyOrOptions;
520
+ const options = isApiSendOptions(configurationKeyOrOptions)
521
+ ? configurationKeyOrOptions
522
+ : maybeOptions;
523
+ const configurationKeyPath = configurationKey?.path;
524
+ const triggerKeyPath = options?.triggerKey?.path;
525
+ const endpointDynamicDependencies = extractDynamicDependencies(pathTemplate);
526
+ const bodyKeyDynamicDependencies = extractDynamicDependencies(bodyKeyPath);
527
+ const configKeyDynamicDependencies = configurationKeyPath
528
+ ? extractDynamicDependencies(configurationKeyPath)
529
+ : [];
530
+ const triggerKeyDynamicDependencies = triggerKeyPath
531
+ ? extractDynamicDependencies(triggerKeyPath)
532
+ : [];
533
+ const mergedDynamicDependencies = [
534
+ ...new Set([
535
+ ...endpointDynamicDependencies,
536
+ ...bodyKeyDynamicDependencies,
537
+ ...configKeyDynamicDependencies,
538
+ ...triggerKeyDynamicDependencies,
539
+ ]),
540
+ ];
541
+ const isDynamicPath = endpointDynamicDependencies.length > 0;
542
+ const usesPublisherConfig = Boolean(configurationKeyPath);
543
+ const refetchEveryMs = options?.refetchEveryMs ?? 0;
544
+ const skipIfBodyMissing = options?.skipIfBodyMissing ?? true;
545
+ const autoPostOnBodyMutation = options?.autoPostOnBodyMutation ?? true;
546
+ const pathOptions: DynamicPathOptions = {
547
+ skipEmptyPlaceholder: options?.skipEmptyPlaceholder,
548
+ };
549
+ const stateKeyPrefix = `__${method.toLowerCase()}_state_`;
550
+
551
+ return function (target: object, propertyKey: string) {
552
+ if (!target) return;
553
+ setSubscribable(target);
554
+ const stateKey = `${stateKeyPrefix}${propertyKey}`;
555
+
556
+ (target as ConnectedComponent).__onConnected__((component) => {
557
+ const comp = component as Record<string, unknown>;
558
+ let state = comp[stateKey] as ApiSendState | undefined;
559
+ if (!state) {
560
+ state = {
561
+ cleanupWatchers: [],
562
+ requestGeneration: 0,
563
+ configPublisher: null,
564
+ configMutationHandler: null,
565
+ scopeWatchCleanup: null,
566
+ bodyPublisher: null,
567
+ bodyMutationHandler: null,
568
+ triggerPublisher: null,
569
+ triggerInvalidateHandler: null,
570
+ refetchTimeoutId: null,
571
+ refetchEveryMs,
572
+ skipIfBodyMissing,
573
+ autoPostOnBodyMutation,
574
+ };
575
+ comp[stateKey] = state;
576
+ }
577
+
578
+ state.cleanupWatchers.forEach((cleanup) => cleanup());
579
+ state.cleanupWatchers = [];
580
+ state.requestGeneration++;
581
+ state.refetchEveryMs = refetchEveryMs;
582
+ state.skipIfBodyMissing = skipIfBodyMissing;
583
+ state.autoPostOnBodyMutation = autoPostOnBodyMutation;
584
+ clearSendRefetch(state);
585
+
586
+ const scheduleRefetch = () => {
587
+ clearSendRefetch(state);
588
+ if (!state.refetchEveryMs || state.refetchEveryMs <= 0) return;
589
+ state.refetchTimeoutId = setTimeout(() => {
590
+ state.refetchTimeoutId = null;
591
+ runSend();
592
+ }, state.refetchEveryMs);
593
+ };
594
+
595
+ const runSend = () => {
596
+ const resolution = isDynamicPath
597
+ ? resolveDynamicPath(component, pathTemplate, pathOptions)
598
+ : { ready: true, path: pathTemplate };
599
+ if (!resolution.ready || !resolution.path) {
600
+ comp[propertyKey] = undefined;
601
+ scheduleRefetch();
602
+ return;
603
+ }
604
+
605
+ const bodyResolution = resolveDynamicPath(
606
+ component,
607
+ bodyKeyPath,
608
+ pathOptions,
609
+ );
610
+ if (!bodyResolution.ready || !bodyResolution.path) {
611
+ comp[propertyKey] = undefined;
612
+ scheduleRefetch();
613
+ return;
614
+ }
615
+
616
+ const bodyPublisher = getPublisherFromPath(bodyResolution.path);
617
+ const body = readBodyFromPublisher(bodyPublisher);
618
+ if (
619
+ state.skipIfBodyMissing &&
620
+ (body === null || body === undefined)
621
+ ) {
622
+ scheduleRefetch();
623
+ return;
624
+ }
625
+
626
+ let config: APIConfiguration | null = null;
627
+ if (usesPublisherConfig && configurationKeyPath) {
628
+ const configRes = resolveDynamicPath(
629
+ component,
630
+ configurationKeyPath,
631
+ pathOptions,
632
+ );
633
+ if (!configRes.ready || !configRes.path) {
634
+ comp[propertyKey] = undefined;
635
+ scheduleRefetch();
636
+ return;
637
+ }
638
+ const configPublisher = getPublisherFromPath(configRes.path);
639
+ config = readApiConfigurationFromPublisher(configPublisher);
640
+ } else {
641
+ config = resolveScopedConfiguration(component);
642
+ }
643
+
644
+ if (!isScopedConfigurationReady(config)) {
645
+ if (!usesPublisherConfig && !state.scopeWatchCleanup) {
646
+ const scopeWatch = watchScopedConfiguration(component, runSend);
647
+ state.scopeWatchCleanup = scopeWatch;
648
+ state.cleanupWatchers.push(() => {
649
+ scopeWatch();
650
+ state.scopeWatchCleanup = null;
651
+ });
652
+ }
653
+ scheduleRefetch();
654
+ return;
655
+ }
656
+
657
+ const generation = ++state.requestGeneration;
658
+ const api = new API(config);
659
+ void callDetailed(api, resolution.path, body).then(
660
+ (payload?: ApiResult<unknown>) => {
661
+ if (generation !== state.requestGeneration) return;
662
+ comp[propertyKey] = payload;
663
+ scheduleRefetch();
664
+ },
665
+ );
666
+ };
667
+
668
+ const scheduleSend = createAnimationFrameCoalescedRunner(runSend);
669
+
670
+ const rebindBodyPublisher = () => {
671
+ detachBodyPublisher(state);
672
+ if (!state.autoPostOnBodyMutation) {
673
+ return;
674
+ }
675
+ const bodyResolution = resolveDynamicPath(
676
+ component,
677
+ bodyKeyPath,
678
+ pathOptions,
679
+ );
680
+ if (!bodyResolution.ready || !bodyResolution.path) {
681
+ return;
682
+ }
683
+ const publisher = getPublisherFromPath(bodyResolution.path);
684
+ if (!publisher) return;
685
+ const mutationHandler = () => scheduleSend();
686
+ publisher.onInternalMutation(mutationHandler);
687
+ state.bodyPublisher = publisher;
688
+ state.bodyMutationHandler = mutationHandler;
689
+ };
690
+
691
+ const rebindTriggerPublisher = () => {
692
+ detachTriggerPublisher(state);
693
+ if (!triggerKeyPath) return;
694
+ const triggerResolution = resolveDynamicPath(
695
+ component,
696
+ triggerKeyPath,
697
+ pathOptions,
698
+ );
699
+ if (!triggerResolution.ready || !triggerResolution.path) {
700
+ return;
701
+ }
702
+ const publisher = getPublisherFromPath(triggerResolution.path);
703
+ if (!publisher) return;
704
+ const invalidateHandler = () => scheduleSend();
705
+ publisher.onInvalidate(invalidateHandler);
706
+ state.triggerPublisher = publisher;
707
+ state.triggerInvalidateHandler = invalidateHandler;
708
+ };
709
+
710
+ const rebindPublisherConfig = () => {
711
+ if (!usesPublisherConfig || !configurationKeyPath) return;
712
+ detachConfigPublisher(state);
713
+ const configRes = resolveDynamicPath(
714
+ component,
715
+ configurationKeyPath,
716
+ pathOptions,
717
+ );
718
+ if (!configRes.ready || !configRes.path) {
719
+ comp[propertyKey] = undefined;
720
+ return;
721
+ }
722
+ const publisher = getPublisherFromPath(configRes.path);
723
+ if (!publisher) {
724
+ comp[propertyKey] = undefined;
725
+ return;
726
+ }
727
+ const mutationHandler = () => scheduleSend();
728
+ publisher.onInternalMutation(mutationHandler);
729
+ state.configPublisher = publisher;
730
+ state.configMutationHandler = mutationHandler;
731
+ };
732
+
733
+ const rebindAll = () => {
734
+ rebindBodyPublisher();
735
+ rebindTriggerPublisher();
736
+ rebindPublisherConfig();
737
+ if (state.autoPostOnBodyMutation) {
738
+ scheduleSend();
739
+ }
740
+ };
741
+
742
+ if (
743
+ usesPublisherConfig ||
744
+ bodyKeyDynamicDependencies.length ||
745
+ triggerKeyDynamicDependencies.length
746
+ ) {
747
+ for (const dependency of mergedDynamicDependencies) {
748
+ const unsubscribe = observeDynamicProperty(
749
+ getDynamicWatchKeys.watcherStore,
750
+ getDynamicWatchKeys.hooked,
751
+ component,
752
+ dependency,
753
+ () => rebindAll(),
754
+ );
755
+ state.cleanupWatchers.push(unsubscribe);
756
+ }
757
+ rebindAll();
758
+ } else {
759
+ const startScopedSend = () => {
760
+ if (isDynamicPath) {
761
+ for (const dependency of endpointDynamicDependencies) {
762
+ const unsubscribe = observeDynamicProperty(
763
+ getDynamicWatchKeys.watcherStore,
764
+ getDynamicWatchKeys.hooked,
765
+ component,
766
+ dependency,
767
+ () => scheduleSend(),
768
+ );
769
+ state.cleanupWatchers.push(unsubscribe);
770
+ }
771
+ }
772
+ rebindBodyPublisher();
773
+ rebindTriggerPublisher();
774
+ if (state.autoPostOnBodyMutation) {
775
+ scheduleSend();
776
+ }
777
+ };
778
+ state.cleanupWatchers.push(
779
+ scheduleAfterHostReady(component, startScopedSend),
780
+ );
781
+ }
782
+ });
783
+
784
+ (target as ConnectedComponent).__onDisconnected__((component) => {
785
+ const comp = component as Record<string, unknown>;
786
+ const state = comp[stateKey] as ApiSendState | undefined;
787
+ if (!state) return;
788
+ detachConfigPublisher(state);
789
+ detachBodyPublisher(state);
790
+ detachTriggerPublisher(state);
791
+ clearSendRefetch(state);
792
+ state.cleanupWatchers.forEach((cleanup) => cleanup());
793
+ state.cleanupWatchers = [];
794
+ state.requestGeneration++;
795
+ comp[propertyKey] = undefined;
796
+ });
797
+ };
798
+ };
799
+ }
800
+
801
+ /**
802
+ * Décorateur **`@post`** : envoie des données via `API.postDetailed` et assigne un
803
+ * `ApiResult<T>` (`request`, `response`, `result` typé `T`).
804
+ *
805
+ * Le body est lu depuis un `DataProviderKey` (mutations et assignations relancent le POST).
806
+ *
807
+ * @example
808
+ * @post(new Endpoint<SyncResult, { sessionId: string }>("sessions/${sessionId}/sync"), syncRequestKey)
809
+ * payload?: ApiResult<SyncResult>;
810
+ */
811
+ export function post<T, B, Ue = any>(
812
+ endpoint: Endpoint<T, Ue>,
813
+ bodyKey: DataProviderKey<B>,
814
+ ): <K extends string>(
815
+ target: DataProviderKeyHost<Ue> & {
816
+ [P in K]?: ApiResult<T> | null | undefined;
817
+ },
818
+ propertyKey: K,
819
+ ) => void;
820
+ export function post<T, B, Ue = any, Uk = any>(
821
+ endpoint: Endpoint<T, Ue>,
822
+ bodyKey: DataProviderKey<B>,
823
+ configurationKey: DataProviderKey<APIConfiguration, Uk>,
824
+ ): <K extends string>(
825
+ target: DataProviderKeyHost<Ue> &
826
+ DataProviderKeyHost<Uk> & {
827
+ [P in K]?: ApiResult<T> | null | undefined;
828
+ },
829
+ propertyKey: K,
830
+ ) => void;
831
+ export function post<T, B, Ue = any, Uk = any>(
832
+ endpoint: Endpoint<T, Ue>,
833
+ bodyKey: DataProviderKey<B>,
834
+ options: PostOptions<Uk>,
835
+ ): <K extends string>(
836
+ target: DataProviderKeyHost<Ue> & {
837
+ [P in K]?: ApiResult<T> | null | undefined;
838
+ },
839
+ propertyKey: K,
840
+ ) => void;
841
+ export function post<T, B, Ue = any, Uk = any, Ut = any>(
842
+ endpoint: Endpoint<T, Ue>,
843
+ bodyKey: DataProviderKey<B>,
844
+ configurationKey: DataProviderKey<APIConfiguration, Uk>,
845
+ options: PostOptions<Ut>,
846
+ ): <K extends string>(
847
+ target: DataProviderKeyHost<Ue> &
848
+ DataProviderKeyHost<Uk> & {
849
+ [P in K]?: ApiResult<T> | null | undefined;
850
+ },
851
+ propertyKey: K,
852
+ ) => void;
853
+ export function post<T, B, Ue = any, Uk = any, Ut = any>(
854
+ endpoint: Endpoint<T, Ue>,
855
+ bodyKey: DataProviderKey<B>,
856
+ configurationKeyOrOptions?:
857
+ | DataProviderKey<APIConfiguration, Uk>
858
+ | PostOptions<Ut>,
859
+ maybeOptions?: PostOptions<Ut>,
860
+ ): <K extends string>(
861
+ target: object & { [P in K]?: ApiResult<T> | null | undefined },
862
+ propertyKey: K,
863
+ ) => void {
864
+ return createApiSendDecorator<T, B, Ue, Uk, Ut>(
865
+ "POST",
866
+ (api, path, body) => api.postDetailed<T, B>(path, body as B),
867
+ )(endpoint, bodyKey, configurationKeyOrOptions, maybeOptions);
868
+ }
869
+
870
+ /** `@put` — même modèle que `@post` via `API.putDetailed`. */
871
+ export function put<T, B, Ue = any>(
872
+ endpoint: Endpoint<T, Ue>,
873
+ bodyKey: DataProviderKey<B>,
874
+ ): <K extends string>(
875
+ target: DataProviderKeyHost<Ue> & {
876
+ [P in K]?: ApiResult<T> | null | undefined;
877
+ },
878
+ propertyKey: K,
879
+ ) => void;
880
+ export function put<T, B, Ue = any, Uk = any>(
881
+ endpoint: Endpoint<T, Ue>,
882
+ bodyKey: DataProviderKey<B>,
883
+ configurationKey: DataProviderKey<APIConfiguration, Uk>,
884
+ ): <K extends string>(
885
+ target: DataProviderKeyHost<Ue> &
886
+ DataProviderKeyHost<Uk> & {
887
+ [P in K]?: ApiResult<T> | null | undefined;
888
+ },
889
+ propertyKey: K,
890
+ ) => void;
891
+ export function put<T, B, Ue = any, Uk = any>(
892
+ endpoint: Endpoint<T, Ue>,
893
+ bodyKey: DataProviderKey<B>,
894
+ options: PutOptions<Uk>,
895
+ ): <K extends string>(
896
+ target: DataProviderKeyHost<Ue> & {
897
+ [P in K]?: ApiResult<T> | null | undefined;
898
+ },
899
+ propertyKey: K,
900
+ ) => void;
901
+ export function put<T, B, Ue = any, Uk = any, Ut = any>(
902
+ endpoint: Endpoint<T, Ue>,
903
+ bodyKey: DataProviderKey<B>,
904
+ configurationKey: DataProviderKey<APIConfiguration, Uk>,
905
+ options: PutOptions<Ut>,
906
+ ): <K extends string>(
907
+ target: DataProviderKeyHost<Ue> &
908
+ DataProviderKeyHost<Uk> & {
909
+ [P in K]?: ApiResult<T> | null | undefined;
910
+ },
911
+ propertyKey: K,
912
+ ) => void;
913
+ export function put<T, B, Ue = any, Uk = any, Ut = any>(
914
+ endpoint: Endpoint<T, Ue>,
915
+ bodyKey: DataProviderKey<B>,
916
+ configurationKeyOrOptions?:
917
+ | DataProviderKey<APIConfiguration, Uk>
918
+ | PutOptions<Ut>,
919
+ maybeOptions?: PutOptions<Ut>,
920
+ ): <K extends string>(
921
+ target: object & { [P in K]?: ApiResult<T> | null | undefined },
922
+ propertyKey: K,
923
+ ) => void {
924
+ return createApiSendDecorator<T, B, Ue, Uk, Ut>(
925
+ "PUT",
926
+ (api, path, body) => api.putDetailed<T, B>(path, body as B),
927
+ )(endpoint, bodyKey, configurationKeyOrOptions, maybeOptions);
928
+ }
929
+
930
+ /** `@patch` — même modèle que `@post` via `API.patchDetailed`. */
931
+ export function patch<T, B, Ue = any>(
932
+ endpoint: Endpoint<T, Ue>,
933
+ bodyKey: DataProviderKey<B>,
934
+ ): <K extends string>(
935
+ target: DataProviderKeyHost<Ue> & {
936
+ [P in K]?: ApiResult<T> | null | undefined;
937
+ },
938
+ propertyKey: K,
939
+ ) => void;
940
+ export function patch<T, B, Ue = any, Uk = any>(
941
+ endpoint: Endpoint<T, Ue>,
942
+ bodyKey: DataProviderKey<B>,
943
+ configurationKey: DataProviderKey<APIConfiguration, Uk>,
944
+ ): <K extends string>(
945
+ target: DataProviderKeyHost<Ue> &
946
+ DataProviderKeyHost<Uk> & {
947
+ [P in K]?: ApiResult<T> | null | undefined;
948
+ },
949
+ propertyKey: K,
950
+ ) => void;
951
+ export function patch<T, B, Ue = any, Uk = any>(
952
+ endpoint: Endpoint<T, Ue>,
953
+ bodyKey: DataProviderKey<B>,
954
+ options: PatchOptions<Uk>,
955
+ ): <K extends string>(
956
+ target: DataProviderKeyHost<Ue> & {
957
+ [P in K]?: ApiResult<T> | null | undefined;
958
+ },
959
+ propertyKey: K,
960
+ ) => void;
961
+ export function patch<T, B, Ue = any, Uk = any, Ut = any>(
962
+ endpoint: Endpoint<T, Ue>,
963
+ bodyKey: DataProviderKey<B>,
964
+ configurationKey: DataProviderKey<APIConfiguration, Uk>,
965
+ options: PatchOptions<Ut>,
966
+ ): <K extends string>(
967
+ target: DataProviderKeyHost<Ue> &
968
+ DataProviderKeyHost<Uk> & {
969
+ [P in K]?: ApiResult<T> | null | undefined;
970
+ },
971
+ propertyKey: K,
972
+ ) => void;
973
+ export function patch<T, B, Ue = any, Uk = any, Ut = any>(
974
+ endpoint: Endpoint<T, Ue>,
975
+ bodyKey: DataProviderKey<B>,
976
+ configurationKeyOrOptions?:
977
+ | DataProviderKey<APIConfiguration, Uk>
978
+ | PatchOptions<Ut>,
979
+ maybeOptions?: PatchOptions<Ut>,
980
+ ): <K extends string>(
981
+ target: object & { [P in K]?: ApiResult<T> | null | undefined },
982
+ propertyKey: K,
983
+ ) => void {
984
+ return createApiSendDecorator<T, B, Ue, Uk, Ut>(
985
+ "PATCH",
986
+ (api, path, body) => api.patchDetailed<T, B>(path, body as B),
987
+ )(endpoint, bodyKey, configurationKeyOrOptions, maybeOptions);
988
+ }