@supersoniks/concorde 4.2.1 → 4.4.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 (75) hide show
  1. package/README.md +163 -0
  2. package/build-infos.json +1 -1
  3. package/concorde-core.bundle.js +585 -670
  4. package/concorde-core.es.js +7165 -9505
  5. package/dist/concorde-core.bundle.js +585 -670
  6. package/dist/concorde-core.es.js +7165 -9505
  7. package/docs/assets/index-DP1oMukw.js +4949 -0
  8. package/docs/assets/index-DZtxIZCW.css +1 -0
  9. package/docs/index.html +2 -2
  10. package/{src/docs/_misc → docs/src/docs/_decorators}/ancestor-attribute.md +15 -31
  11. package/docs/src/docs/_decorators/bind.md +164 -0
  12. package/docs/src/docs/_decorators/get.md +65 -0
  13. package/docs/src/docs/_decorators/publish.md +54 -0
  14. package/docs/src/docs/_decorators/subscribe.md +36 -0
  15. package/docs/src/docs/_misc/dataProviderKey.md +135 -0
  16. package/docs/src/docs/_misc/endpoint.md +42 -0
  17. package/docs/src/docs/search/docs-search.json +850 -710
  18. package/docs/src/tsconfig.json +43 -4
  19. package/package.json +25 -4
  20. package/php/get-challenge.php +34 -0
  21. package/php/some-service.php +42 -0
  22. package/scripts/pre-build.mjs +4 -0
  23. package/src/core/_types/endpoint.ts +4 -0
  24. package/src/core/_types/key.ts +1 -0
  25. package/src/core/components/functional/example/example.ts +38 -6
  26. package/src/core/decorators/Subscriber.ts +2 -0
  27. package/src/core/decorators/api.spec.ts +150 -0
  28. package/src/core/decorators/api.ts +244 -0
  29. package/src/core/decorators/subscriber/bind.ts +57 -145
  30. package/src/core/decorators/subscriber/dynamicPath.ts +77 -0
  31. package/src/core/decorators/subscriber/dynamicPropertyWatch.ts +105 -0
  32. package/src/core/decorators/subscriber/onAssign.ts +11 -147
  33. package/src/core/decorators/subscriber/publish.spec.ts +21 -0
  34. package/src/core/decorators/subscriber/publish.ts +148 -0
  35. package/src/core/decorators/subscriber/publisherPath.ts +13 -0
  36. package/src/core/decorators/subscriber/subscribe.spec.ts +21 -0
  37. package/src/core/decorators/subscriber/subscribe.ts +32 -0
  38. package/src/core/decorators/subscriber/subscribe.type-test.ts +32 -0
  39. package/src/core/utils/api.ts +83 -15
  40. package/src/core/utils/dataProviderKey.spec.ts +34 -0
  41. package/src/core/utils/dataProviderKey.ts +86 -0
  42. package/src/core/utils/endpoint.spec.ts +41 -0
  43. package/src/core/utils/endpoint.ts +87 -0
  44. package/src/decorators.ts +14 -0
  45. package/{docs/src/docs/_misc → src/docs/_decorators}/ancestor-attribute.md +15 -31
  46. package/src/docs/_decorators/bind.md +164 -0
  47. package/src/docs/_decorators/get.md +65 -0
  48. package/src/docs/_decorators/publish.md +54 -0
  49. package/src/docs/_decorators/subscribe.md +36 -0
  50. package/src/docs/_misc/dataProviderKey.md +135 -0
  51. package/src/docs/_misc/endpoint.md +42 -0
  52. package/src/docs/example/decorators-demo-bind-demos.ts +210 -0
  53. package/src/docs/example/decorators-demo-geo.ts +45 -0
  54. package/src/docs/example/decorators-demo-init.ts +228 -0
  55. package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +324 -0
  56. package/src/docs/example/decorators-demo.ts +12 -459
  57. package/src/docs/navigation/navigation.ts +27 -10
  58. package/src/docs/search/docs-search.json +1059 -609
  59. package/src/tsconfig-model.json +1 -1
  60. package/src/tsconfig.json +65 -1
  61. package/src/tsconfig.tsbuildinfo +1 -1
  62. package/src/utils.ts +8 -1
  63. package/vite/config.js +25 -6
  64. package/vite.config.mts +13 -0
  65. package/docs/assets/index-B0IJ9I_B.js +0 -4918
  66. package/docs/assets/index-B3QHEJTV.css +0 -1
  67. package/docs/src/docs/_misc/bind.md +0 -436
  68. package/docs/src/docs/_misc/key.md +0 -135
  69. package/src/docs/_misc/bind.md +0 -362
  70. /package/docs/src/docs/{_misc → _decorators}/auto-subscribe.md +0 -0
  71. /package/docs/src/docs/{_misc → _decorators}/on-assign.md +0 -0
  72. /package/docs/src/docs/{_misc → _decorators}/wait-for-ancestors.md +0 -0
  73. /package/src/docs/{_misc → _decorators}/auto-subscribe.md +0 -0
  74. /package/src/docs/{_misc → _decorators}/on-assign.md +0 -0
  75. /package/src/docs/{_misc → _decorators}/wait-for-ancestors.md +0 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Type-safe navigation through composite data structures (publisher paths).
