@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
@@ -1,14 +1,18 @@
1
1
  # @get
2
2
 
3
- Loads data through [`API.getDetailed`](../../core/utils/api.ts). The decorated property is **`ApiGetResult<T> | null`**: `request`, `response` (or `null` for `dataProvider(...)` resolution without HTTP), and typed `result`.
3
+ Loads data through [`API.getDetailed`](../../core/utils/api.ts). The decorated property is **`ApiResult<T> | null`**: `request`, `response` (or `null` for `dataProvider(...)` resolution without HTTP), and typed `result`.
4
4
 
5
- Pass an [`Endpoint<T>`](#docs/_misc/endpoint.md/endpoint) as the first argument. Import `get` and `ApiGetResult` from `@supersoniks/concorde/decorators`, and `Endpoint` from `@supersoniks/concorde/utils/endpoint`.
5
+ Pass an [`Endpoint<T>`](#docs/_misc/endpoint.md/endpoint) as the first argument. Import `get` and `ApiResult` from `@supersoniks/concorde/decorators`, and `Endpoint` from `@supersoniks/concorde/utils/endpoint`.
6
6
 
7
7
  ## Configuration
8
8
 
9
9
  - **Default:** `HTML.getApiConfiguration(host)` (ancestor `serviceURL`, etc.).
10
10
  - **Second argument:** `DataProviderKey<APIConfiguration>` — config is read from the publisher at the resolved path; internal mutations trigger another GET. See [API configuration](#docs/_misc/api-configuration.md/api-configuration) for mock demos.
11
11
 
12
+ ## Dynamic path
13
+
14
+ `${prop}` on the endpoint or config key is resolved on the host. While a placeholder is `null` or `undefined`, no GET runs. Optional `skipEmptyPlaceholder: true` also blocks `""` (empty string only). Details: [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
15
+
12
16
  ## When the GET runs again
13
17
 
14
18
  - A referenced Lit property changes (endpoint path and/or config key contains `${...}`).
@@ -18,7 +22,7 @@ Pass an [`Endpoint<T>`](#docs/_misc/endpoint.md/endpoint) as the first argument.
18
22
 
19
23
  <sonic-code language="typescript">
20
24
  <template>
21
- import { get, type ApiGetResult } from "@supersoniks/concorde/decorators";
25
+ import { get, type ApiResult } from "@supersoniks/concorde/decorators";
22
26
  import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
23
27
  import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
24
28
  </template>
@@ -32,7 +36,7 @@ Same demo service as [`sonic-queue`](../../core/components/functional/queue/queu
32
36
  <template>
33
37
  @get(new Endpoint<User>("users/${userId}"))
34
38
  @state()
35
- payload: ApiGetResult<User> | null = null;
39
+ payload: ApiResult<User> | null = null;
36
40
  </template>
37
41
  </sonic-code>
38
42
 
@@ -66,3 +70,8 @@ Scoped `@get` with `@publish` / `@subscribe` on the payload (see [@publish](#doc
66
70
  </sonic-code>
67
71
 
68
72
  Stale responses are ignored if the path or generation changed before the request finished.
73
+
74
+ ## See also
75
+
76
+ - [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path)
77
+ - [@post](#docs/_decorators/post.md/post) · [@put](#docs/_decorators/put.md/put) · [@patch](#docs/_decorators/patch.md/patch) — send decorators (same path rules)
@@ -161,6 +161,10 @@ onProfile(profile: Profile) {
161
161
 
162
162
  > Options can be combined, e.g. `@handle(a, b, { waitForAllDefined: true, skip: [Skip.Nullish] })`. For any **arbitrary** validation on a specific value, just guard inside the method (`if (!isValid(v)) return;`) — that is exactly what an `accept`-style predicate would do, since `@handle` only runs your method.
163
163
 
164
+ ### `skipEmptyPlaceholder`
165
+
166
+ On a **dynamic key path** (`"items.${itemId}"`), if `true`, a placeholder resolved to `''` is treated as not ready (no subscription) — **empty string only**, not `0` or `false`. See [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
167
+
164
168
  ## Highlights
165
169
 
166
170
  - Strict typing: the method receives one argument per key, in order.
@@ -168,4 +172,4 @@ onProfile(profile: Profile) {
168
172
  - By default the method runs on **every** assignment, even with `null` / `undefined` (unlike `@onAssign`, which waits for all values). Opt back into that behavior with `waitForAllDefined`.
169
173
  - `skip` filters out values by **named category** (e.g. `[Skip.Nullish, Skip.EmptyObject]`); for arbitrary checks on a specific value, guard inside the method.
170
174
 
171
- See also [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey).
175
+ See also [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey) and [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
@@ -128,6 +128,8 @@ Each placeholder is replaced at runtime with the current value of the correspond
128
128
  - removes the previous subscriptions before attaching the new ones,
129
129
  - observe the changes inside `willUpdate(changedProperties)` so nothing touches the getters/setters.
130
130
 
131
+ While a placeholder is `null`/`undefined`, subscriptions are detached. Optional `skipEmptyPlaceholder` on [@handle](#docs/_decorators/handle.md/handle) (typed replacement). Details: [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
132
+
131
133
 
132
134
  <sonic-code language="typescript">
133
135
  <template>
@@ -0,0 +1,45 @@
1
+ # @patch
2
+
3
+ Sends data through [`API.patchDetailed`](../../core/utils/api.ts). Same model as [@post](#docs/_decorators/post.md/post): decorated property is **`ApiResult<T> | null`** (`request`, `response`, `result`).
4
+
5
+ Pass an [`Endpoint<T>`](#docs/_misc/endpoint.md/endpoint) and a `DataProviderKey` for the **request body**. Import `patch` and `ApiResult` from `@supersoniks/concorde/decorators`.
6
+
7
+ ## Configuration
8
+
9
+ Same as [@post](#docs/_decorators/post.md/post) / [@get](#docs/_decorators/get.md/get): scoped `HTML.getApiConfiguration(host)` or `DataProviderKey<APIConfiguration>` as third argument.
10
+
11
+ ## Options (`PatchOptions`)
12
+
13
+ Same shape as `PostOptions` on [@post](#docs/_decorators/post.md/post) (`ApiSendOptions`): `refetchEveryMs`, `skipIfBodyMissing`, `autoPostOnBodyMutation`, `triggerKey`, `skipEmptyPlaceholder`. See [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path) for `${sessionId}` and empty-string behaviour.
14
+
15
+ ## Import
16
+
17
+ <sonic-code language="typescript">
18
+ <template>
19
+ import { patch, type ApiResult } from "@supersoniks/concorde/decorators";
20
+ import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
21
+ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
22
+ </template>
23
+ </sonic-code>
24
+
25
+ ## Example
26
+
27
+ <sonic-code language="typescript">
28
+ <template>
29
+ const patchBodyKey = new DataProviderKey&lt;Partial&lt;Resource&gt;&gt;("resourcePatch");
30
+
31
+ @patch(
32
+ new Endpoint&lt;Resource, { resourceId: string }&gt;("resources/${resourceId}"),
33
+ patchBodyKey,
34
+ { skipEmptyPlaceholder: true },
35
+ )
36
+ @state()
37
+ payload?: ApiResult&lt;Resource&gt; | null;
38
+ </template>
39
+ </sonic-code>
40
+
41
+ ## See also
42
+
43
+ - [@post](#docs/_decorators/post.md/post) — POST (live demos)
44
+ - [@put](#docs/_decorators/put.md/put) — full replacement (PUT)
45
+ - [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path)
@@ -0,0 +1,93 @@
1
+ # @post
2
+
3
+ Sends data through [`API.postDetailed`](../../core/utils/api.ts). The decorated property is **`ApiResult<T> | null`**: `request`, `response`, and typed `result`.
4
+
5
+ Pass an [`Endpoint<T>`](#docs/_misc/endpoint.md/endpoint) as the first argument and a `DataProviderKey` for the **request body** as the second. Import `post` and `ApiResult` from `@supersoniks/concorde/decorators`, and `Endpoint` from `@supersoniks/concorde/utils/endpoint`.
6
+
7
+ ## Configuration
8
+
9
+ Same as [@get](#docs/_decorators/get.md/get): scoped `HTML.getApiConfiguration(host)` by default, or `DataProviderKey<APIConfiguration>` as third argument. See [API configuration](#docs/_misc/api-configuration.md/api-configuration) for mock demos.
10
+
11
+ ## Optional `PostOptions` (third or fourth argument)
12
+
13
+ | Option | Description |
14
+ |--------|-------------|
15
+ | `refetchEveryMs` | Re-post on an interval (ms). |
16
+ | `skipIfBodyMissing` | Skip when body publisher is `null`/`undefined` (default: `true`). |
17
+ | `autoPostOnBodyMutation` | Re-post when the body publisher mutates (default: `true`). `false` = manual via `triggerKey.invalidate()`. |
18
+ | `skipEmptyPlaceholder` | If `true`, a placeholder resolved to `''` blocks the request (empty string only — not `0` or `false`). See [Dynamic paths](#docs/_misc/dynamic-path.md/dynamic-path). |
19
+ | `triggerKey` | `DataProviderKey` — `invalidate()` re-runs the POST with the current body. |
20
+
21
+ ## When the POST runs again
22
+
23
+ - Body publisher `onInternalMutation` when `autoPostOnBodyMutation` is `true` (default). Plusieurs mutations synchrones dans la même frame sont regroupées en **un seul POST** via `requestAnimationFrame`.
24
+ - Endpoint path dependency changes (`${sessionId}` on the host).
25
+ - Configuration publisher mutation.
26
+ - `triggerKey` publisher `invalidate()`.
27
+ - `refetchEveryMs` timer (if set).
28
+
29
+ ## Import
30
+
31
+ <sonic-code language="typescript">
32
+ <template>
33
+ import { post, publish, type ApiResult } from "@supersoniks/concorde/decorators";
34
+ import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
35
+ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
36
+ </template>
37
+ </sonic-code>
38
+
39
+ ## Minimal example
40
+
41
+ Mock service: `POST /docs-mock-api/api/register` (same route as [`sonic-submit`](#core/components/functional/submit/submit.md/submit) demos). Publisher setup lives in `decorators-demo-post.ts`.
42
+
43
+ <sonic-code language="typescript">
44
+ <template>
45
+ const syncRequest = new DataProviderKey&lt;SyncRequest&gt;("syncRequest");
46
+ const syncTrigger = new DataProviderKey&lt;number&gt;("syncTrigger");
47
+
48
+ @post(
49
+ new Endpoint&lt;SyncResponse, { sessionId: string }&gt;("sessions/${sessionId}/sync"),
50
+ syncRequest,
51
+ { triggerKey: syncTrigger },
52
+ )
53
+ @state()
54
+ payload?: ApiResult&lt;SyncResponse&gt; | null;
55
+ </template>
56
+ </sonic-code>
57
+
58
+ ## Live demos
59
+
60
+ <sonic-code>
61
+ <template>
62
+ <docs-demo-sources for="demo-api-post"></docs-demo-sources>
63
+ <demo-api-post></demo-api-post>
64
+ </template>
65
+ </sonic-code>
66
+
67
+ Dynamic endpoint path — changing `sessionId` on the host re-runs the POST (`POST /docs-mock-api/api/sessions/{id}/sync`):
68
+
69
+ <sonic-code>
70
+ <template>
71
+ <docs-demo-sources for="demo-api-post-dynamic"></docs-demo-sources>
72
+ <demo-api-post-dynamic></demo-api-post-dynamic>
73
+ </template>
74
+ </sonic-code>
75
+
76
+ `@post` + `@publish` on the same property (see [@publish](#docs/_decorators/publish.md/publish)):
77
+
78
+ <sonic-code>
79
+ <template>
80
+ <docs-demo-sources for="demo-api-post-publish"></docs-demo-sources>
81
+ <demo-api-post-publish></demo-api-post-publish>
82
+ </template>
83
+ </sonic-code>
84
+
85
+ Stale responses are ignored if the path or generation changed before the request finished.
86
+
87
+ **Note:** plusieurs composants `@post` sur la même page qui partagent le même `bodyKey` enverront chacun une requête à chaque mutation du body — utiliser une clé par composant (voir les deux démos live ci-dessus).
88
+
89
+ Path placeholders (`${sessionId}`, …): [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
90
+
91
+ ## See also
92
+
93
+ - [@put](#docs/_decorators/put.md/put) · [@patch](#docs/_decorators/patch.md/patch) — same decorator model, `PUT` / `PATCH` via `putDetailed` / `patchDetailed`
@@ -52,4 +52,4 @@ export class DemoPublish extends LitElement {
52
52
  </template>
53
53
  </sonic-code>
54
54
 
55
- Dynamic paths use the same placeholder rules as `@bind` / `@subscribe`.
55
+ Dynamic paths use the same placeholder rules as `@bind` / `@subscribe`. Resolution and `skipEmptyPlaceholder`: [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
@@ -0,0 +1,43 @@
1
+ # @put
2
+
3
+ Sends data through [`API.putDetailed`](../../core/utils/api.ts). Same model as [@post](#docs/_decorators/post.md/post): decorated property is **`ApiResult<T> | null`** (`request`, `response`, `result`).
4
+
5
+ Pass an [`Endpoint<T>`](#docs/_misc/endpoint.md/endpoint) and a `DataProviderKey` for the **request body**. Import `put` and `ApiResult` from `@supersoniks/concorde/decorators`.
6
+
7
+ ## Configuration
8
+
9
+ Same as [@post](#docs/_decorators/post.md/post) / [@get](#docs/_decorators/get.md/get): scoped `HTML.getApiConfiguration(host)` or `DataProviderKey<APIConfiguration>` as third argument.
10
+
11
+ ## Options (`PutOptions`)
12
+
13
+ Same shape as `PostOptions` on [@post](#docs/_decorators/post.md/post) (`ApiSendOptions`): `refetchEveryMs`, `skipIfBodyMissing`, `autoPostOnBodyMutation`, `triggerKey`, `skipEmptyPlaceholder`. See [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path) for `${sessionId}` and empty-string behaviour.
14
+
15
+ ## Import
16
+
17
+ <sonic-code language="typescript">
18
+ <template>
19
+ import { put, type ApiResult } from "@supersoniks/concorde/decorators";
20
+ import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
21
+ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
22
+ </template>
23
+ </sonic-code>
24
+
25
+ ## Example
26
+
27
+ <sonic-code language="typescript">
28
+ <template>
29
+ const bodyKey = new DataProviderKey&lt;UpdatePayload&gt;("resourceBody");
30
+
31
+ @put(new Endpoint&lt;Resource, { resourceId: string }&gt;("resources/${resourceId}"), bodyKey)
32
+ @state()
33
+ payload?: ApiResult&lt;Resource&gt; | null;
34
+ </template>
35
+ </sonic-code>
36
+
37
+ Changing `resourceId` on the host re-runs the PUT when the path becomes ready — see [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
38
+
39
+ ## See also
40
+
41
+ - [@post](#docs/_decorators/post.md/post) — POST (live demos, polling, `@publish`)
42
+ - [@patch](#docs/_decorators/patch.md/patch) — partial update (PATCH)
43
+ - [Endpoint](#docs/_misc/endpoint.md/endpoint) · [API configuration](#docs/_misc/api-configuration.md/api-configuration)
@@ -45,7 +45,9 @@ cart: { items: string[] } | null = null;
45
45
 
46
46
  ## Dynamic path and scope
47
47
 
48
- Placeholders `${prop}` in the key string are resolved from **properties on the same component**. Declare them in the key’s second generic so TypeScript expects them on the host:
48
+ Placeholders `${prop}` in the key string are resolved from **properties on the same component**. While a value is `null`/`undefined`, the subscription is inactive; optional `{ skipEmptyPlaceholder: true }` also waits on `""`. Full rules: [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
49
+
50
+ Declare dynamic props in the key’s second generic so TypeScript expects them on the host:
49
51
 
50
52
  <sonic-code language="typescript">
51
53
  <template>
@@ -94,4 +96,5 @@ user: User | null = null;
94
96
 
95
97
  - [Data flow](#docs/_core-concept/dataFlow.md/dataFlow) — overview
96
98
  - [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey) — paths and host generics
99
+ - [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path) — `${prop}` resolution and `skipEmptyPlaceholder`
97
100
  - [sub()](#docs/_directives/sub.md/sub) — same paths inside `html` templates
@@ -37,7 +37,7 @@ render() {
37
37
 
38
38
  ## Dynamic DataProviderKey (`${prop}`)
39
39
 
40
- Like `@subscribe`: the path is resolved on the template **host component**; the directive re-subscribes when observed props change.
40
+ Like `@subscribe`: the path is resolved on the template **host component**; the directive re-subscribes when observed props change. Placeholder values (`null`, `""`, `0`, …): [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path) (`skipEmptyPlaceholder` not available on `sub()` yet).
41
41
 
42
42
  <sonic-code language="typescript">
43
43
  <template>
@@ -81,7 +81,7 @@ export const docsUserRowKey = new DataProviderKey&lt;
81
81
  </template>
82
82
  </sonic-code>
83
83
 
84
- The second generic (`{ dataProvider: string | null }`) lists what the host must expose so `"${dataProvider}"` can be resolved. See [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey).
84
+ The second generic (`{ dataProvider: string | null }`) lists what the host must expose so `"${dataProvider}"` can be resolved. See [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey) and [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path).
85
85
 
86
86
  ### Scope — which store segment applies
87
87
 
@@ -1,6 +1,6 @@
1
1
  # API configuration
2
2
 
3
- `APIConfiguration` is the object built by [`HTML.getApiConfiguration`](../../core/utils/HTML.ts) from **ancestor attributes** on the DOM (or from a typed publisher — see [@get configuration key](#docs/_decorators/get.md/get)). It is passed to [`API`](../../core/utils/api.ts) by fetchers, **sonic-submit**, the **`wording()`** directive, and **`@get`**.
3
+ `APIConfiguration` is the object built by [`HTML.getApiConfiguration`](../../core/utils/HTML.ts) from **ancestor attributes** on the DOM (or from a typed publisher — see [@get](#docs/_decorators/get.md/get) / [@post](#docs/_decorators/post.md/post) configuration key). It is passed to [`API`](../../core/utils/api.ts) by fetchers, **sonic-submit**, the **`wording()`** directive, **`@get`**, **`@post`**, **`@put`**, and **`@patch`**.
4
4
 
5
5
  > **Mock service:** same [Local API demos](#docs/_misc/docs-mock-api.md/docs-mock-api) Service Worker / Vite middleware. Routes used on this page are listed in the [API config routes](#api-config-routes) section below.
6
6
 
@@ -74,6 +74,8 @@ Implementation: `src/docs/mock-api/api-config-mock.ts` (bundled in the Service W
74
74
  ## See also
75
75
 
76
76
  - [@get](#docs/_decorators/get.md/get) — `APIConfiguration` + `Endpoint`
77
+ - [@post](#docs/_decorators/post.md/post) — same configuration model for POST
78
+ - [@put](#docs/_decorators/put.md/put) · [@patch](#docs/_decorators/patch.md/patch) — PUT / PATCH
77
79
  - [Endpoint](#docs/_misc/endpoint.md/endpoint) — typed path
78
80
  - [Local API demos](#docs/_misc/docs-mock-api.md/docs-mock-api) — offline `serviceURL`
79
81
  - [Fetch](#core/components/functional/fetch/fetch.md/fetch) — attribute table (legacy sonic-fetch)
@@ -78,7 +78,7 @@ firstItem.path; // "data.items.0"
78
78
 
79
79
  ### Dynamic paths
80
80
 
81
- Use placeholders `${prop}` or `{$prop}` in the path string. The path is resolved at runtime from the component's properties. The type remains declarative:
81
+ Use placeholders `${prop}` or `{$prop}` in the path string. The path is resolved at runtime from the component's properties. See [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path) for resolution rules and `skipEmptyPlaceholder`. The type remains declarative:
82
82
 
83
83
  <sonic-code language="typescript">
84
84
  <template>
@@ -159,7 +159,7 @@ export class UserForm extends LitElement {
159
159
  </template>
160
160
  </sonic-code>
161
161
 
162
- These decorators support dynamic paths: `"base.${prop}"` in the constructor. A wrong property type (e.g. `number` for `DataProviderKey<string>`) is a TypeScript error. See [@handle](#docs/_decorators/handle.md/handle) for method callbacks.
162
+ These decorators support dynamic paths: `"base.${prop}"` in the constructor. A wrong property type (e.g. `number` for `DataProviderKey<string>`) is a TypeScript error. Resolution rules: [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path). See [@handle](#docs/_decorators/handle.md/handle) for method callbacks.
163
163
 
164
164
  ## Notes
165
165
 
@@ -0,0 +1,71 @@
1
+ # Dynamic path placeholders
2
+
3
+ Decorators and `DataProviderKey` paths can include **placeholders** resolved on the host component at runtime:
4
+
5
+ - `${prop}` or `{$prop}` — e.g. `"users/${userId}"`, `"api/sessions/${sessionId}/sync"`
6
+ - Nested expressions — e.g. `"teams.${teamId}.members"`
7
+
8
+ Resolution is done by [`resolveDynamicPath`](../../core/decorators/subscriber/dynamicPath.ts). The root property names (`userId`, `sessionId`, …) are watched via `requestAnimationFrame` (see `dynamicPropertyWatch.ts`).
9
+
10
+ ## Default behaviour (`ready` / `not ready`)
11
+
12
+ | Placeholder value | Path `ready`? | Inserted segment | Notes |
13
+ |-------------------|---------------|------------------|-------|
14
+ | `undefined` | **no** | — | Wait until defined |
15
+ | `null` | **no** | — | Same as undefined |
16
+ | `""` | **yes** | empty string | e.g. `sessions//sync` — request may still run |
17
+ | `0` | **yes** | `"0"` | Not treated as “missing” |
18
+ | `false` | **yes** | `"false"` | |
19
+ | `42`, `"alpha"` | **yes** | `"42"`, `"alpha"` | |
20
+
21
+ When `ready: false`, decorators **do not** call the network (for `@get` / `@post` / `@put` / `@patch`), **unsubscribe** (`@bind` / `@subscribe`), or skip publisher binding (`@publish` / `@handle`). The decorated property is often left unchanged or set to `undefined` (HTTP decorators).
22
+
23
+ When the placeholder later becomes valid, observers run again and behaviour resumes.
24
+
25
+ ## `skipEmptyPlaceholder` option
26
+
27
+ Some APIs should not run while an id is still `""`. Opt in per decorator:
28
+
29
+ ```typescript
30
+ @get(new Endpoint<User, { userId: string }>("users/${userId}"), {
31
+ skipEmptyPlaceholder: true,
32
+ })
33
+ ```
34
+
35
+ | Option | Scope |
36
+ |--------|--------|
37
+ | `skipEmptyPlaceholder?: boolean` | **Only** empty string `''` on a placeholder |
38
+ | Default `false` | `""` is inserted into the path (legacy behaviour) |
39
+ | `true` | `""` → `ready: false`, same as `null` / `undefined` for that segment |
40
+
41
+ Does **not** affect `0`, `false`, `null`, or `undefined` (nullish stays “not ready” regardless).
42
+
43
+ Available on:
44
+
45
+ | API | Where |
46
+ |-----|--------|
47
+ | `@get` | 2nd or 3rd argument (`GetOptions`) |
48
+ | `@post` / `@put` / `@patch` | `PostOptions` / `ApiSendOptions` |
49
+ | `@bind` / `@subscribe` | `BindOptions` |
50
+ | `@publish` | 2nd argument `PublishOptions` |
51
+ | `@handle` | `HandleOptions` |
52
+
53
+ ## Per-decorator summary
54
+
55
+ | Decorator | `ready: false` | `ready: true` |
56
+ |-----------|----------------|---------------|
57
+ | `@get` | No HTTP; `payload` → `undefined` | `ApiResult` assigned |
58
+ | `@post` / `@put` / `@patch` | No HTTP; `payload` → `undefined` | `ApiResult` assigned |
59
+ | `@subscribe` / `@bind` | Unsubscribe; prop keeps last value | Subscribe `onAssign` |
60
+ | `@publish` | Internal publisher `null`; writes ignored | `publisher.set` on assign |
61
+ | `@handle` | No subscription; with `waitForAllDefined`, method not called | Callback on assign |
62
+
63
+ ## See also
64
+
65
+ - [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey) — path syntax
66
+ - [Endpoint](#docs/_misc/endpoint.md/endpoint) — HTTP paths + host generic `U`
67
+ - HTTP: [@get](#docs/_decorators/get.md/get) · [@post](#docs/_decorators/post.md/post) · [@put](#docs/_decorators/put.md/put) · [@patch](#docs/_decorators/patch.md/patch)
68
+ - Store: [@subscribe](#docs/_decorators/subscribe.md/subscribe) · [@bind](#docs/_decorators/bind.md/bind) · [@publish](#docs/_decorators/publish.md/publish) · [@handle](#docs/_decorators/handle.md/handle)
69
+ - Legacy strings: [@onAssign](#docs/_decorators/on-assign.md/on-assign) (dynamic paths section)
70
+ - Templates: [sub()](#docs/_directives/sub.md/sub) — dynamic paths in Lit (no `skipEmptyPlaceholder` yet)
71
+ - Tutorial: [My first component](#docs/_getting-started/my-first-component.md/my-first-component) — `"${dataProvider}"` scope pattern
@@ -2,7 +2,7 @@
2
2
 
3
3
  `Endpoint<T, U>` describes a single HTTP path (or a path accepted by `API.get`) and carries the expected response type `T`. Unlike [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey), there is no dot-navigation: the path is one string.
4
4
 
5
- The optional second generic `U` (default `any`) describes host properties used to resolve dynamic segments in the path (`${…}` / `{$…}`), for example with the [@get](#docs/_decorators/get.md/get) decorator.
5
+ The optional second generic `U` (default `any`) describes host properties used to resolve dynamic segments in the path (`${…}` / `{$…}`), for example with [@get](#docs/_decorators/get.md/get) or [@post](#docs/_decorators/post.md/post). See [Dynamic path placeholders](#docs/_misc/dynamic-path.md/dynamic-path) for `null` / `undefined` / `""` / `0` and `skipEmptyPlaceholder`.
6
6
 
7
7
  ## Import
8
8
 
@@ -30,7 +30,7 @@ const one = new Endpoint&lt;User, { userId: string }&gt;("users/${userId}");
30
30
 
31
31
  ## Publisher key for payloads
32
32
 
33
- `getDataProviderKey()` returns a typed publisher key whose `path` matches the endpoint path (payload typing follows `ApiGetResult` for this endpoint). Useful when pairing `@get` with `@publish` / `@subscribe` (see [@get](#docs/_decorators/get.md/get)).
33
+ `getDataProviderKey()` returns a typed publisher key whose `path` matches the endpoint path (payload typing follows `ApiResult` for this endpoint). Useful when pairing `@get` with `@publish` / `@subscribe` (see [@get](#docs/_decorators/get.md/get)).
34
34
 
35
35
  ## Data-provider paths
36
36
 
@@ -39,5 +39,7 @@ const one = new Endpoint&lt;User, { userId: string }&gt;("users/${userId}");
39
39
  ## See also
40
40
 
41
41
  - [API configuration](#docs/_misc/api-configuration.md/api-configuration) — `serviceURL`, token, wording (mock demos)
42
- - [@get](#docs/_decorators/get.md/get) — decorator that uses `Endpoint<T>`
42
+ - [@get](#docs/_decorators/get.md/get) — GET decorator
43
+ - [@post](#docs/_decorators/post.md/post) — POST decorator (body from a `DataProviderKey`)
44
+ - [@put](#docs/_decorators/put.md/put) · [@patch](#docs/_decorators/patch.md/patch) — PUT / PATCH decorators
43
45
  - [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey) — typed publisher paths (dot notation)
@@ -1,6 +1,14 @@
1
- import { html, LitElement, nothing } from "lit";
2
- import { customElement, property } from "lit/decorators.js";
1
+ import { css, html, LitElement, nothing } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
4
+ import { tailwind } from "../tailwind";
3
5
  import { docsSourceLinks, type DocsSource } from "./docs-source-link";
6
+ import { DOCS_SOURCE_RAW } from "./docs-source-raw";
7
+ import { resolveSourceExcerpt } from "./docs-source-excerpt";
8
+ import "../prism/prism";
9
+ import { prismCSS } from "../prism";
10
+ import * as Prism from "prismjs";
11
+ import "prismjs/components/prism-typescript";
4
12
 
5
13
  export type { DocsSource };
6
14
 
@@ -260,6 +268,30 @@ export const DOCS_DEMO_SOURCE_REGISTRY: Record<string, DocsSource[]> = {
260
268
  },
261
269
  { path: "src/docs/example/decorators-demo-geo.ts", label: "geo API" },
262
270
  ],
271
+ "demo-api-post": [
272
+ {
273
+ path: "src/docs/example/decorators-demo-post.ts",
274
+ line: 53,
275
+ label: "demo-api-post",
276
+ },
277
+ { path: "src/docs/mock-api/router.ts", label: "mock router" },
278
+ ],
279
+ "demo-api-post-dynamic": [
280
+ {
281
+ path: "src/docs/example/decorators-demo-post.ts",
282
+ line: 143,
283
+ label: "demo-api-post-dynamic",
284
+ },
285
+ { path: "src/docs/mock-api/router.ts", label: "mock router (sessions sync)" },
286
+ ],
287
+ "demo-api-post-publish": [
288
+ {
289
+ path: "src/docs/example/decorators-demo-post.ts",
290
+ line: 209,
291
+ label: "demo-api-post-publish",
292
+ },
293
+ { path: "src/docs/mock-api/router.ts", label: "mock router" },
294
+ ],
263
295
  "demo-handle": [
264
296
  {
265
297
  path: "src/docs/example/decorators-demo-subscribe-publish-get-demos.ts",
@@ -384,14 +416,81 @@ export const DOCS_DEMO_SOURCE_REGISTRY: Record<string, DocsSource[]> = {
384
416
  ],
385
417
  };
386
418
 
419
+ function highlightTs(code: string): string {
420
+ return Prism.highlight(code, Prism.languages.typescript, "typescript");
421
+ }
422
+
387
423
  @customElement("docs-demo-sources")
388
424
  export class DocsDemoSources extends LitElement {
425
+ static styles = [
426
+ tailwind,
427
+ prismCSS,
428
+ css`
429
+ :host {
430
+ display: block;
431
+ margin-bottom: 0.75rem;
432
+ }
433
+ pre {
434
+ font-size: 13px !important;
435
+ margin: 0;
436
+ }
437
+ `,
438
+ ];
439
+
389
440
  /** Registry key matching the live demo custom element tag. */
390
441
  @property({ attribute: "for" }) forTag = "";
391
442
 
443
+ @property({ type: Boolean, attribute: "show-code" }) showCode = true;
444
+
445
+ @state() private highlighted = "";
446
+
447
+ connectedCallback() {
448
+ super.connectedCallback();
449
+ this.refreshHighlight();
450
+ }
451
+
452
+ updated(changed: Map<string, unknown>) {
453
+ super.updated(changed);
454
+ if (changed.has("forTag") || changed.has("showCode")) {
455
+ this.refreshHighlight();
456
+ }
457
+ }
458
+
459
+ private refreshHighlight() {
460
+ if (!this.showCode) {
461
+ this.highlighted = "";
462
+ return;
463
+ }
464
+ const sources = DOCS_DEMO_SOURCE_REGISTRY[this.forTag];
465
+ const primary = sources?.[0];
466
+ if (!primary) {
467
+ this.highlighted = "";
468
+ return;
469
+ }
470
+ const raw = DOCS_SOURCE_RAW[primary.path];
471
+ if (!raw) {
472
+ console.warn(`[docs-demo-sources] No raw source for ${primary.path}`);
473
+ this.highlighted = "";
474
+ return;
475
+ }
476
+ const excerpt = resolveSourceExcerpt(raw, this.forTag, {
477
+ line: primary.line,
478
+ });
479
+ this.highlighted = highlightTs(excerpt);
480
+ }
481
+
392
482
  render() {
393
483
  const sources = DOCS_DEMO_SOURCE_REGISTRY[this.forTag];
394
484
  if (!sources?.length) return nothing;
395
- return docsSourceLinks(sources);
485
+ return html`
486
+ ${docsSourceLinks(sources)}
487
+ ${this.showCode && this.highlighted
488
+ ? html`
489
+ <pre
490
+ class="rounded-md custom-scroll language-typescript overflow-auto border border-neutral-200/80 dark:border-neutral-700/80"
491
+ ><code>${unsafeHTML(this.highlighted)}</code></pre>
492
+ `
493
+ : nothing}
494
+ `;
396
495
  }
397
496
  }
@@ -1,28 +1,4 @@
1
1
  /**
2
- * Raw sources for `<docs-lit-demo>` — explicit imports only (no import.meta.glob).
3
- * Included in the **docs** bundle (`src/docs.ts`), not in the **core** library build (`src/index.ts`).
4
- * Add one import + map entry when registering a new file in `DOCS_LIT_DEMO_REGISTRY`.
2
+ * @deprecated Importez `DOCS_SOURCE_RAW` depuis `./docs-source-raw`.
5
3
  */
6
- import docsJokeDemos from "../example/docs-joke-demos.ts?raw";
7
- import docsListDemos from "../example/docs-list-demos.ts?raw";
8
- import docsQueueDemos from "../example/docs-queue-demos.ts?raw";
9
- import docsToggleDemos from "../example/docs-toggle-demos.ts?raw";
10
- import docsUserTwoScopes from "../example/docs-user-two-scopes.ts?raw";
11
- import docsRouterDemos from "../example/docs-router-demos.ts?raw";
12
- import docsSubmitDemos from "../example/docs-submit-demos.ts?raw";
13
- import docsApiConfigDemos from "../example/docs-api-config-demos.ts?raw";
14
- import docsUsersList from "../example/docs-users-list.ts?raw";
15
- import users from "../example/users.ts?raw";
16
-
17
- export const DOCS_LIT_DEMO_RAW: Record<string, string> = {
18
- "src/docs/example/docs-toggle-demos.ts": docsToggleDemos,
19
- "src/docs/example/docs-joke-demos.ts": docsJokeDemos,
20
- "src/docs/example/docs-queue-demos.ts": docsQueueDemos,
21
- "src/docs/example/docs-list-demos.ts": docsListDemos,
22
- "src/docs/example/docs-router-demos.ts": docsRouterDemos,
23
- "src/docs/example/docs-submit-demos.ts": docsSubmitDemos,
24
- "src/docs/example/docs-api-config-demos.ts": docsApiConfigDemos,
25
- "src/docs/example/docs-users-list.ts": docsUsersList,
26
- "src/docs/example/docs-user-two-scopes.ts": docsUserTwoScopes,
27
- "src/docs/example/users.ts": users,
28
- };
4
+ export { DOCS_LIT_DEMO_RAW, DOCS_SOURCE_RAW } from "./docs-source-raw";