sygnal 5.4.0 → 6.0.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 (316) hide show
  1. package/CHANGELOG.md +646 -0
  2. package/README.md +24 -9
  3. package/dist/astro/client.cjs.js +16 -9
  4. package/dist/astro/client.cjs.js.map +1 -1
  5. package/dist/astro/client.mjs +16 -9
  6. package/dist/astro/client.mjs.map +1 -1
  7. package/dist/astro/index.cjs.js +362 -91
  8. package/dist/astro/index.cjs.js.map +1 -1
  9. package/dist/astro/index.mjs +362 -92
  10. package/dist/astro/index.mjs.map +1 -1
  11. package/dist/astro/server.cjs.js +3396 -165
  12. package/dist/astro/server.cjs.js.map +1 -1
  13. package/dist/astro/server.mjs +3396 -165
  14. package/dist/astro/server.mjs.map +1 -1
  15. package/dist/devtools.cjs.js +1431 -0
  16. package/dist/devtools.cjs.js.map +1 -0
  17. package/dist/devtools.esm.js +1419 -0
  18. package/dist/devtools.esm.js.map +1 -0
  19. package/dist/diagnostics.cjs.js +3032 -296
  20. package/dist/diagnostics.cjs.js.map +1 -1
  21. package/dist/diagnostics.esm.js +3032 -296
  22. package/dist/diagnostics.esm.js.map +1 -1
  23. package/dist/element.cjs.js +230 -0
  24. package/dist/element.cjs.js.map +1 -0
  25. package/dist/element.esm.js +228 -0
  26. package/dist/element.esm.js.map +1 -0
  27. package/dist/guide/accessibility.md +257 -0
  28. package/dist/guide/adapters.md +221 -0
  29. package/dist/guide/behaviors.md +312 -0
  30. package/dist/guide/browser-sources.md +203 -0
  31. package/dist/guide/drag-and-drop.md +342 -0
  32. package/dist/guide/element-commands.md +306 -0
  33. package/dist/guide/error-boundaries.md +169 -0
  34. package/dist/guide/forms-reference.md +381 -0
  35. package/dist/guide/forms.md +250 -0
  36. package/dist/guide/http.md +299 -0
  37. package/dist/guide/inputs.md +173 -0
  38. package/dist/guide/persistence.md +245 -0
  39. package/dist/guide/recipes/carousel.md +155 -0
  40. package/dist/guide/recipes/charts.md +177 -0
  41. package/dist/guide/recipes/code-editor.md +135 -0
  42. package/dist/guide/recipes/data-grid.md +155 -0
  43. package/dist/guide/recipes/data-table.md +196 -0
  44. package/dist/guide/recipes/i18n.md +222 -0
  45. package/dist/guide/recipes/icons.md +115 -0
  46. package/dist/guide/recipes/overview.md +33 -0
  47. package/dist/guide/recipes/rich-text.md +154 -0
  48. package/dist/guide/resources.md +471 -0
  49. package/dist/guide/ssr.md +255 -0
  50. package/dist/guide/timers.md +178 -0
  51. package/dist/guide/ui/accordion.md +93 -0
  52. package/dist/guide/ui/combobox.md +144 -0
  53. package/dist/guide/ui/dialog.md +124 -0
  54. package/dist/guide/ui/disclosure.md +60 -0
  55. package/dist/guide/ui/menu.md +125 -0
  56. package/dist/guide/ui/overview.md +166 -0
  57. package/dist/guide/ui/popover.md +101 -0
  58. package/dist/guide/ui/select.md +114 -0
  59. package/dist/guide/ui/tabs.md +161 -0
  60. package/dist/guide/ui/toaster.md +152 -0
  61. package/dist/guide/ui/tooltip.md +103 -0
  62. package/dist/guide/undo.md +178 -0
  63. package/dist/guide/virtual-collections.md +183 -0
  64. package/dist/guide/web-components.md +179 -0
  65. package/dist/guide/widgets.md +217 -0
  66. package/dist/index.cjs.js +13178 -5769
  67. package/dist/index.cjs.js.map +1 -1
  68. package/dist/index.d.ts +2908 -276
  69. package/dist/index.esm.js +13149 -5779
  70. package/dist/index.esm.js.map +1 -1
  71. package/dist/jsx-dev-runtime.cjs.js +26 -334
  72. package/dist/jsx-dev-runtime.cjs.js.map +1 -1
  73. package/dist/jsx-dev-runtime.esm.js +19 -327
  74. package/dist/jsx-dev-runtime.esm.js.map +1 -1
  75. package/dist/jsx-runtime.cjs.js +26 -334
  76. package/dist/jsx-runtime.cjs.js.map +1 -1
  77. package/dist/jsx-runtime.esm.js +19 -327
  78. package/dist/jsx-runtime.esm.js.map +1 -1
  79. package/dist/jsx.cjs.js +10 -314
  80. package/dist/jsx.cjs.js.map +1 -1
  81. package/dist/jsx.esm.js +8 -315
  82. package/dist/jsx.esm.js.map +1 -1
  83. package/dist/react.cjs.js +117 -0
  84. package/dist/react.cjs.js.map +1 -0
  85. package/dist/react.esm.js +115 -0
  86. package/dist/react.esm.js.map +1 -0
  87. package/dist/shims/globalthis.cjs +20 -0
  88. package/dist/sygnal.min.js +1 -1
  89. package/dist/sygnal.min.js.map +1 -1
  90. package/dist/ui-combobox.cjs.js +169 -0
  91. package/dist/ui-combobox.cjs.js.map +1 -0
  92. package/dist/ui-combobox.esm.js +148 -0
  93. package/dist/ui-combobox.esm.js.map +1 -0
  94. package/dist/ui-menu.cjs.js +87 -0
  95. package/dist/ui-menu.cjs.js.map +1 -0
  96. package/dist/ui-menu.esm.js +66 -0
  97. package/dist/ui-menu.esm.js.map +1 -0
  98. package/dist/ui-select.cjs.js +104 -0
  99. package/dist/ui-select.cjs.js.map +1 -0
  100. package/dist/ui-select.esm.js +83 -0
  101. package/dist/ui-select.esm.js.map +1 -0
  102. package/dist/ui.cjs.js +738 -0
  103. package/dist/ui.cjs.js.map +1 -0
  104. package/dist/ui.esm.js +727 -0
  105. package/dist/ui.esm.js.map +1 -0
  106. package/dist/vike/ClientOnly.cjs.js +4 -14
  107. package/dist/vike/ClientOnly.cjs.js.map +1 -1
  108. package/dist/vike/ClientOnly.mjs +4 -14
  109. package/dist/vike/ClientOnly.mjs.map +1 -1
  110. package/dist/vike/{+config.js → config/+config.js} +9 -1
  111. package/dist/vike/config/+config.js.map +1 -0
  112. package/dist/vike/config/package.json +6 -0
  113. package/dist/vike/onRenderClient.cjs.js +165 -95
  114. package/dist/vike/onRenderClient.cjs.js.map +1 -1
  115. package/dist/vike/onRenderClient.mjs +165 -95
  116. package/dist/vike/onRenderClient.mjs.map +1 -1
  117. package/dist/vike/onRenderHtml.cjs.js +59 -24
  118. package/dist/vike/onRenderHtml.cjs.js.map +1 -1
  119. package/dist/vike/onRenderHtml.mjs +60 -25
  120. package/dist/vike/onRenderHtml.mjs.map +1 -1
  121. package/dist/vite/plugin.cjs.js +323 -88
  122. package/dist/vite/plugin.cjs.js.map +1 -1
  123. package/dist/vite/plugin.mjs +323 -89
  124. package/dist/vite/plugin.mjs.map +1 -1
  125. package/dist/zag.cjs.js +408 -0
  126. package/dist/zag.cjs.js.map +1 -0
  127. package/dist/zag.esm.js +405 -0
  128. package/dist/zag.esm.js.map +1 -0
  129. package/llms.txt +166 -103
  130. package/package.json +98 -14
  131. package/src/astro/client.ts +27 -16
  132. package/src/astro/index.d.ts +24 -0
  133. package/src/astro/index.ts +69 -3
  134. package/src/astro/server.ts +8 -2
  135. package/src/collection.ts +6 -93
  136. package/src/core/actions.ts +134 -0
  137. package/src/core/cell.ts +202 -0
  138. package/src/core/debug.ts +23 -0
  139. package/src/core/define.ts +242 -0
  140. package/src/core/hooks.ts +314 -0
  141. package/src/core/hosts/collection.ts +325 -0
  142. package/src/core/hosts/switchable.ts +146 -0
  143. package/src/core/instance.ts +592 -0
  144. package/src/core/markers/clientonly.ts +13 -0
  145. package/src/core/markers/lazy.ts +40 -0
  146. package/src/core/markers/portal.ts +96 -0
  147. package/src/core/markers/suspense.ts +41 -0
  148. package/src/core/markers/transition.ts +70 -0
  149. package/src/core/registry.ts +25 -0
  150. package/src/core/runtime.ts +573 -0
  151. package/src/core/statics.ts +104 -0
  152. package/src/core/teardown.ts +60 -0
  153. package/src/core/view.ts +37 -0
  154. package/src/cycle/dom/DocumentDOMSource.ts +13 -8
  155. package/src/cycle/dom/ElementFinder.ts +7 -9
  156. package/src/cycle/dom/EventDelegator.ts +174 -223
  157. package/src/cycle/dom/IsolateModule.ts +65 -35
  158. package/src/cycle/dom/MainDOMSource.ts +9 -2
  159. package/src/cycle/dom/PriorityQueue.ts +4 -2
  160. package/src/cycle/dom/SymbolTree.ts +18 -47
  161. package/src/cycle/dom/classNameModule.ts +9 -7
  162. package/src/cycle/dom/controlledInputModule.ts +23 -6
  163. package/src/cycle/dom/enrichEventStream.ts +25 -47
  164. package/src/cycle/dom/fragment.ts +7 -0
  165. package/src/cycle/dom/isolate.ts +30 -27
  166. package/src/cycle/dom/makeDOMDriver.ts +115 -56
  167. package/src/cycle/dom/mockDOMSource.ts +21 -1
  168. package/src/cycle/dom/modules.ts +16 -3
  169. package/src/cycle/dom/propsModule.ts +65 -0
  170. package/src/cycle/dom/selectModule.ts +22 -17
  171. package/src/cycle/dom/snabbdom.ts +1 -6
  172. package/src/cycle/dom/thunk.ts +3 -0
  173. package/src/cycle/dom/utils.ts +98 -0
  174. package/src/cycle/dom/viewTransition.ts +43 -0
  175. package/src/cycle/state/StateSource.ts +19 -3
  176. package/src/cycle/state/objIsEqual.ts +50 -0
  177. package/src/cycle/state/types.ts +0 -10
  178. package/src/defineComponent.ts +34 -0
  179. package/src/devtools.d.ts +151 -0
  180. package/src/devtools.ts +34 -0
  181. package/src/element.d.ts +59 -0
  182. package/src/element.ts +235 -0
  183. package/src/extra/backoff.ts +9 -0
  184. package/src/extra/behaviors.ts +169 -0
  185. package/src/extra/browserSignals.ts +23 -0
  186. package/src/extra/browserSources.ts +326 -0
  187. package/src/extra/command.ts +3 -3
  188. package/src/extra/controls.ts +47 -0
  189. package/src/extra/copyAsTest.ts +286 -0
  190. package/src/extra/devtools.ts +101 -31
  191. package/src/extra/devtoolsActions.ts +379 -0
  192. package/src/extra/devtoolsHook.ts +9 -0
  193. package/src/extra/devtoolsNext.ts +83 -0
  194. package/src/extra/diagnostics/checks/actionLog.ts +108 -0
  195. package/src/extra/diagnostics/checks/behaviors.ts +49 -0
  196. package/src/extra/diagnostics/checks/browserSources.ts +96 -0
  197. package/src/extra/diagnostics/checks/collections.ts +1 -1
  198. package/src/extra/diagnostics/checks/controls.ts +148 -0
  199. package/src/extra/diagnostics/checks/dataset.ts +57 -0
  200. package/src/extra/diagnostics/checks/dom.ts +20 -11
  201. package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
  202. package/src/extra/diagnostics/checks/events.ts +17 -2
  203. package/src/extra/diagnostics/checks/fetch.ts +135 -0
  204. package/src/extra/diagnostics/checks/forms.ts +163 -0
  205. package/src/extra/diagnostics/checks/index.ts +97 -4
  206. package/src/extra/diagnostics/checks/inspect.ts +140 -32
  207. package/src/extra/diagnostics/checks/next.ts +417 -0
  208. package/src/extra/diagnostics/checks/persist.ts +54 -0
  209. package/src/extra/diagnostics/checks/props.ts +10 -5
  210. package/src/extra/diagnostics/checks/public.d.ts +98 -7
  211. package/src/extra/diagnostics/checks/replies.ts +128 -0
  212. package/src/extra/diagnostics/checks/router.ts +100 -0
  213. package/src/extra/diagnostics/checks/rxjsHints.ts +1 -1
  214. package/src/extra/diagnostics/checks/shared.ts +86 -2
  215. package/src/extra/diagnostics/checks/shorthand.ts +115 -0
  216. package/src/extra/diagnostics/checks/sortable.ts +129 -0
  217. package/src/extra/diagnostics/checks/state.ts +100 -7
  218. package/src/extra/diagnostics/checks/statics.ts +48 -0
  219. package/src/extra/diagnostics/checks/strict.ts +10 -67
  220. package/src/extra/diagnostics/checks/timers.ts +80 -0
  221. package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
  222. package/src/extra/diagnostics/checks/virtual.ts +73 -0
  223. package/src/extra/diagnostics/checks/widgets.ts +109 -0
  224. package/src/extra/diagnostics/checks/wiring.ts +66 -10
  225. package/src/extra/diagnostics/codes.ts +268 -23
  226. package/src/extra/diagnostics/index.ts +34 -9
  227. package/src/extra/diagnostics/legacy.ts +17 -0
  228. package/src/extra/driverFactories.ts +83 -19
  229. package/src/extra/elementCommands.ts +53 -0
  230. package/src/extra/fetchDriver.ts +519 -0
  231. package/src/extra/focusWithin.ts +27 -0
  232. package/src/extra/form.ts +268 -0
  233. package/src/extra/formHelpers.ts +139 -0
  234. package/src/extra/head.ts +106 -0
  235. package/src/extra/hmr.ts +2 -3
  236. package/src/extra/owned.ts +24 -0
  237. package/src/extra/pager.ts +50 -0
  238. package/src/extra/persist.ts +133 -0
  239. package/src/extra/pwa.ts +1 -1
  240. package/src/extra/queryCache.ts +107 -0
  241. package/src/extra/reducers.ts +1 -1
  242. package/src/extra/reduxDevtools.ts +92 -0
  243. package/src/extra/replies.ts +54 -0
  244. package/src/extra/router.ts +323 -0
  245. package/src/extra/run.ts +90 -217
  246. package/src/extra/selection.ts +76 -0
  247. package/src/extra/socketDriver.ts +265 -0
  248. package/src/extra/sortable.ts +377 -0
  249. package/src/extra/ssr.ts +427 -160
  250. package/src/extra/standardSchema.ts +27 -0
  251. package/src/extra/testing.ts +2433 -175
  252. package/src/extra/timers.ts +95 -0
  253. package/src/extra/undo.ts +261 -0
  254. package/src/extra/viewTransitions.ts +38 -0
  255. package/src/extra/virtual.ts +513 -0
  256. package/src/extra/widget.ts +201 -0
  257. package/src/index.d.ts +2631 -117
  258. package/src/index.ts +25 -4
  259. package/src/jsx-runtime.ts +15 -1
  260. package/src/jsx.ts +2 -1
  261. package/src/lazy.ts +50 -7
  262. package/src/portal.ts +3 -1
  263. package/src/pragma/index.ts +256 -133
  264. package/src/react-peers.d.ts +5 -0
  265. package/src/react.d.ts +43 -0
  266. package/src/react.ts +110 -0
  267. package/src/shared.ts +48 -0
  268. package/src/slot.ts +1 -1
  269. package/src/suspense.ts +3 -1
  270. package/src/switchable.ts +6 -121
  271. package/src/transition.ts +3 -1
  272. package/src/ui/accordion.ts +64 -0
  273. package/src/ui/dialog.ts +129 -0
  274. package/src/ui/disclosure.ts +35 -0
  275. package/src/ui/popover.ts +45 -0
  276. package/src/ui/shared.ts +94 -0
  277. package/src/ui/tabs.ts +82 -0
  278. package/src/ui/toaster.ts +198 -0
  279. package/src/ui/tooltip.ts +70 -0
  280. package/src/ui/zag/combobox.ts +115 -0
  281. package/src/ui/zag/menu.ts +40 -0
  282. package/src/ui/zag/select.ts +54 -0
  283. package/src/ui/zag/shared.ts +43 -0
  284. package/src/ui-combobox.d.ts +20 -0
  285. package/src/ui-combobox.ts +6 -0
  286. package/src/ui-menu.d.ts +30 -0
  287. package/src/ui-menu.ts +6 -0
  288. package/src/ui-select.d.ts +17 -0
  289. package/src/ui-select.ts +6 -0
  290. package/src/ui-zag-types.d.ts +42 -0
  291. package/src/ui.d.ts +208 -0
  292. package/src/ui.ts +15 -0
  293. package/src/vike/+config.ts +9 -1
  294. package/src/vike/ClientOnly.ts +1 -1
  295. package/src/vike/onRenderClient.ts +144 -96
  296. package/src/vike/onRenderHtml.ts +69 -26
  297. package/src/vike/types.ts +7 -1
  298. package/src/vite/globalthis-shim.ts +18 -0
  299. package/src/vite/globalthis.ts +56 -0
  300. package/src/vite/plugin.d.ts +22 -3
  301. package/src/vite/plugin.ts +222 -24
  302. package/src/zag.d.ts +64 -0
  303. package/src/zag.ts +238 -0
  304. package/dist/vike/+config.cjs.js +0 -66
  305. package/dist/vike/+config.cjs.js.map +0 -1
  306. package/dist/vike/+config.js.map +0 -1
  307. package/src/component.ts +0 -2301
  308. package/src/cycle/isolate/index.ts +0 -196
  309. package/src/cycle/run/index.ts +0 -151
  310. package/src/cycle/run/internals.ts +0 -143
  311. package/src/cycle/state/Collection.ts +0 -184
  312. package/src/cycle/state/index.ts +0 -5
  313. package/src/cycle/state/pickCombine.ts +0 -201
  314. package/src/cycle/state/pickMerge.ts +0 -122
  315. package/src/cycle/state/withState.ts +0 -47
  316. package/src/pragma/fn.ts +0 -55
@@ -0,0 +1,76 @@
1
+ /*
2
+ * PLAN-4 GS-1: the first-party `selection` behavior.
3
+ *
4
+ * const { Pick, All } = controls({ Pick: 'input', All: 'input' })
5
+ * Mailbox.uses = { sel: selection({ multi: true, item: Pick, all: All, from: 'mails' }) }
6
+ * <Pick type="checkbox" data-id={mail.id} checked={isSelected(state.sel, mail.id)} aria-label={mail.subject} />
7
+ *
8
+ * Slice: { selected } (ids as strings, in selection order) plus the calculated `count`.
9
+ * Actions: sel.SELECT (an id, or a click on the `item` control: its `attr`, default data-id;
10
+ * single mode replaces the selection, multi mode toggles the id), sel.SELECT_ALL (an id array,
11
+ * or every `idField` of the host's `state[from]` list), sel.TOGGLE_ALL (the `all` control: all
12
+ * selected → none, else all), sel.CLEAR. Each is ABORT when nothing changes.
13
+ *
14
+ * `from` is read from the host state, which a behavior's own reducers don't see, so this
15
+ * factory wraps the merged SELECT_ALL / TOGGLE_ALL entries to pass the host list's ids.
16
+ */
17
+ import { defineBehavior } from './behaviors'
18
+ import { ABORT } from '../shared'
19
+
20
+ const ids = (d: any) => Array.isArray(d) ? d.map(String) : null
21
+ const set = (s: any, selected: string[]) =>
22
+ selected.join('\0') === s.selected.join('\0') ? ABORT : {...s, selected}
23
+ // an id, or a click event: the item element's attribute (target.closest: a click on an icon inside)
24
+ const idOf = (d: any, attr: string, item: any): any => {
25
+ if (!d || typeof d != 'object') return d
26
+ const el = d.target?.closest?.(String(item)) || d.ownerTarget || d.target
27
+ return el?.getAttribute?.(attr) ?? el?.dataset?.[attr.slice(5).replace(/-(\w)/g, (_: any, c: string) => c.toUpperCase())]
28
+ }
29
+
30
+ /** Is `id` selected in a `selection` slice (`isSelected(state.sel, item.id)`)? Ids compare as strings. */
31
+ export const isSelected = (slice: any, id: any): boolean => !!slice?.selected?.includes(String(id))
32
+
33
+ const def = (multi: boolean, attr: string, item: any) => ({
34
+ initialState: {selected: [] as string[]},
35
+ intent: ({DOM}: any, {item, all, clear}: any) => ({
36
+ ...(item && {SELECT: DOM.click(item)}),
37
+ ...(all && {TOGGLE_ALL: DOM.click(all)}),
38
+ ...(clear && {CLEAR: DOM.click(clear)}),
39
+ }),
40
+ model: {
41
+ SELECT: (s: any, d: any) => {
42
+ const id = idOf(d, attr, item)
43
+ if (id == null) return ABORT
44
+ const k = String(id), has = s.selected.includes(k)
45
+ return set(s, !multi ? [k] : has ? s.selected.filter((x: string) => x !== k) : [...s.selected, k])
46
+ },
47
+ SELECT_ALL: (s: any, d: any) => ids(d) ? set(s, ids(d)!) : ABORT,
48
+ TOGGLE_ALL: (s: any, d: any) => {
49
+ const all = ids(d)
50
+ return !all ? ABORT : set(s, all.every(k => s.selected.includes(k)) ? [] : all)
51
+ },
52
+ CLEAR: (s: any) => set(s, []),
53
+ },
54
+ calculated: {count: (s: any) => s.selected.length},
55
+ })
56
+
57
+ /**
58
+ * Single or multiple selection over a list (GS-1 behavior). Options: `multi` (false), the
59
+ * controls `item` (on each row, with the id in `attr`, default 'data-id'), `all` (select-all
60
+ * toggle; needs `from`) and `clear`, `from` (the host state key of the list) and `idField`
61
+ * ('id', the id field of its items). Read it with `state.sel.selected`, `state.sel.count` and
62
+ * `isSelected(state.sel, id)`.
63
+ */
64
+ export const selection = (options: any = {}): any => {
65
+ const {multi = false, attr = 'data-id', from, idField = 'id'} = options
66
+ const b = defineBehavior(def(multi, attr, options.item))(options), merge = b.merge
67
+ b.merge = (c: any, k: string) => {
68
+ merge(c, k)
69
+ if (from) for (const a of ['SELECT_ALL', 'TOGGLE_ALL']) {
70
+ const e = c.model[k + '.' + a], S = c.stateSourceName, f = e?.[S]
71
+ if (f) c.model[k + '.' + a] = {...e, [S]: (st: any, d: any, ...x: any[]) =>
72
+ f(st, Array.isArray(d) ? d : (st?.[from] || []).map((i: any) => i?.[idField]), ...x)}
73
+ }
74
+ }
75
+ return b
76
+ }
@@ -0,0 +1,265 @@
1
+ import xs, {Stream} from 'xstream';
2
+ import {senderOf, keepSender, allowed, makeReplies} from './replies';
3
+ import {error as logError} from './diagnostics/legacy';
4
+ import {backoff} from './backoff';
5
+
6
+ /*
7
+ * makeSocketDriver(options?) (PLAN-3 §1.3): WebSocket and server-sent events, declared per
8
+ * component instance and answered with reply actions (./replies.ts). Docs on the declarations
9
+ * in src/index.d.ts. Summary:
10
+ *
11
+ * - Sink values:
12
+ * - `{ connections: { [name]: spec | falsy } }`: the sender's whole set of connections. Diffed
13
+ * per (sender, name): a new name opens, a removed or falsy one closes, a changed URL /
14
+ * protocols / withCredentials / share closes and reopens, anything else (action names,
15
+ * reconnect) only rebinds.
16
+ * - `{ to: name, json? | text? | binary? }`: send on the sender's connection `name` (json is
17
+ * stringified). Queued while (re)connecting, bounded (`queueLimit`, oldest dropped). A send
18
+ * to no such connection, a closed one, or an SSE one is SYG611 (not sent).
19
+ * - A value with a `then` / `catch` key (or a spec with one) is SYG610 (ignored).
20
+ * - Spec: `{ socket: url, protocols?, message?, open?, close?, error?, reconnect?, share? }` or
21
+ * `{ sse: url, withCredentials?, message?, open?, close?, error?, events?, reconnect?, share? }`.
22
+ * URLs are prefixed with `baseUrl` unless absolute; a socket path resolves against `location`
23
+ * with ws:/wss: for http:/https:.
24
+ * - Reply actions (to exactly the sender): `message` (data: the JSON-parsed frame when it parses,
25
+ * else the raw data), `open` ({ reconnected }), `close` ({ code, reason, willReconnect }: only
26
+ * for closes the driver didn't make, G-148), `error` ({ error }); SSE `events: { name: 'ACTION' }`
27
+ * names actions for named events. An event type without an action name goes to `select(name?)` as
28
+ * `{ name, type, data }` (the plain form).
29
+ * - Reconnect (default on): jittered exponential backoff, `{ delayMs: 500, maxDelayMs: 10000,
30
+ * jitter: 0.2 }`, per spec over the driver's `reconnect`; `jitter: false` + equal delays is a
31
+ * fixed delay. A failure to open is a drop. Removal, replacement or dispose cancels a retry.
32
+ * SSE: EventSource retries transient drops itself (close fires with willReconnect: true); the
33
+ * driver's reconnect applies when it gives up (readyState CLOSED).
34
+ * - Sharing (default): connections with the same URL (and protocols / withCredentials) share one
35
+ * socket, ref-counted; every subscriber gets each event. `share: false` opts out.
36
+ * - A disposed sender's connections close (and their retries stop); app dispose closes all.
37
+ * - No WebSocket / EventSource (SSR): nothing opens, nothing is reported.
38
+ * - `WebSocket` / `EventSource` are read at connect time: options.WebSocket ?? globalThis.WebSocket.
39
+ */
40
+
41
+ const SCOPE = '__sygnalScope';
42
+ const scopeOf = (v: any): any[] => v[SCOPE] || [];
43
+ /** a copy of a sink value tagged with one more (outer) scope */
44
+ const tag = (v: any, scope: any) => v && typeof v == 'object'
45
+ ? Object.defineProperty(keepSender(v, {...v}), SCOPE, {value: [scope, ...scopeOf(v)]}) : v;
46
+
47
+ export function makeSocketDriver(options: any = {}) {
48
+ return (sink$: Stream<any>) => {
49
+ const g: any = globalThis;
50
+ // sender → name → entry { sender, name, spec, scope, key, t (its transport), opened }
51
+ const conns = new Map<any, Map<string, any>>();
52
+ // key → transport { key, url, spec, subs: Set<entry>, s (socket), open, queue, n (attempts), timer, on (SSE names) }
53
+ const transports = new Map<string, any>();
54
+ const subs = new Set<any>();
55
+ let disposed = false;
56
+ let unshared = 0;
57
+
58
+ const bad = (msg: string, v: any) =>
59
+ logError('SYG611', v.__emitterName, `makeSocketDriver: ${msg}`, "Declare { connections: { room: { socket: url } } }, then send { to: 'room', json }", v);
60
+
61
+ const deliver = (e: any, type: string, data: any) => {
62
+ const action = e.spec[type];
63
+ if (typeof action == 'string' && e.sender !== undefined) return reply(e.sender, action, data);
64
+ const ev = {name: e.name, type, data};
65
+ subs.forEach(s => {
66
+ if ((s.name === undefined || s.name === e.name) && s.ns.every((x: any, i: number) => e.scope[i] === x)) {
67
+ try { s.l.next(ev); } catch (err) { console.error(err); }
68
+ }
69
+ });
70
+ };
71
+ const each = (t: any, f: (e: any) => any) => [...t.subs].forEach(f);
72
+ const parse = (d: any) => {
73
+ if (typeof d == 'string') try { return JSON.parse(d); } catch (_) {}
74
+ return d;
75
+ };
76
+ const resolve = (u: string, sse: boolean) => {
77
+ if (!/^[a-z][a-z\d+.-]*:/i.test(u)) u = (options.baseUrl || '') + u;
78
+ const loc = g.location;
79
+ if (sse || /^wss?:/i.test(u) || !loc) return u;
80
+ try {
81
+ const r = new URL(u, loc.href);
82
+ r.protocol = r.protocol == 'https:' ? 'wss:' : 'ws:';
83
+ return r.href;
84
+ } catch (_) { return u; }
85
+ };
86
+ const policy = (spec: any) => {
87
+ const r = spec.reconnect, d = options.reconnect;
88
+ return r === false || (r == null && d === false) ? null : {...(d && typeof d == 'object' && d), ...(r && typeof r == 'object' && r)};
89
+ };
90
+
91
+ const out = (t: any, data: any, e: any) => {
92
+ try { t.s.send(data); } catch (error) { deliver(e, 'error', {error}); }
93
+ };
94
+ // SSE named events: one listener per name across the subscribers
95
+ const listen = (t: any) => t.s && t.sse && each(t, e => Object.keys(e.spec.events || {}).forEach(name => {
96
+ if (t.on.has(name)) return;
97
+ t.on.add(name);
98
+ const s = t.s;
99
+ s.addEventListener(name, (m: any) => t.s === s && each(t, x => {
100
+ const a = x.spec.events?.[name];
101
+ if (typeof a == 'string' && x.sender !== undefined) reply(x.sender, a, parse(m.data));
102
+ }));
103
+ }));
104
+
105
+ // the socket closed (or failed to open) without the driver closing it
106
+ const gone = (t: any, info: any) => {
107
+ t.s = null;
108
+ t.open = false;
109
+ let again: any;
110
+ each(t, e => {
111
+ const p = !t.x && policy(e.spec);
112
+ deliver(e, 'close', {...info, willReconnect: !!p});
113
+ if (p) again = again || p;
114
+ else detach(e, true);
115
+ });
116
+ if (again && t.subs.size) t.timer = setTimeout(() => connect(t), backoff(again, t.n++));
117
+ };
118
+
119
+ const connect = (t: any) => {
120
+ t.timer = 0;
121
+ const C = t.sse ? options.EventSource ?? g.EventSource : options.WebSocket ?? g.WebSocket;
122
+ if (typeof C != 'function') return;
123
+ let s: any;
124
+ try {
125
+ s = t.sse ? new C(t.url, {withCredentials: !!t.spec.withCredentials}) : new C(t.url, t.spec.protocols);
126
+ } catch (error) {
127
+ // a URL the constructor rejects never opens: no retry
128
+ each(t, e => deliver(e, 'error', {error}));
129
+ t.x = 1;
130
+ return gone(t, {code: 1006, reason: '' + error});
131
+ }
132
+ t.s = s;
133
+ t.on = new Set();
134
+ const mine = (f: any) => (ev: any) => { if (t.s === s) f(ev); };
135
+ s.onopen = mine(() => {
136
+ t.n = 0;
137
+ t.open = true;
138
+ each(t, e => { deliver(e, 'open', {reconnected: !!e.opened}); e.opened = true; });
139
+ const q = t.queue;
140
+ t.queue = [];
141
+ q.forEach((x: any) => t.s === s && out(t, x.data, x.e));
142
+ });
143
+ s.onmessage = mine((m: any) => { const d = parse(m.data); each(t, e => deliver(e, 'message', d)); });
144
+ s.onerror = mine((error: any) => {
145
+ each(t, e => deliver(e, 'error', {error}));
146
+ if (t.sse) {
147
+ // CLOSED: EventSource gave up (the driver's reconnect applies); else it retries itself
148
+ if (s.readyState == 2 || ![...t.subs].some(e => policy(e.spec))) { s.close(); gone(t, {}); }
149
+ else if (t.open) { t.open = false; each(t, e => deliver(e, 'close', {willReconnect: true})); }
150
+ }
151
+ });
152
+ if (!t.sse) s.onclose = mine((c: any) => gone(t, {code: c.code, reason: c.reason}));
153
+ listen(t);
154
+ };
155
+
156
+ // closes the driver makes: no `close` action, late events ignored, retry cancelled
157
+ const end = (t: any) => {
158
+ clearTimeout(t.timer);
159
+ transports.delete(t.key);
160
+ const s = t.s;
161
+ t.s = null;
162
+ if (s) try { s.close(); } catch (_) {}
163
+ };
164
+ const detach = (e: any, keep?: boolean) => {
165
+ const t = e.t;
166
+ if (!keep) conns.get(e.sender)?.delete(e.name);
167
+ if (!t) return;
168
+ e.t = null;
169
+ t.subs.delete(e);
170
+ t.queue = t.queue.filter((x: any) => x.e !== e);
171
+ if (!t.subs.size) end(t);
172
+ };
173
+ const attach = (e: any) => {
174
+ const key = e.spec.share === false ? e.key + '#' + ++unshared : e.key;
175
+ let t = transports.get(key);
176
+ const fresh = !t;
177
+ if (fresh) transports.set(key, (t = {key, sse: e.sse, url: e.url, spec: e.spec, subs: new Set(), queue: [], n: 0, on: new Set()}));
178
+ e.t = t;
179
+ t.subs.add(e);
180
+ if (fresh) connect(t);
181
+ else {
182
+ listen(t);
183
+ // joined an open shared socket: it is open for this subscriber too
184
+ if (t.open) queueMicrotask(() => { if (e.t === t && t.open && !e.opened) { e.opened = true; deliver(e, 'open', {reconnected: false}); } });
185
+ }
186
+ };
187
+
188
+ const declare = (sender: any, next: any, scope: any[]) => {
189
+ let mine = conns.get(sender);
190
+ if (!mine) conns.set(sender, (mine = new Map()));
191
+ mine.forEach((e, name) => { if (!next[name]) detach(e); });
192
+ Object.keys(next).forEach(name => {
193
+ const spec = next[name];
194
+ if (!spec) return;
195
+ const old = mine!.get(name);
196
+ if (typeof spec != 'object' || (typeof spec.socket != 'string' && typeof spec.sse != 'string') || !allowed(spec, 'makeSocketDriver')) {
197
+ if (old) detach(old);
198
+ if (typeof spec == 'object' && ('then' in spec || 'catch' in spec)) return;
199
+ return bad(`connection '${name}' needs a socket or sse URL`, spec);
200
+ }
201
+ const sse = typeof spec.sse == 'string';
202
+ const url = resolve(sse ? spec.sse : spec.socket, sse);
203
+ // the connection's identity: a change reconnects, anything else only rebinds
204
+ const key = (spec.share === false ? 'u' : '') + (sse ? 'e' + !!spec.withCredentials : 's' + JSON.stringify(spec.protocols)) + url;
205
+ if (old && old.key === key) {
206
+ old.spec = spec;
207
+ if (old.t) listen(old.t);
208
+ return;
209
+ }
210
+ if (old) detach(old);
211
+ const e = {sender, name, spec, scope, key, sse, url, t: null, opened: false};
212
+ mine!.set(name, e);
213
+ attach(e);
214
+ });
215
+ };
216
+
217
+ const send = (sender: any, v: any) => {
218
+ const e = conns.get(sender)?.get(v.to);
219
+ const t = e?.t;
220
+ if (!t || t.sse) return bad(t ? `'${v.to}' is a server-sent events connection (read-only)` : `no connection named '${v.to}' is open`, v);
221
+ const data = v.json !== undefined ? JSON.stringify(v.json) : v.text ?? v.binary;
222
+ if (t.open) out(t, data, e);
223
+ else if (t.queue.push({data, e}) > (options.queueLimit ?? 100)) t.queue.shift();
224
+ };
225
+
226
+ const closeAll = () => {
227
+ conns.forEach(m => m.forEach(e => detach(e)));
228
+ conns.clear();
229
+ };
230
+ const {replies, reply} = makeReplies(sender => {
231
+ conns.get(sender)?.forEach(e => detach(e));
232
+ conns.delete(sender);
233
+ });
234
+
235
+ sink$.addListener({
236
+ next: (v: any) => {
237
+ if (!v || typeof v != 'object' || disposed || !allowed(v, 'makeSocketDriver')) return;
238
+ try {
239
+ const sender = senderOf(v);
240
+ if (v.connections !== undefined) declare(sender, v.connections || {}, scopeOf(v));
241
+ else if (v.to !== undefined) send(sender, v);
242
+ else bad('expected { connections } or { to, json | text | binary }', v);
243
+ } catch (e) { console.error('[Sygnal] makeSocketDriver', v, e); }
244
+ },
245
+ error: (e: any) => console.error('[Sygnal] makeSocketDriver', e),
246
+ complete: closeAll,
247
+ });
248
+
249
+ const source = (ns: any[]): any => ({
250
+ select: (name?: string) => {
251
+ let sub: any;
252
+ return xs.create<any>({start: l => { subs.add((sub = {l, name, ns})); }, stop: () => { subs.delete(sub); }});
253
+ },
254
+ isolateSource: (_: any, scope: any) => source(ns.concat(scope)),
255
+ isolateSink: (sink: any, scope: any) => sink.map((v: any) => tag(v, scope)),
256
+ // PLAN-4.6: the same per value (the core scopes a value without a stream per instance)
257
+ isolateValue: tag,
258
+ // the core sends a component's `connections` static here (PLAN-3 2-B)
259
+ __sygnalStatic: 'connections',
260
+ ...replies,
261
+ });
262
+
263
+ return {...source([]), dispose: () => { closeAll(); disposed = true; }};
264
+ };
265
+ }