3
+ * Each property or index access extends the path. Retrieve the final path via toString() or path.
4
+ * Supports dynamic paths: use placeholders like "users.${userIndex}" in the constructor.
5
+ *
6
+ * @example
7
+ * const myKey = new DataProviderKey<Data>("data").items[0];
8
+ * myKey.toString(); // "data.items.0"
9
+ * myKey.path; // same
10
+ *
11
+ * @example
12
+ * // U = dépendances dynamiques sur l’hôte (voir DataProviderKeyHost) — propagé sur .foo.bar
13
+ * new DataProviderKey<User, { userIndex: number }>("demoUsers.${userIndex}");
14
+ */
15
+
16
+ type IsAny<T> = 0 extends 1 & T ? true : false;
17
+
18
+ /**
19
+ * Prototype de classe décorée : propriétés minimales attendues sur l’hôte quand la clé est
20
+ * `DataProviderKey<…, U>` (U renseigné à la construction). Avec `U` par défaut (`any`), pas de contrainte.
21
+ */
22
+ export type DataProviderKeyHost<U> = IsAny<U> extends true
23
+ ? object
24
+ : keyof U extends never
25
+ ? object
26
+ : object & U;
27
+
28
+ /**
29
+ * U : forme minimale du composant pour résoudre les placeholders `${…}` du path ; inchangée lors de la navigation.
30
+ */
31
+ type DataProviderKeyProxy<T, U = any> = T extends object
32
+ ? {
33
+ [K in keyof T as T[K] extends (...args: unknown[]) => unknown
34
+ ? never
35
+ : K]: DataProviderKey<T[K], U>;
36
+ }
37
+ : object;
38
+
39
+ export type DataProviderKey<T, U = any> = DataProviderKeyImpl<T, U> &
40
+ DataProviderKeyProxy<T, U>;
41
+
42
+ class DataProviderKeyImpl<T, U = any> {
43
+ declare readonly _phantom?: T;
44
+ declare readonly _phantomDeps?: U;
45
+
46
+ constructor(public readonly path: string) {}
47
+
48
+ toString(): string {
49
+ return this.path;
50
+ }
51
+ }
52
+
53
+ function createDataProviderKeyProxy<T, U = any>(
54
+ key: DataProviderKeyImpl<T, U>,
55
+ ): DataProviderKey<T, U> {
56
+ return new Proxy(key, {
57
+ get(target, prop: string | symbol) {
58
+ if (prop === "path") return target.path;
59
+ if (prop === "toString") return target.toString.bind(target);
60
+ if (prop === Symbol.toStringTag) return "DataProviderKey";
61
+ if (typeof prop === "symbol")
62
+ return (target as unknown as Record<symbol, unknown>)[prop];
63
+ const newPath = target.path
64
+ ? `${target.path}.${String(prop)}`
65
+ : String(prop);
66
+ return createDataProviderKeyProxy(
67
+ new DataProviderKeyImpl<unknown, U>(newPath),
68
+ );
69
+ },
70
+ }) as DataProviderKey<T, U>;
71
+ }
72
+
73
+ export interface DataProviderKeyConstructor {
74
+ new <T, U = any>(path: string): DataProviderKey<T, U>;
75
+ }
76
+
77
+ /* eslint-disable @typescript-eslint/no-explicit-any */
78
+ export const DataProviderKey: DataProviderKeyConstructor = function (
79
+ this: any,
80
+ path: string,
81
+ ): DataProviderKey<unknown, any> {
82
+ if (!(this instanceof DataProviderKey)) {
83
+ return new (DataProviderKey as DataProviderKeyConstructor)(path);
84
+ }
85
+ return createDataProviderKeyProxy(new DataProviderKeyImpl(path));
86
+ } as any;
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { Endpoint } from "./endpoint";
3
+
4
+ describe("Endpoint", () => {
5
+ it("expose le path normalisé (trim)", () => {
6
+ const e = new Endpoint<{ x: number }>(" communes?limit=1 ");
7
+ expect(e.path).toBe("communes?limit=1");
8
+ expect(e.toString()).toBe("communes?limit=1");
9
+ });
10
+
11
+ it("refuse le path vide", () => {
12
+ expect(() => new Endpoint("")).toThrow(RangeError);
13
+ expect(() => new Endpoint(" ")).toThrow(RangeError);
14
+ });
15
+
16
+ it("normalizePath et isNonEmpty", () => {
17
+ expect(Endpoint.normalizePath("a")).toBe("a");
18
+ expect(Endpoint.isNonEmpty(" x ")).toBe(true);
19
+ expect(Endpoint.isNonEmpty("")).toBe(false);
20
+ });
21
+
22
+ it("normalizePath enlève le slash initial et les doubles slash (relatif)", () => {
23
+ expect(Endpoint.normalizePath("/users/1")).toBe("users/1");
24
+ expect(Endpoint.normalizePath("//users//1")).toBe("users/1");
25
+ expect(Endpoint.normalizePath("v1//users//x?limit=1")).toBe(
26
+ "v1/users/x?limit=1",
27
+ );
28
+ });
29
+
30
+ it("normalizePath pour URL absolue : pathname sans doubles slash", () => {
31
+ expect(
32
+ Endpoint.normalizePath("https://example.com/api//v1//x"),
33
+ ).toBe("https://example.com/api/v1/x");
34
+ });
35
+
36
+ it("looksLikeDataProviderPath", () => {
37
+ expect(Endpoint.looksLikeDataProviderPath("dataProvider(foo)")).toBe(true);
38
+ expect(Endpoint.looksLikeDataProviderPath(" DataProvider(x)")).toBe(true);
39
+ expect(Endpoint.looksLikeDataProviderPath("users/1")).toBe(false);
40
+ });
41
+ });
@@ -0,0 +1,87 @@
1
+ import { ApiGetResult } from "./api";
2
+ import { DataProviderKey } from "./dataProviderKey";
3
+
4
+ /**
5
+ * Représente un chemin d’endpoint HTTP (ou path accepté par `API.get`),
6
+ * typé par la forme de la réponse attendue `T`.
7
+ *
8
+ * **`U` (optionnel, défaut `any`)** : propriétés minimales sur le composant pour résoudre les
9
+ * segments dynamiques du path (`${…}` / `{$…}`), comme pour `DataProviderKey<T, U>`. Utilisé par `@get`.
10
+ *
11
+ * Contrairement à `DataProviderKey`, il n’y a **pas** de navigation par propriétés
12
+ * (pas de dot-syntax) : le path est une seule chaîne.
13
+ *
14
+ * @example
15
+ * new Endpoint<User, { userId: string }>("users/${userId}");
16
+ */
17
+ export class Endpoint<T, U = any> {
18
+ declare readonly _phantom?: T;
19
+ declare readonly _phantomHost?: U;
20
+
21
+ readonly path: string;
22
+
23
+ constructor(path: string) {
24
+ this.path = Endpoint.normalizePath(path);
25
+ }
26
+
27
+ /** Même path qu’`Endpoint` ; le 2ᵉ générique `U` est propagé sur la clé publisher. */
28
+ getDataProviderKey(): DataProviderKey<ApiGetResult<T>, U> {
29
+ return new DataProviderKey<ApiGetResult<T>, U>(this.path);
30
+ }
31
+
32
+ /**
33
+ * Trim, refuse le path vide, enlève les `/` en tête (path relatif au `serviceURL`),
34
+ * et fusionne les `/` successifs en un seul.
35
+ * Pour une URL absolue (`http://` / `https://`), normalise surtout le pathname (pas de `//` redondants).
36
+ */
37
+ static normalizePath(path: string): string {
38
+ const t = String(path).trim();
39
+ if (!t) {
40
+ throw new RangeError("Endpoint: path cannot be empty");
41
+ }
42
+
43
+ if (/^https?:\/\//i.test(t)) {
44
+ let u: URL;
45
+ try {
46
+ u = new URL(t);
47
+ } catch {
48
+ throw new RangeError("Endpoint: invalid absolute URL");
49
+ }
50
+ u.pathname = u.pathname.replace(/\/+/g, "/");
51
+ return u.href;
52
+ }
53
+
54
+ const q = t.indexOf("?");
55
+ const hash = t.indexOf("#");
56
+ let end = t.length;
57
+ if (q >= 0) end = Math.min(end, q);
58
+ if (hash >= 0) end = Math.min(end, hash);
59
+ let base = t.slice(0, end);
60
+ const tail = t.slice(end);
61
+
62
+ base = base.replace(/^\/+/, "");
63
+ base = base.replace(/\/+/g, "/");
64
+
65
+ if (!base && tail) {
66
+ throw new RangeError("Endpoint: path cannot be empty");
67
+ }
68
+
69
+ return base + tail;
70
+ }
71
+
72
+ /** Utile avant construction dynamique. */
73
+ static isNonEmpty(path: string): boolean {
74
+ return String(path).trim().length > 0;
75
+ }
76
+
77
+ /**
78
+ * Indique un path local du type `dataProvider(id)…` reconnu par `API.get`.
79
+ */
80
+ static looksLikeDataProviderPath(path: string): boolean {
81
+ return /^\s*dataProvider\s*\(/i.test(String(path));
82
+ }
83
+
84
+ toString(): string {
85
+ return this.path;
86
+ }
87
+ }
package/src/decorators.ts CHANGED
@@ -1,11 +1,22 @@
1
1
  import * as mySubscriber from "@supersoniks/concorde/core/decorators/Subscriber";
2
+ import * as api from "@supersoniks/concorde/core/decorators/api";
3
+ import { Endpoint } from "./core/utils/endpoint";
2
4
  import * as lifecycle from "@supersoniks/concorde/core/decorators/lifecycle";
3
5
 
4
6
  export const bind = mySubscriber.bind;
7
+ export const publish = mySubscriber.publish;
8
+ export const subscribe = mySubscriber.subscribe;
5
9
  export const onAssign = mySubscriber.onAssign;
6
10
  export const ancestorAttribute = mySubscriber.ancestorAttribute;
7
11
  export const autoSubscribe = mySubscriber.autoSubscribe;
8
12
  export const autoFill = mySubscriber.autoFill;
13
+ export const get = api.get;
14
+ export {
15
+ DataProviderKey,
16
+ type DataProviderKeyHost,
17
+ } from "./core/utils/dataProviderKey";
18
+ export { Endpoint };
19
+ export type { ApiGetResult } from "./core/utils/api";
9
20
  export const awaitConnectedAncestors = lifecycle.awaitConnectedAncestors;
10
21
  export const dispatchConnectedEvent = lifecycle.dispatchConnectedEvent;
11
22
  export const CONNECTED = lifecycle.CONNECTED;
@@ -17,8 +28,11 @@ window["concorde-decorator-subscriber"] =
17
28
  window["concorde-decorator-subscriber"] || {};
18
29
  window["concorde-decorator-subscriber"] = {
19
30
  bind: mySubscriber.bind,
31
+ publish: mySubscriber.publish,
32
+ subscribe: mySubscriber.subscribe,
20
33
  onAssing: mySubscriber.onAssign,
21
34
  ancestorAttribute: mySubscriber.ancestorAttribute,
22
35
  autoSubscribe: mySubscriber.autoSubscribe,
23
36
  autoFill: mySubscriber.autoFill,
37
+ get: api.get,
24
38
  };
@@ -17,45 +17,29 @@ import { ancestorAttribute } from "@supersoniks/concorde/decorators";
17
17
  </sonic-code>
18
18
 
19
19
  ### Basic example
20
- Le composant lit les attributs `dataProvider` et `testAttribute` exposés par son conteneur ancêtre.
20
+
21
+ The component reads `dataProvider` and `testAttribute` from its ancestor wrapper.
21
22
 
22
23
  <sonic-code language="typescript">
23
24
  <template>
24
- //...
25
- @customElement("demo-bind-reflect")
26
- export class DemoBindReflect extends LitElement {
27
- static styles = [tailwind];
25
+ import { html, LitElement } from "lit";
26
+ import { customElement } from "lit/decorators.js";
27
+ import { ancestorAttribute } from "@supersoniks/concorde/decorators";
28
28
  //
29
- @bind("bindReflectDemo.count", { reflect: true })
30
- @state()
31
- withReflect: number = 0;
29
+ @customElement("demo-ancestor-attribute")
30
+ export class DemoAncestorAttribute extends LitElement {
31
+ @ancestorAttribute("dataProvider")
32
+ dataProvider: string | null = null;
32
33
  //
33
- @bind("bindReflectDemo.count")
34
- @state()
35
- withoutReflect: number = 0;
36
- // initialize the publisher data
37
- connectedCallback() {
38
- super.connectedCallback();
39
- this.resetData();
40
- }
34
+ @ancestorAttribute("testAttribute")
35
+ testAttribute: string | null = null;
41
36
  //
42
- resetData() {
43
- PublisherManager.get("bindReflectDemo").set({ count: 0 });
44
- }
45
37
  render() {
46
38
  return html`
47
- <div class="mb-3">
48
- from publisher : ${sub("bindReflectDemo.count")} <br />
49
- from component with reflect : ${this.withReflect} <br />
50
- from component without reflect : ${this.withoutReflect}
51
- </div>
52
- <sonic-button @click=${() => this.withReflect++}
53
- >Increment with reflect</sonic-button
54
- >
55
- <sonic-button @click=${() => this.withoutReflect++}
56
- >Increment without reflect</sonic-button
57
- >
58
- <sonic-button @click=${this.resetData}>Reset publisher data</sonic-button>
39
+ <section>
40
+ <p>dataProvider: <strong>${this.dataProvider || "null"}</strong></p>
41
+ <p>testAttribute: <strong>${this.testAttribute || "null"}</strong></p>
42
+ </section>
59
43
  `;
60
44
  }
61
45
  }
@@ -0,0 +1,164 @@
1
+ # @bind
2
+
3
+ Binds a class property to a path in a publisher. The property updates when publisher data changes.
4
+
5
+ For Lit re-renders, also add `@state()` on the same property.
6
+
7
+ **See also:** [@subscribe](#docs/_decorators/subscribe.md/subscribe), [@publish](#docs/_decorators/publish.md/publish), [@get](#docs/_decorators/get.md/get).
8
+
9
+ ## Principle
10
+
11
+ The decorator subscribes via `PublisherManager` using dot notation. Publisher updates flow into the decorated property.
12
+
13
+ ## Import
14
+
15
+ <sonic-code language="typescript">
16
+ <template>
17
+ import { bind } from "@supersoniks/concorde/decorators";
18
+ </template>
19
+ </sonic-code>
20
+
21
+ ## Example
22
+
23
+ <sonic-code language="typescript">
24
+ <template>
25
+ @customElement("demo-bind")
26
+ export class DemoBind extends LitElement {
27
+ static styles = [tailwind];
28
+ //
29
+ @bind("demoData.firstName")
30
+ @state()
31
+ firstName = "";
32
+ //
33
+ @bind("demoData.lastName")
34
+ @state()
35
+ lastName: string = "";
36
+ //
37
+ @bind("demoData.count")
38
+ @state()
39
+ count: number = 0;
40
+ //
41
+ render() {
42
+ return //......
43
+ }
44
+ //
45
+ updateData() {
46
+ const demoData = PublisherManager.get("demoData");
47
+ const demoUsers = PublisherManager.get("demoUsers");
48
+ const randomIndex = Math.floor(Math.random() * demoUsers.get().length);
49
+ const randomUser = demoUsers.get()[randomIndex];
50
+ demoData.set({
51
+ firstName: randomUser.firstName,
52
+ lastName: randomUser.lastName,
53
+ count: (demoData.count.get() || 0) + 1,
54
+ });
55
+ }
56
+ }
57
+ //
58
+ </template>
59
+ </sonic-code>
60
+
61
+ <sonic-code>
62
+ <template>
63
+ <demo-bind></demo-bind>
64
+ </template>
65
+ </sonic-code>
66
+
67
+ ## `DataProviderKey` (strict typing)
68
+
69
+ `@bind` accepts either a string path (legacy) or a `DataProviderKey<T>`. The property type must match `T`. Use `reflect: true` to push local writes back to the publisher (see below). See [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey).
70
+
71
+ <sonic-code language="typescript">
72
+ <template>
73
+ import { bind } from "@supersoniks/concorde/decorators";
74
+ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
75
+ //
76
+ type Data = { count: number };
77
+ const dataKey = new DataProviderKey<Data>("data");
78
+ //
79
+ @bind(dataKey.count, { reflect: true })
80
+ @state()
81
+ count: number = 0;
82
+ </template>
83
+ </sonic-code>
84
+
85
+ ## Reflect (`reflect: true`)
86
+
87
+ Two-way sync: reads from the publisher and local assignments call `publisher.set(...)`. An internal guard avoids infinite loops.
88
+
89
+ <sonic-code language="typescript">
90
+ <template>
91
+ @bind("userData.profile.avatarUrl", { reflect: true })
92
+ @state()
93
+ avatar: string;
94
+ </template>
95
+ </sonic-code>
96
+
97
+ <sonic-code language="typescript">
98
+ <template>
99
+ @customElement("demo-bind-reflect")
100
+ export class DemoBindReflect extends LitElement {
101
+ static styles = [tailwind];
102
+ //
103
+ @bind("bindReflectDemo.count", { reflect: true })
104
+ @state()
105
+ withReflect: number = 0;
106
+ //
107
+ @bind("bindReflectDemo.count")
108
+ @state()
109
+ withoutReflect: number = 0;
110
+ //
111
+ render() {
112
+ return html`
113
+ <div class="mb-3">
114
+ from publisher : ${sub("bindReflectDemo.count") || 0} <br />
115
+ from component with reflect : ${this.withReflect || 0} <br />
116
+ from component without reflect : ${this.withoutReflect || 0}
117
+ </div>
118
+ <sonic-button @click=${() => this.withReflect++}
119
+ >Increment with reflect</sonic-button
120
+ >
121
+ <sonic-button @click=${() => this.withoutReflect++}
122
+ >Increment without reflect</sonic-button
123
+ >
124
+ `;
125
+ }
126
+ }
127
+ //
128
+ </template>
129
+ </sonic-code>
130
+
131
+ <sonic-code toggleCode>
132
+ <template>
133
+ <demo-bind-reflect></demo-bind-reflect>
134
+ </template>
135
+ </sonic-code>
136
+
137
+ ## Path syntax
138
+
139
+ - First segment: data provider id.
140
+ - Nested properties: dot notation.
141
+ - Arrays: numeric index (`items.0`).
142
+
143
+ ### Dynamic paths
144
+
145
+ Use `${prop}` or `${this.prop}` inside a **normal string literal** (not a JS template literal with backticks). `@bind` re-subscribes when a reactive dependency changes.
146
+
147
+ > Properties referenced in the pattern must be reactive (`@property`, etc.) or you must call `requestUpdate` manually.
148
+
149
+ <sonic-code>
150
+ <template>
151
+ <demo-bind-dynamic></demo-bind-dynamic>
152
+ </template>
153
+ </sonic-code>
154
+
155
+ ## Behavior
156
+
157
+ - Subscribes at `connectedCallback`, unsubscribes at `disconnectedCallback`.
158
+ - If the path does not exist yet, a publisher may be created with `null`.
159
+
160
+ ## Notes
161
+
162
+ Works with any component that has the usual DOM lifecycle (`LitElement`, `Subscriber` mixin, etc.).
163
+
164
+ Shared data: [Sharing data](#docs/_getting-started/pubsub.md/pubsub).
@@ -0,0 +1,65 @@
1
+ # @get
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`.
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`.
6
+
7
+ ## Configuration
8
+
9
+ - **Default:** `HTML.getApiConfiguration(host)` (ancestor `serviceURL`, etc.).
10
+ - **Second argument:** `DataProviderKey<APIConfiguration>` — config is read from the publisher at the resolved path; internal mutations trigger another GET.
11
+
12
+ ## When the GET runs again
13
+
14
+ - A referenced Lit property changes (endpoint path and/or config key contains `${...}`).
15
+ - `set` on the active configuration publisher (`onInternalMutation`).
16
+
17
+ ## Import
18
+
19
+ <sonic-code language="typescript">
20
+ <template>
21
+ import { get, type ApiGetResult } from "@supersoniks/concorde/decorators";
22
+ import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
23
+ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
24
+ </template>
25
+ </sonic-code>
26
+
27
+ ## Minimal example
28
+
29
+ Same demo service as [`sonic-queue`](../../core/components/functional/queue/queue.demo.ts) (`geo.api.gouv.fr`). Publisher setup lives in `decorators-demo-geo.ts` and `decorators-demo-subscribe-publish-get-demos.ts`.
30
+
31
+ <sonic-code language="typescript">
32
+ <template>
33
+ @get(new Endpoint<User>("users/${userId}"))
34
+ @state()
35
+ payload: ApiGetResult<User> | null = null;
36
+ </template>
37
+ </sonic-code>
38
+
39
+ ## Live demos
40
+
41
+ <sonic-code>
42
+ <template>
43
+ <demo-api-get></demo-api-get>
44
+ </template>
45
+ </sonic-code>
46
+
47
+ Dynamic config and endpoint path (`demo-api-get-configuration-key` in doc sources):
48
+
49
+ <sonic-code>
50
+ <template>
51
+ <demo-api-get-configuration-key></demo-api-get-configuration-key>
52
+ </template>
53
+ </sonic-code>
54
+
55
+ Scoped `@get` with `@publish` / `@subscribe` on the payload (see [@publish](#docs/_decorators/publish.md/publish) and [@subscribe](#docs/_decorators/subscribe.md/subscribe)) — wrap under an ancestor with `serviceURL="https://geo.api.gouv.fr/"`:
56
+
57
+ <sonic-code>
58
+ <template>
59
+ <div serviceURL="https://geo.api.gouv.fr/">
60
+ <demo-api-get-publish-subscribe></demo-api-get-publish-subscribe>
61
+ </div>
62
+ </template>
63
+ </sonic-code>
64
+
65
+ Stale responses are ignored if the path or generation changed before the request finished.
@@ -0,0 +1,54 @@
1
+ # @publish
2
+
3
+ Write-only binding: assigning to the property publishes to the `DataProviderKey` path. No read subscription (inverse of [@subscribe](#docs/_decorators/subscribe.md/subscribe)).
4
+
5
+ Similar to the “reflect” half of [@bind](#docs/_decorators/bind.md/bind) without listening to the publisher.
6
+
7
+ ## Import
8
+
9
+ <sonic-code language="typescript">
10
+ <template>
11
+ import { publish } from "@supersoniks/concorde/decorators";
12
+ import { sub } from "@supersoniks/concorde/directives";
13
+ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
14
+ </template>
15
+ </sonic-code>
16
+
17
+ ## Example
18
+
19
+ <sonic-code language="typescript">
20
+ <template>
21
+ type PublishDemoData = { email: string; message: string };
22
+ const publishDemoKey = new DataProviderKey<PublishDemoData>("publishDemo");
23
+ //
24
+ @customElement("demo-publish")
25
+ export class DemoPublish extends LitElement {
26
+ @publish(publishDemoKey.email)
27
+ @state()
28
+ email = "";
29
+ //
30
+ @publish(publishDemoKey.message)
31
+ @state()
32
+ message = "";
33
+ //
34
+ render() {
35
+ return html`
36
+ <sonic-input
37
+ .value=${this.email}
38
+ @input=${(e) => (this.email = (e.target as HTMLInputElement).value)}
39
+ label="Email"
40
+ ></sonic-input>
41
+ <p>${sub("publishDemo.email")}</p>
42
+ `;
43
+ }
44
+ }
45
+ </template>
46
+ </sonic-code>
47
+
48
+ <sonic-code>
49
+ <template>
50
+ <demo-publish></demo-publish>
51
+ </template>
52
+ </sonic-code>
53
+
54
+ Dynamic paths use the same placeholder rules as `@bind` / `@subscribe`.
@@ -0,0 +1,36 @@
1
+ # @subscribe
2
+
3
+ Read-only binding: **only** `DataProviderKey<T>` (no legacy string path). No `reflect` option — the publisher updates the property, not the other way around.
4
+
5
+ For bidirectional binding or string paths, use [@bind](#docs/_decorators/bind.md/bind).
6
+
7
+ ## Import
8
+
9
+ <sonic-code language="typescript">
10
+ <template>
11
+ import { subscribe } from "@supersoniks/concorde/decorators";
12
+ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
13
+ //
14
+ type Data = { count: number };
15
+ const dataKey = new DataProviderKey<Data>("data");
16
+ //
17
+ @subscribe(dataKey.count)
18
+ @state()
19
+ count = 0;
20
+ </template>
21
+ </sonic-code>
22
+
23
+ ## Highlights
24
+
25
+ - Strict typing: the property type must match `T`.
26
+ - Dynamic paths: placeholders in `DataProviderKey`, resolved from the host component’s properties.
27
+
28
+ ## Demo
29
+
30
+ <sonic-code>
31
+ <template>
32
+ <demo-subscribe-dynamic></demo-subscribe-dynamic>
33
+ </template>
34
+ </sonic-code>
35
+
36
+ See also [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey).