@supersoniks/concorde 4.9.0 → 4.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-infos.json +1 -1
- package/concorde-core.bundle.js +209 -209
- package/concorde-core.es.js +3388 -3365
- package/dist/concorde-core.bundle.js +209 -209
- package/dist/concorde-core.es.js +3388 -3365
- package/dist/docs-mock-api-sw.js +3 -2
- package/dist/docs-mock-api-sw.js.map +2 -2
- package/dist/js/core/decorators/api.js +64 -12
- package/dist/js/core/decorators/api.js.map +1 -1
- package/dist/types/core/decorators/api.d.ts +14 -1
- package/dist/types/core/decorators/api.d.ts.map +1 -1
- package/docs/assets/{index-t0-i22oI.css → index-CZLTFws2.css} +1 -1
- package/docs/assets/{index-BEgZEIbv.js → index-nkkwC13U.js} +751 -476
- package/docs/index.html +2 -3
- package/docs/src/docs/_decorators/ancestor-attribute.md +56 -8
- package/docs/src/docs/search/docs-search.json +62 -2
- package/docs/src/tsconfig.declarations.json +22 -0
- package/docs/src/tsconfig.emit.json +23 -0
- package/docs/src/tsconfig.json +3 -285
- package/package.json +1 -1
- package/public/docs-mock-api-sw.js +3 -2
- package/public/docs-mock-api-sw.js.map +2 -2
- package/src/core/decorators/api.spec.ts +74 -4
- package/src/core/decorators/api.ts +103 -20
- package/src/docs/_decorators/get.md +47 -1
- package/src/docs/components/docs-demo-sources.ts +7 -2
- package/src/docs/example/decorators-demo-geo.ts +5 -0
- package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +72 -31
- package/src/docs/mock-api/router.ts +3 -1
- package/src/docs/search/docs-search.json +31 -1
|
@@ -13,10 +13,53 @@ Pass an [`Endpoint<T>`](#docs/_misc/endpoint.md/endpoint) as the first argument.
|
|
|
13
13
|
|
|
14
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
15
|
|
|
16
|
+
## Options (`GetOptions`)
|
|
17
|
+
|
|
18
|
+
Pass as the **second** argument (scoped config) or **third** (with a configuration key). Same shape as send decorators where applicable:
|
|
19
|
+
|
|
20
|
+
| Option | Description |
|
|
21
|
+
|--------|-------------|
|
|
22
|
+
| `skipEmptyPlaceholder` | Treat `""` as not ready (see [Dynamic path](#docs/_misc/dynamic-path.md/dynamic-path)). |
|
|
23
|
+
| `refetchEveryMs` | Automatic polling interval (ms). `0` or omitted = no polling. |
|
|
24
|
+
| `triggerKey` | `DataProviderKey` — `PublisherManager.get(...).invalidate()` on that publisher re-runs the GET. |
|
|
25
|
+
|
|
26
|
+
### Manual refetch (`triggerKey`)
|
|
27
|
+
|
|
28
|
+
Same pattern as [@post](#docs/_decorators/post.md/post): a publisher acts as a **signal**, independent of the response payload.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { get, type ApiResult } from "@supersoniks/concorde/decorators";
|
|
32
|
+
import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
|
|
33
|
+
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
34
|
+
import { dp } from "@supersoniks/concorde/core/utils/PublisherProxy";
|
|
35
|
+
|
|
36
|
+
const geoCommunesEndpoint = new Endpoint<GeoCommuneRow[]>(
|
|
37
|
+
"communes?limit=5&fields=nom,code",
|
|
38
|
+
);
|
|
39
|
+
const geoCommunesRefreshKey = new DataProviderKey<void>(
|
|
40
|
+
"docsDemoGeoCommunesRefresh",
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
@get(geoCommunesEndpoint, apiConfigurationKey, {
|
|
44
|
+
triggerKey: geoCommunesRefreshKey,
|
|
45
|
+
})
|
|
46
|
+
@state()
|
|
47
|
+
payload: ApiResult<GeoCommuneRow[]> | null = null;
|
|
48
|
+
|
|
49
|
+
// Same component or anywhere else:
|
|
50
|
+
dp(geoCommunesRefreshKey).invalidate();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Prefer `triggerKey` over mutating the API configuration publisher when you only want to **reload the same URL** — no need to touch `serviceURL`, tokens, etc.
|
|
54
|
+
|
|
55
|
+
The docs mock adds an **`X-Fetched-At`** response header on `/docs-mock-api/geo/communes` so live demos can show when the last fetch happened (`response.headers.get("X-Fetched-At")`).
|
|
56
|
+
|
|
16
57
|
## When the GET runs again
|
|
17
58
|
|
|
18
59
|
- A referenced Lit property changes (endpoint path and/or config key contains `${...}`).
|
|
19
60
|
- `set` on the active configuration publisher (`onInternalMutation`).
|
|
61
|
+
- `triggerKey` publisher `invalidate()`.
|
|
62
|
+
- `refetchEveryMs` polling (after each successful fetch).
|
|
20
63
|
|
|
21
64
|
## Import
|
|
22
65
|
|
|
@@ -42,6 +85,8 @@ payload: ApiResult<User> | null = null;
|
|
|
42
85
|
|
|
43
86
|
## Live demos
|
|
44
87
|
|
|
88
|
+
`@get` with **`triggerKey`** — refresh button + `X-Fetched-At` timestamp in the UI:
|
|
89
|
+
|
|
45
90
|
<sonic-code>
|
|
46
91
|
<template>
|
|
47
92
|
<docs-demo-sources for="demo-api-get"></docs-demo-sources>
|
|
@@ -49,12 +94,13 @@ payload: ApiResult<User> | null = null;
|
|
|
49
94
|
</template>
|
|
50
95
|
</sonic-code>
|
|
51
96
|
|
|
52
|
-
Dynamic config and endpoint path (`
|
|
97
|
+
Dynamic config and endpoint path. Manual refetch is triggered **from a separate component** (same `triggerKey` publisher):
|
|
53
98
|
|
|
54
99
|
<sonic-code>
|
|
55
100
|
<template>
|
|
56
101
|
<docs-demo-sources for="demo-api-get-configuration-key"></docs-demo-sources>
|
|
57
102
|
<demo-api-get-configuration-key></demo-api-get-configuration-key>
|
|
103
|
+
<demo-api-get-refresh-remote></demo-api-get-refresh-remote>
|
|
58
104
|
</template>
|
|
59
105
|
</sonic-code>
|
|
60
106
|
|
|
@@ -255,10 +255,15 @@ export const DOCS_DEMO_SOURCE_REGISTRY: Record<string, DocsSource[]> = {
|
|
|
255
255
|
"demo-api-get-configuration-key": [
|
|
256
256
|
{
|
|
257
257
|
path: "src/docs/example/decorators-demo-subscribe-publish-get-demos.ts",
|
|
258
|
-
line:
|
|
258
|
+
line: 147,
|
|
259
259
|
label: "demo-api-get-configuration-key",
|
|
260
260
|
},
|
|
261
|
-
{
|
|
261
|
+
{
|
|
262
|
+
path: "src/docs/example/decorators-demo-subscribe-publish-get-demos.ts",
|
|
263
|
+
line: 239,
|
|
264
|
+
label: "demo-api-get-refresh-remote",
|
|
265
|
+
},
|
|
266
|
+
{ path: "src/docs/example/decorators-demo-geo.ts", label: "geo API + triggerKey" },
|
|
262
267
|
],
|
|
263
268
|
"demo-api-get-publish-subscribe": [
|
|
264
269
|
{
|
|
@@ -49,3 +49,8 @@ export const geoCommunesApiGetEndpointDynamic = new Endpoint<GeoCommuneRow[]>(
|
|
|
49
49
|
export const geoCommunesApiGetPublishKey = new DataProviderKey<
|
|
50
50
|
ApiResult<GeoCommuneRow[]>
|
|
51
51
|
>(geoCommunesApiGetEndpoint.path);
|
|
52
|
+
|
|
53
|
+
/** Signal de refetch manuel pour les démos `@get` (voir `triggerKey`). */
|
|
54
|
+
export const docsDemoGeoCommunesRefreshKey = new DataProviderKey<void>(
|
|
55
|
+
"docsDemoGeoCommunesRefresh",
|
|
56
|
+
);
|
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
import { html, LitElement } from "lit";
|
|
2
2
|
import { customElement, property, state } from "lit/decorators.js";
|
|
3
3
|
import { get, handle, publish, subscribe } from "@supersoniks/concorde/decorators";
|
|
4
|
-
import type {
|
|
5
|
-
APIConfiguration,
|
|
6
|
-
ApiResult,
|
|
7
|
-
} from "@supersoniks/concorde/core/utils/api";
|
|
4
|
+
import type { ApiResult } from "@supersoniks/concorde/core/utils/api";
|
|
8
5
|
import { DataProviderKey } from "@supersoniks/concorde/core/utils/dataProviderKey";
|
|
9
6
|
import { sub } from "@supersoniks/concorde/directives";
|
|
10
7
|
import {
|
|
8
|
+
dp,
|
|
11
9
|
get as storeGet,
|
|
12
10
|
set,
|
|
13
11
|
} from "@supersoniks/concorde/core/utils/PublisherProxy";
|
|
14
12
|
import { publishDemoKey } from "./docs-provider-keys";
|
|
15
|
-
import {
|
|
16
|
-
docsDemoDynApiConfAKey,
|
|
17
|
-
docsDemoDynApiConfBKey,
|
|
18
|
-
} from "./decorators-demo-geo";
|
|
19
13
|
import { tailwind } from "../tailwind";
|
|
20
14
|
import "./decorators-demo-init";
|
|
21
15
|
import {
|
|
22
16
|
docsDemoDynApiConfKeyTemplate,
|
|
23
17
|
docsDemoGeoApiConfigurationKey,
|
|
18
|
+
docsDemoGeoCommunesRefreshKey,
|
|
24
19
|
geoCommunesApiGetEndpoint,
|
|
25
20
|
geoCommunesApiGetEndpointDynamic,
|
|
26
21
|
geoCommunesApiGetPublishKey,
|
|
27
22
|
type GeoCommuneRow,
|
|
28
23
|
} from "./decorators-demo-geo";
|
|
29
24
|
|
|
25
|
+
function formatFetchedAt(response: Response | null | undefined): string {
|
|
26
|
+
const raw = response?.headers.get("X-Fetched-At");
|
|
27
|
+
if (!raw) return "—";
|
|
28
|
+
try {
|
|
29
|
+
return new Date(raw).toLocaleTimeString();
|
|
30
|
+
} catch {
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
type PublishDemoData = {
|
|
31
36
|
email: string;
|
|
32
37
|
message: string;
|
|
@@ -90,22 +95,35 @@ export class DemoPublish extends LitElement {
|
|
|
90
95
|
export class DemoApiGet extends LitElement {
|
|
91
96
|
static styles = [tailwind];
|
|
92
97
|
|
|
93
|
-
@get(geoCommunesApiGetEndpoint, docsDemoGeoApiConfigurationKey
|
|
98
|
+
@get(geoCommunesApiGetEndpoint, docsDemoGeoApiConfigurationKey, {
|
|
99
|
+
triggerKey: docsDemoGeoCommunesRefreshKey,
|
|
100
|
+
})
|
|
94
101
|
@state()
|
|
95
102
|
geoApiPayload?: ApiResult<GeoCommuneRow[]>;
|
|
96
103
|
|
|
104
|
+
private refresh() {
|
|
105
|
+
dp(docsDemoGeoCommunesRefreshKey).invalidate();
|
|
106
|
+
}
|
|
107
|
+
|
|
97
108
|
render() {
|
|
98
109
|
const rows = this.geoApiPayload?.result;
|
|
99
110
|
const status = this.geoApiPayload?.response?.status;
|
|
111
|
+
const fetchedAt = formatFetchedAt(this.geoApiPayload?.response ?? null);
|
|
100
112
|
return html`
|
|
101
113
|
<div class="space-y-2">
|
|
102
114
|
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
|
103
115
|
<code>@get</code> — même service que
|
|
104
116
|
<code>sonic-queue</code> (<code>/docs-mock-api/geo/</code>).
|
|
105
117
|
Propriété typée <code>ApiResult<T></code>.
|
|
118
|
+
Refetch via <code>triggerKey</code> (<code
|
|
119
|
+
>docsDemoGeoCommunesRefresh</code
|
|
120
|
+
>).
|
|
106
121
|
</p>
|
|
122
|
+
<sonic-button size="sm" @click=${this.refresh}>Rafraîchir</sonic-button>
|
|
107
123
|
${status != null
|
|
108
|
-
? html`<p class="text-xs text-neutral-500">
|
|
124
|
+
? html`<p class="text-xs text-neutral-500">
|
|
125
|
+
HTTP ${status} · fetch ${fetchedAt}
|
|
126
|
+
</p>`
|
|
109
127
|
: ""}
|
|
110
128
|
${this.geoApiPayload === null
|
|
111
129
|
? html`<p class="text-neutral-500">Chargement…</p>`
|
|
@@ -139,29 +157,23 @@ export class DemoApiGetConfigurationKey extends LitElement {
|
|
|
139
157
|
@property({ type: Number })
|
|
140
158
|
communeLimit = 5;
|
|
141
159
|
|
|
142
|
-
@get(geoCommunesApiGetEndpointDynamic, docsDemoDynApiConfKeyTemplate
|
|
160
|
+
@get(geoCommunesApiGetEndpointDynamic, docsDemoDynApiConfKeyTemplate, {
|
|
161
|
+
triggerKey: docsDemoGeoCommunesRefreshKey,
|
|
162
|
+
})
|
|
143
163
|
@state()
|
|
144
164
|
geoApiPayloadDyn?: ApiResult<GeoCommuneRow[]>;
|
|
145
165
|
|
|
146
|
-
private touchCurrentConfigPublisher() {
|
|
147
|
-
const key =
|
|
148
|
-
this.configSlot === "A" ? docsDemoDynApiConfAKey : docsDemoDynApiConfBKey;
|
|
149
|
-
const cur = storeGet(key) as APIConfiguration;
|
|
150
|
-
set(key, {
|
|
151
|
-
...cur,
|
|
152
|
-
blockUntilDone: !cur.blockUntilDone,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
166
|
render() {
|
|
157
167
|
const rows = this.geoApiPayloadDyn?.result;
|
|
158
168
|
const status = this.geoApiPayloadDyn?.response?.status;
|
|
169
|
+
const fetchedAt = formatFetchedAt(this.geoApiPayloadDyn?.response ?? null);
|
|
159
170
|
return html`
|
|
160
171
|
<div class="space-y-3">
|
|
161
172
|
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
|
162
173
|
Config dynamique (<code>docsDemoDynApiConf\${configSlot}</code>) +
|
|
163
|
-
path d’endpoint avec <code>communeLimit</code>.
|
|
164
|
-
|
|
174
|
+
path d’endpoint avec <code>communeLimit</code>. Le refetch manuel
|
|
175
|
+
passe par <code>triggerKey</code> — voir le bouton « ailleurs »
|
|
176
|
+
ci-dessous dans la doc.
|
|
165
177
|
</p>
|
|
166
178
|
<div class="flex flex-wrap gap-2 items-center">
|
|
167
179
|
<span class="text-xs text-neutral-500">Limite API :</span>
|
|
@@ -196,16 +208,11 @@ export class DemoApiGetConfigurationKey extends LitElement {
|
|
|
196
208
|
}}
|
|
197
209
|
>docsDemoDynApiConfB</sonic-button
|
|
198
210
|
>
|
|
199
|
-
<sonic-button
|
|
200
|
-
size="sm"
|
|
201
|
-
@click=${() => this.touchCurrentConfigPublisher()}
|
|
202
|
-
>Toucher la config</sonic-button
|
|
203
|
-
>
|
|
204
211
|
</div>
|
|
205
212
|
${status != null
|
|
206
213
|
? html`<p class="text-xs text-neutral-500">
|
|
207
|
-
HTTP ${status} · limit=${this.communeLimit} ·
|
|
208
|
-
docsDemoDynApiConf${this.configSlot}
|
|
214
|
+
HTTP ${status} · fetch ${fetchedAt} · limit=${this.communeLimit} ·
|
|
215
|
+
conf=docsDemoDynApiConf${this.configSlot}
|
|
209
216
|
</p>`
|
|
210
217
|
: ""}
|
|
211
218
|
${this.geoApiPayloadDyn === null
|
|
@@ -230,6 +237,35 @@ export class DemoApiGetConfigurationKey extends LitElement {
|
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
|
|
240
|
+
/** Déclenche le refetch d’un `@get` distant via `triggerKey` (sans toucher la config). */
|
|
241
|
+
@customElement("demo-api-get-refresh-remote")
|
|
242
|
+
export class DemoApiGetRefreshRemote extends LitElement {
|
|
243
|
+
static styles = [tailwind];
|
|
244
|
+
|
|
245
|
+
private refreshFromElsewhere() {
|
|
246
|
+
dp(docsDemoGeoCommunesRefreshKey).invalidate();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
render() {
|
|
250
|
+
return html`
|
|
251
|
+
<div
|
|
252
|
+
class="rounded border border-dashed border-neutral-300 dark:border-neutral-600 p-3 space-y-2"
|
|
253
|
+
>
|
|
254
|
+
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
|
255
|
+
Composant séparé — même publisher
|
|
256
|
+
<code>docsDemoGeoCommunesRefresh</code> :
|
|
257
|
+
</p>
|
|
258
|
+
<pre
|
|
259
|
+
class="text-xs bg-neutral-100 dark:bg-neutral-800 p-2 rounded overflow-x-auto"
|
|
260
|
+
><code>dp(docsDemoGeoCommunesRefreshKey).invalidate();</code></pre>
|
|
261
|
+
<sonic-button size="sm" variant="secondary" @click=${this.refreshFromElsewhere}>
|
|
262
|
+
Rafraîchir le GET (depuis ailleurs)
|
|
263
|
+
</sonic-button>
|
|
264
|
+
</div>
|
|
265
|
+
`;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
233
269
|
@customElement("demo-api-get-publish-subscribe")
|
|
234
270
|
export class DemoApiGetPublishSubscribe extends LitElement {
|
|
235
271
|
static styles = [tailwind];
|
|
@@ -247,6 +283,9 @@ export class DemoApiGetPublishSubscribe extends LitElement {
|
|
|
247
283
|
const rows =
|
|
248
284
|
this.geoCommunesFromSubscribe ?? this.geoApiPayloadPublished?.result;
|
|
249
285
|
const status = this.geoApiPayloadPublished?.response?.status;
|
|
286
|
+
const fetchedAt = formatFetchedAt(
|
|
287
|
+
this.geoApiPayloadPublished?.response ?? null,
|
|
288
|
+
);
|
|
250
289
|
return html`
|
|
251
290
|
<div class="space-y-2">
|
|
252
291
|
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
|
@@ -255,7 +294,9 @@ export class DemoApiGetPublishSubscribe extends LitElement {
|
|
|
255
294
|
rendu que <code><demo-api-get></code> (doc @get).
|
|
256
295
|
</p>
|
|
257
296
|
${status != null
|
|
258
|
-
? html`<p class="text-xs text-neutral-500">
|
|
297
|
+
? html`<p class="text-xs text-neutral-500">
|
|
298
|
+
HTTP ${status} · fetch ${fetchedAt}
|
|
299
|
+
</p>`
|
|
259
300
|
: ""}
|
|
260
301
|
${this.geoApiPayloadPublished === null
|
|
261
302
|
? html`<p class="text-neutral-500">Chargement…</p>`
|
|
@@ -242,7 +242,9 @@ export async function handleDocsMockApiRequest(
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
if (subPath === "/geo/communes" || subPath.startsWith("/geo/communes")) {
|
|
245
|
-
return json(geoCommunes(url)
|
|
245
|
+
return json(geoCommunes(url), {
|
|
246
|
+
headers: { "X-Fetched-At": new Date().toISOString() },
|
|
247
|
+
});
|
|
246
248
|
}
|
|
247
249
|
|
|
248
250
|
const jokeMatch = subPath.match(/^\/jokes\/joke\/([^/]+)$/);
|
|
@@ -4030,7 +4030,22 @@
|
|
|
4030
4030
|
}
|
|
4031
4031
|
},
|
|
4032
4032
|
{
|
|
4033
|
-
"search": "Use { dynamic: true } when the decorated property must follow the DOM.
|
|
4033
|
+
"search": "Use { dynamic: true } when the decorated property must follow the DOM. With Lit, add @state() on the same property so template updates are visible when the observer assigns a new value.\n",
|
|
4034
|
+
"files": {
|
|
4035
|
+
"docs/_decorators/ancestor-attribute.md": {
|
|
4036
|
+
"title": "@ancestorAttribute",
|
|
4037
|
+
"hashes": {
|
|
4038
|
+
"dynamic-mode": {
|
|
4039
|
+
"count": 1,
|
|
4040
|
+
"title": "Dynamic mode",
|
|
4041
|
+
"type": "paragraph"
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
},
|
|
4047
|
+
{
|
|
4048
|
+
"search": "The live demo below shows only the resolved attribute value — no store, no reparenting.\n",
|
|
4034
4049
|
"files": {
|
|
4035
4050
|
"docs/_decorators/ancestor-attribute.md": {
|
|
4036
4051
|
"title": "@ancestorAttribute",
|
|
@@ -4729,6 +4744,21 @@
|
|
|
4729
4744
|
}
|
|
4730
4745
|
}
|
|
4731
4746
|
},
|
|
4747
|
+
{
|
|
4748
|
+
"search": "Pass as the second argument (scoped config) or third (with a configuration key). Same shape as send decorators where applicable:\n",
|
|
4749
|
+
"files": {
|
|
4750
|
+
"docs/_decorators/get.md": {
|
|
4751
|
+
"title": "@get",
|
|
4752
|
+
"hashes": {
|
|
4753
|
+
"options-getoptions": {
|
|
4754
|
+
"count": 1,
|
|
4755
|
+
"title": "Options (`GetOptions`)",
|
|
4756
|
+
"type": "paragraph"
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
},
|
|
4732
4762
|
{
|
|
4733
4763
|
"search": "Same demo service as sonic-queue (/docs-mock-api/geo/). Publisher setup lives in decorators-demo-geo.ts and decorators-demo-subscribe-publish-get-demos.ts.\n",
|
|
4734
4764
|
"files": {
|