@supersoniks/concorde 4.9.9 → 4.9.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/build-infos.json +1 -1
  2. package/concorde-core.bundle.js +241 -241
  3. package/concorde-core.es.js +2908 -2845
  4. package/dist/concorde-core.bundle.js +241 -241
  5. package/dist/concorde-core.es.js +2908 -2845
  6. package/dist/js/core/decorators/api.js +92 -2
  7. package/dist/js/core/decorators/api.js.map +1 -1
  8. package/dist/js/decorators.js +4 -0
  9. package/dist/js/decorators.js.map +1 -1
  10. package/dist/types/core/decorators/api.d.ts +82 -60
  11. package/dist/types/core/decorators/api.d.ts.map +1 -1
  12. package/dist/types/decorators.d.ts +3 -1
  13. package/dist/types/decorators.d.ts.map +1 -1
  14. package/docs/assets/index-BL6WVF7g.css +1 -0
  15. package/docs/assets/{index-hElUgV9_.js → index-DVn8AeyA.js} +572 -555
  16. package/docs/crawl/docs/_decorators/get.html +64 -81
  17. package/docs/crawl/docs/_decorators/patch.html +35 -23
  18. package/docs/crawl/docs/_decorators/post.html +72 -72
  19. package/docs/crawl/docs/_decorators/put.html +36 -21
  20. package/docs/crawl/docs/_getting-started/ai-agents.html +45 -6
  21. package/docs/crawl/hubs/decorators.html +141 -69
  22. package/docs/crawl/hubs/remote-data.html +141 -69
  23. package/docs/index.html +2 -2
  24. package/docs/llms-full.txt +203 -149
  25. package/docs/llms.txt +4 -4
  26. package/docs/src/core/components/functional/queue/queue.md +0 -0
  27. package/docs/src/core/components/ui/button/button.md +0 -0
  28. package/docs/src/docs/_decorators/get.md +55 -81
  29. package/docs/src/docs/_decorators/patch.md +36 -25
  30. package/docs/src/docs/_decorators/post.md +71 -62
  31. package/docs/src/docs/_decorators/put.md +37 -24
  32. package/docs/src/docs/_getting-started/ai-agents.md +24 -4
  33. package/docs/src/docs/search/docs-search.json +151 -1
  34. package/docs/src/tsconfig.json +3 -0
  35. package/package.json +1 -1
  36. package/src/core/decorators/api.post.spec.ts +79 -1
  37. package/src/core/decorators/api.spec.ts +100 -1
  38. package/src/core/decorators/api.ts +252 -138
  39. package/src/decorators.ts +7 -0
  40. package/src/docs/_decorators/get.md +81 -80
  41. package/src/docs/_decorators/patch.md +36 -25
  42. package/src/docs/_decorators/post.md +99 -62
  43. package/src/docs/_decorators/put.md +37 -24
  44. package/src/docs/components/docs-demo-sources.ts +1 -6
  45. package/src/docs/example/decorators-demo-geo.ts +0 -21
  46. package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +41 -132
  47. package/src/docs/search/docs-search.json +128 -133
  48. package/docs/assets/index-Bnp7gVaA.css +0 -1
@@ -4744,6 +4744,51 @@
4744
4744
  }
4745
4745
  }
4746
4746
  },
4747
+ {
4748
+ "search": "The decorator returned by get(...) can be kept as a typed handle. Its refetch(component) action targets only the connected component instance and works even before the first ApiResult exists:\n",
4749
+ "files": {
4750
+ "docs/_decorators/get.md": {
4751
+ "title": "@get",
4752
+ "hashes": {
4753
+ "local-refetch-handle": {
4754
+ "count": 1,
4755
+ "title": "Local refetch handle",
4756
+ "type": "paragraph"
4757
+ }
4758
+ }
4759
+ }
4760
+ }
4761
+ },
4762
+ {
4763
+ "search": "The inline form remains unchanged:\n",
4764
+ "files": {
4765
+ "docs/_decorators/get.md": {
4766
+ "title": "@get",
4767
+ "hashes": {
4768
+ "local-refetch-handle": {
4769
+ "count": 1,
4770
+ "title": "Local refetch handle",
4771
+ "type": "paragraph"
4772
+ }
4773
+ }
4774
+ }
4775
+ }
4776
+ },
4777
+ {
4778
+ "search": "For a refetch triggered from another component, keep using triggerKey and invalidate().\n",
4779
+ "files": {
4780
+ "docs/_decorators/get.md": {
4781
+ "title": "@get",
4782
+ "hashes": {
4783
+ "local-refetch-handle": {
4784
+ "count": 1,
4785
+ "title": "Local refetch handle",
4786
+ "type": "paragraph"
4787
+ }
4788
+ }
4789
+ }
4790
+ }
4791
+ },
4747
4792
  {
4748
4793
  "search": "Pass as the second argument (scoped config) or third (with a configuration key). Same shape as send decorators where applicable:\n",
4749
4794
  "files": {
@@ -4759,6 +4804,51 @@
4759
4804
  }
4760
4805
  }
4761
4806
  },
4807
+ {
4808
+ "search": "Same pattern as @post: a publisher acts as a signal, independent of the response payload.\n",
4809
+ "files": {
4810
+ "docs/_decorators/get.md": {
4811
+ "title": "@get",
4812
+ "hashes": {
4813
+ "manual-refetch-triggerkey": {
4814
+ "count": 1,
4815
+ "title": "Manual refetch (`triggerKey`)",
4816
+ "type": "paragraph"
4817
+ }
4818
+ }
4819
+ }
4820
+ }
4821
+ },
4822
+ {
4823
+ "search": "Prefer triggerKey over mutating the API configuration publisher when you only want to reload the same URL — no need to touch serviceURL, tokens, etc.\n",
4824
+ "files": {
4825
+ "docs/_decorators/get.md": {
4826
+ "title": "@get",
4827
+ "hashes": {
4828
+ "manual-refetch-triggerkey": {
4829
+ "count": 1,
4830
+ "title": "Manual refetch (`triggerKey`)",
4831
+ "type": "paragraph"
4832
+ }
4833
+ }
4834
+ }
4835
+ }
4836
+ },
4837
+ {
4838
+ "search": "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")).\n",
4839
+ "files": {
4840
+ "docs/_decorators/get.md": {
4841
+ "title": "@get",
4842
+ "hashes": {
4843
+ "manual-refetch-triggerkey": {
4844
+ "count": 1,
4845
+ "title": "Manual refetch (`triggerKey`)",
4846
+ "type": "paragraph"
4847
+ }
4848
+ }
4849
+ }
4850
+ }
4851
+ },
4762
4852
  {
4763
4853
  "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",
4764
4854
  "files": {
@@ -4775,7 +4865,22 @@
4775
4865
  }
4776
4866
  },
4777
4867
  {
4778
- "search": "Dynamic config and endpoint path (demo-api-get-configuration-key in doc sources):\n",
4868
+ "search": "@get with triggerKey — refresh button + X-Fetched-At timestamp in the UI:\n",
4869
+ "files": {
4870
+ "docs/_decorators/get.md": {
4871
+ "title": "@get",
4872
+ "hashes": {
4873
+ "live-demos": {
4874
+ "count": 1,
4875
+ "title": "Live demos",
4876
+ "type": "paragraph"
4877
+ }
4878
+ }
4879
+ }
4880
+ }
4881
+ },
4882
+ {
4883
+ "search": "Dynamic config and endpoint path. Manual refetch is triggered from a separate component (same triggerKey publisher):\n",
4779
4884
  "files": {
4780
4885
  "docs/_decorators/get.md": {
4781
4886
  "title": "@get",
@@ -5884,6 +5989,36 @@
5884
5989
  }
5885
5990
  }
5886
5991
  },
5992
+ {
5993
+ "search": "The decorator returned by post(...) can be kept as a typed handle. Its send(component) action re-runs the POST with the current body publisher:\n",
5994
+ "files": {
5995
+ "docs/_decorators/post.md": {
5996
+ "title": "@post",
5997
+ "hashes": {
5998
+ "local-send-handle": {
5999
+ "count": 1,
6000
+ "title": "Local send handle",
6001
+ "type": "paragraph"
6002
+ }
6003
+ }
6004
+ }
6005
+ }
6006
+ },
6007
+ {
6008
+ "search": "send() is an explicit write operation. It is intentionally distinct from GET invalidation because replaying a POST, PUT, or PATCH can have side effects.\n",
6009
+ "files": {
6010
+ "docs/_decorators/post.md": {
6011
+ "title": "@post",
6012
+ "hashes": {
6013
+ "local-send-handle": {
6014
+ "count": 1,
6015
+ "title": "Local send handle",
6016
+ "type": "paragraph"
6017
+ }
6018
+ }
6019
+ }
6020
+ }
6021
+ },
5887
6022
  {
5888
6023
  "search": "Mock service: POST /docs-mock-api/api/register (same route as sonic-submit demos). Publisher setup lives in decorators-demo-post.ts.\n",
5889
6024
  "files": {
@@ -6874,6 +7009,21 @@
6874
7009
  }
6875
7010
  }
6876
7011
  },
7012
+ {
7013
+ "search": "Everything after <!-- concorde-ai --> is replaced on each ai-init run (idempotent). Do not paste a full copy of the Concorde guide above the marker — put project-specific notes in AGENTS.concorde.md (overlay). If the preamble duplicates package AGENTS.md, ai-init slims or drops it and prints a warning. Legacy filenames AGENTS.overlay.md / AGENTS.starter.md are still accepted with a deprecation warning.\n",
7014
+ "files": {
7015
+ "docs/_getting-started/ai-agents.md": {
7016
+ "title": "AI agents (skills & AGENTS.md)",
7017
+ "hashes": {
7018
+ "agentsmd-policy-non-invasive": {
7019
+ "count": 1,
7020
+ "title": "`AGENTS.md` policy (non-invasive)",
7021
+ "type": "paragraph"
7022
+ }
7023
+ }
7024
+ }
7025
+ }
7026
+ },
6877
7027
  {
6878
7028
  "search": "Source files in the npm package: node_modules/@supersoniks/concorde/ai/ (see also ai/README.md in the Concorde repository).\n",
6879
7029
  "files": {
@@ -789,6 +789,9 @@
789
789
  "./utils/PublisherProxy": [
790
790
  "/sites/concorde/src/core/utils/PublisherProxy.ts"
791
791
  ],
792
+ "./utils/SafariScrollGuard": [
793
+ "/sites/concorde/src/core/utils/SafariScrollGuard.ts"
794
+ ],
792
795
  "./utils/Utils": [
793
796
  "/sites/concorde/src/core/utils/Utils.ts"
794
797
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supersoniks/concorde",
3
- "version": "4.9.9",
3
+ "version": "4.9.91",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "",
@@ -5,7 +5,7 @@ import API, {
5
5
  type ApiResult,
6
6
  } from "@supersoniks/concorde/core/utils/api";
7
7
  import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";
8
- import { patch, post, put } from "./api";
8
+ import { patch, post, put, send } from "./api";
9
9
 
10
10
  async function flushSendFrames() {
11
11
  await new Promise<void>((resolve) => {
@@ -68,6 +68,84 @@ describe("post", () => {
68
68
  expect(el.payload?.result).toEqual({ ok: true });
69
69
  });
70
70
 
71
+ it("expose un send local avec le body courant", async () => {
72
+ const bodyPubId = "postSpecLocalSend";
73
+ const pub = PublisherManager.get(bodyPubId);
74
+ pub.set({ value: "first" });
75
+ const bodyKey = new DataProviderKey<{ value: string }>(bodyPubId);
76
+ const syncEp = new Endpoint<{ ok: boolean }>("items/local-send");
77
+ const localPost = post(syncEp, bodyKey);
78
+ const tag = "test-api-post-local-send";
79
+
80
+ class C extends HTMLElement {
81
+ @localPost
82
+ payload: ApiResult<{ ok: boolean }> | null = null;
83
+ }
84
+ if (!customElements.get(tag)) {
85
+ customElements.define(tag, C);
86
+ }
87
+
88
+ const wrap = document.createElement("div");
89
+ wrap.setAttribute("serviceURL", "https://api.example.test");
90
+ const el = document.createElement(tag) as InstanceType<typeof C>;
91
+ wrap.appendChild(el);
92
+ document.body.appendChild(wrap);
93
+
94
+ await flushSendFrames();
95
+ vi.mocked(API.prototype.postDetailed).mockClear();
96
+
97
+ pub.set({ value: "second" });
98
+ localPost.send(el);
99
+ await flushSendFrames();
100
+
101
+ expect(API.prototype.postDetailed).toHaveBeenCalledTimes(1);
102
+ expect(API.prototype.postDetailed).toHaveBeenCalledWith(
103
+ "items/local-send",
104
+ { value: "second" },
105
+ );
106
+ });
107
+
108
+ it("peut décorer une méthode avec send après sa résolution", async () => {
109
+ const bodyPubId = "postSpecMethodSend";
110
+ const pub = PublisherManager.get(bodyPubId);
111
+ pub.set({ value: "first" });
112
+ const bodyKey = new DataProviderKey<{ value: string }>(bodyPubId);
113
+ const syncEp = new Endpoint<{ ok: boolean }>("items/method-send");
114
+ const localPost = post(syncEp, bodyKey);
115
+ const tag = "test-api-post-method-send";
116
+
117
+ class C extends HTMLElement {
118
+ @localPost
119
+ payload: ApiResult<{ ok: boolean }> | null = null;
120
+
121
+ @send(localPost)
122
+ async save() {
123
+ await Promise.resolve();
124
+ pub.set({ value: "after" });
125
+ }
126
+ }
127
+ if (!customElements.get(tag)) {
128
+ customElements.define(tag, C);
129
+ }
130
+
131
+ const wrap = document.createElement("div");
132
+ wrap.setAttribute("serviceURL", "https://api.example.test");
133
+ const el = document.createElement(tag) as InstanceType<typeof C>;
134
+ wrap.appendChild(el);
135
+ document.body.appendChild(wrap);
136
+
137
+ await flushSendFrames();
138
+ vi.mocked(API.prototype.postDetailed).mockClear();
139
+
140
+ await el.save();
141
+ await flushSendFrames();
142
+
143
+ expect(API.prototype.postDetailed).toHaveBeenCalledWith(
144
+ "items/method-send",
145
+ { value: "after" },
146
+ );
147
+ });
148
+
71
149
  it("endpoint dynamique et relance quand le body publisher mute", async () => {
72
150
  const bodyPubId = "postSpecBody2";
73
151
  const pub = PublisherManager.get(bodyPubId);
@@ -6,7 +6,7 @@ import API, {
6
6
  type ApiResult,
7
7
  } from "@supersoniks/concorde/core/utils/api";
8
8
  import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";
9
- import { get } from "./api";
9
+ import { get, refetch } from "./api";
10
10
 
11
11
  const staticConfig: APIConfiguration = {
12
12
  serviceURL: "https://api.example.test",
@@ -190,6 +190,105 @@ describe("get", () => {
190
190
  expect(el.payload?.result).toEqual({ id: "loaded" });
191
191
  });
192
192
 
193
+ it("expose un refetch local avant et après le premier payload", async () => {
194
+ const pubId = "getSpecLocalRefetch";
195
+ PublisherManager.get(pubId).set(staticConfig);
196
+ const confKey = new DataProviderKey<APIConfiguration>(pubId);
197
+ const itemEp = new Endpoint<{ id: string }>("items/local");
198
+ const localGet = get(itemEp, confKey);
199
+ const tag = "test-api-get-local-refetch";
200
+
201
+ class C extends HTMLElement {
202
+ @localGet
203
+ payload: ApiResult<{ id: string }> | null = null;
204
+ }
205
+ if (!customElements.get(tag)) {
206
+ customElements.define(tag, C);
207
+ }
208
+
209
+ const el = document.createElement(tag) as InstanceType<typeof C>;
210
+ document.body.appendChild(el);
211
+
212
+ localGet.refetch(el);
213
+ await flushGetFrames();
214
+ expect(API.prototype.getDetailed).toHaveBeenCalledTimes(1);
215
+
216
+ vi.mocked(API.prototype.getDetailed).mockClear();
217
+ el.payload = mockPayload({ id: "manual" });
218
+ localGet.refetch(el);
219
+ await flushGetFrames();
220
+
221
+ expect(API.prototype.getDetailed).toHaveBeenCalledTimes(1);
222
+ expect(el.payload?.result).toEqual({ id: "loaded" });
223
+ });
224
+
225
+ it("ne refait plus de GET avec un handle local après déconnexion", async () => {
226
+ const pubId = "getSpecLocalRefetchDisconnect";
227
+ PublisherManager.get(pubId).set(staticConfig);
228
+ const confKey = new DataProviderKey<APIConfiguration>(pubId);
229
+ const localGet = get(
230
+ new Endpoint<{ id: string }>("items/local-disconnect"),
231
+ confKey,
232
+ );
233
+ const tag = "test-api-get-local-refetch-disconnect";
234
+
235
+ class C extends HTMLElement {
236
+ @localGet
237
+ payload: ApiResult<{ id: string }> | null = null;
238
+ }
239
+ if (!customElements.get(tag)) {
240
+ customElements.define(tag, C);
241
+ }
242
+
243
+ const el = document.createElement(tag) as InstanceType<typeof C>;
244
+ document.body.appendChild(el);
245
+ await flushGetFrames();
246
+ vi.mocked(API.prototype.getDetailed).mockClear();
247
+
248
+ el.remove();
249
+ localGet.refetch(el);
250
+ await flushGetFrames();
251
+
252
+ expect(API.prototype.getDetailed).not.toHaveBeenCalled();
253
+ });
254
+
255
+ it("peut décorer une méthode et refetcher après sa résolution", async () => {
256
+ const pubId = "getSpecMethodRefetch";
257
+ PublisherManager.get(pubId).set(staticConfig);
258
+ const confKey = new DataProviderKey<APIConfiguration>(pubId);
259
+ const rowEp = new Endpoint<{ id: string }, { rowId: string }>(
260
+ "rows/${rowId}",
261
+ );
262
+ const localGet = get(rowEp, confKey);
263
+ const tag = "test-api-get-method-refetch";
264
+
265
+ class C extends HTMLElement {
266
+ rowId = "42";
267
+
268
+ @localGet
269
+ payload: ApiResult<{ id: string }> | null = null;
270
+
271
+ @refetch(localGet)
272
+ async reload() {
273
+ await Promise.resolve();
274
+ this.rowId = "84";
275
+ }
276
+ }
277
+ if (!customElements.get(tag)) {
278
+ customElements.define(tag, C);
279
+ }
280
+
281
+ const el = document.createElement(tag) as InstanceType<typeof C>;
282
+ document.body.appendChild(el);
283
+ await flushGetFrames();
284
+ vi.mocked(API.prototype.getDetailed).mockClear();
285
+
286
+ await el.reload();
287
+ await flushGetFrames();
288
+
289
+ expect(API.prototype.getDetailed).toHaveBeenCalledWith("rows/84");
290
+ });
291
+
193
292
  it("refetch automatique avec refetchEveryMs", async () => {
194
293
  vi.useFakeTimers();
195
294
  const itemEp = new Endpoint<{ id: string }>("items/1");