sigmx 0.1.0 → 0.2.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 (308) hide show
  1. package/README.md +54 -43
  2. package/dist/cli.js +3 -1
  3. package/dist/cli.js.map +2 -2
  4. package/dist/kernel/compile.d.ts +8 -9
  5. package/dist/kernel/compile.js +102 -67
  6. package/dist/kernel/compile.js.map +2 -2
  7. package/dist/kernel/contracts.d.ts +12 -13
  8. package/dist/kernel/index.d.ts +11 -12
  9. package/dist/kernel/index.js +14 -12
  10. package/dist/kernel/index.js.map +3 -3
  11. package/dist/kernel/precompile.d.ts +8 -12
  12. package/dist/kernel/precompile.js +33 -76
  13. package/dist/kernel/precompile.js.map +2 -2
  14. package/dist/kernel/precompiled.d.ts +7 -4
  15. package/dist/kernel/precompiled.js +5 -5
  16. package/dist/kernel/precompiled.js.map +2 -2
  17. package/dist/kernel/reactive.d.ts +13 -11
  18. package/dist/kernel/reactive.js +96 -71
  19. package/dist/kernel/reactive.js.map +3 -3
  20. package/dist/kernel/runtime.js +85 -63
  21. package/dist/kernel/runtime.js.map +3 -3
  22. package/dist/kernel/scan.d.ts +2 -11
  23. package/dist/kernel/scan.js +26 -9
  24. package/dist/kernel/scan.js.map +2 -2
  25. package/dist/kernel/state.d.ts +5 -8
  26. package/dist/kernel/state.js +114 -76
  27. package/dist/kernel/state.js.map +2 -2
  28. package/dist/kernel/strict-csp.d.ts +0 -5
  29. package/dist/kernel/strict-csp.js +2 -2
  30. package/dist/kernel/strict-csp.js.map +2 -2
  31. package/dist/lib/casing.d.ts +4 -1
  32. package/dist/lib/casing.js +8 -1
  33. package/dist/lib/casing.js.map +2 -2
  34. package/dist/lib/objects.d.ts +5 -4
  35. package/dist/lib/objects.js +13 -5
  36. package/dist/lib/objects.js.map +2 -2
  37. package/dist/lib/schedule.d.ts +3 -5
  38. package/dist/lib/schedule.js +12 -10
  39. package/dist/lib/schedule.js.map +2 -2
  40. package/dist/lib/sse.d.ts +10 -1
  41. package/dist/lib/sse.js +32 -28
  42. package/dist/lib/sse.js.map +2 -2
  43. package/dist/plugins/def.d.ts +5 -0
  44. package/dist/plugins/def.js +22 -0
  45. package/dist/plugins/def.js.map +7 -0
  46. package/dist/plugins/directives/attribute-sync.js +8 -12
  47. package/dist/plugins/directives/attribute-sync.js.map +2 -2
  48. package/dist/plugins/directives/boost.d.ts +1 -1
  49. package/dist/plugins/directives/boost.js +34 -30
  50. package/dist/plugins/directives/boost.js.map +2 -2
  51. package/dist/plugins/directives/busy-flag.js +13 -16
  52. package/dist/plugins/directives/busy-flag.js.map +2 -2
  53. package/dist/plugins/directives/class-toggle.js +17 -16
  54. package/dist/plugins/directives/class-toggle.js.map +2 -2
  55. package/dist/plugins/directives/cloak.js +2 -7
  56. package/dist/plugins/directives/cloak.js.map +2 -2
  57. package/dist/plugins/directives/collapse.js +36 -37
  58. package/dist/plugins/directives/collapse.js.map +2 -2
  59. package/dist/plugins/directives/declare.js +5 -8
  60. package/dist/plugins/directives/declare.js.map +2 -2
  61. package/dist/plugins/directives/derive.js +9 -15
  62. package/dist/plugins/directives/derive.js.map +2 -2
  63. package/dist/plugins/directives/element-ref.js +8 -8
  64. package/dist/plugins/directives/element-ref.js.map +2 -2
  65. package/dist/plugins/directives/event-listener.js +18 -23
  66. package/dist/plugins/directives/event-listener.js.map +2 -2
  67. package/dist/plugins/directives/frame-loop.js +9 -14
  68. package/dist/plugins/directives/frame-loop.js.map +2 -2
  69. package/dist/plugins/directives/html.js +6 -7
  70. package/dist/plugins/directives/html.js.map +2 -2
  71. package/dist/plugins/directives/inline-style.js +17 -20
  72. package/dist/plugins/directives/inline-style.js.map +2 -2
  73. package/dist/plugins/directives/mask.js +19 -23
  74. package/dist/plugins/directives/mask.js.map +2 -2
  75. package/dist/plugins/directives/media-query.d.ts +4 -1
  76. package/dist/plugins/directives/media-query.js +13 -12
  77. package/dist/plugins/directives/media-query.js.map +2 -2
  78. package/dist/plugins/directives/on-mount.js +4 -9
  79. package/dist/plugins/directives/on-mount.js.map +2 -2
  80. package/dist/plugins/directives/patch-listener.js +24 -19
  81. package/dist/plugins/directives/patch-listener.js.map +2 -2
  82. package/dist/plugins/directives/remove-me.js +7 -8
  83. package/dist/plugins/directives/remove-me.js.map +2 -2
  84. package/dist/plugins/directives/scroll-to.d.ts +4 -1
  85. package/dist/plugins/directives/scroll-to.js +8 -14
  86. package/dist/plugins/directives/scroll-to.js.map +2 -2
  87. package/dist/plugins/directives/size-watch.js +7 -12
  88. package/dist/plugins/directives/size-watch.js.map +2 -2
  89. package/dist/plugins/directives/state-dump.js +6 -10
  90. package/dist/plugins/directives/state-dump.js.map +2 -2
  91. package/dist/plugins/directives/storage-sync.js +13 -9
  92. package/dist/plugins/directives/storage-sync.js.map +2 -2
  93. package/dist/plugins/directives/teleport.js +8 -14
  94. package/dist/plugins/directives/teleport.js.map +2 -2
  95. package/dist/plugins/directives/text-content.js +6 -7
  96. package/dist/plugins/directives/text-content.js.map +2 -2
  97. package/dist/plugins/directives/timer.js +7 -13
  98. package/dist/plugins/directives/timer.js.map +2 -2
  99. package/dist/plugins/directives/transition.d.ts +1 -2
  100. package/dist/plugins/directives/transition.js +32 -34
  101. package/dist/plugins/directives/transition.js.map +2 -2
  102. package/dist/plugins/directives/tween.d.ts +2 -2
  103. package/dist/plugins/directives/tween.js +34 -50
  104. package/dist/plugins/directives/tween.js.map +2 -2
  105. package/dist/plugins/directives/two-way-bind.d.ts +1 -1
  106. package/dist/plugins/directives/two-way-bind.js +70 -67
  107. package/dist/plugins/directives/two-way-bind.js.map +2 -2
  108. package/dist/plugins/directives/url-params.js +36 -59
  109. package/dist/plugins/directives/url-params.js.map +3 -3
  110. package/dist/plugins/directives/url-replace.js +6 -7
  111. package/dist/plugins/directives/url-replace.js.map +2 -2
  112. package/dist/plugins/directives/validity.js +6 -9
  113. package/dist/plugins/directives/validity.js.map +2 -2
  114. package/dist/plugins/directives/viewport.js +18 -23
  115. package/dist/plugins/directives/viewport.js.map +2 -2
  116. package/dist/plugins/directives/visibility.js +9 -14
  117. package/dist/plugins/directives/visibility.js.map +2 -2
  118. package/dist/plugins/directives/watch.d.ts +0 -1
  119. package/dist/plugins/directives/watch.js +2 -8
  120. package/dist/plugins/directives/watch.js.map +2 -2
  121. package/dist/plugins/functions/bulk-set.js +10 -7
  122. package/dist/plugins/functions/bulk-set.js.map +2 -2
  123. package/dist/plugins/functions/bulk-toggle.js +10 -7
  124. package/dist/plugins/functions/bulk-toggle.js.map +2 -2
  125. package/dist/plugins/functions/confirm.js +2 -2
  126. package/dist/plugins/functions/confirm.js.map +2 -2
  127. package/dist/plugins/functions/copy-text.js +5 -5
  128. package/dist/plugins/functions/copy-text.js.map +2 -2
  129. package/dist/plugins/functions/dispatch.js +5 -5
  130. package/dist/plugins/functions/dispatch.js.map +2 -2
  131. package/dist/plugins/functions/locale-format.d.ts +4 -1
  132. package/dist/plugins/functions/locale-format.js +9 -16
  133. package/dist/plugins/functions/locale-format.js.map +2 -2
  134. package/dist/plugins/functions/range-map.js +6 -6
  135. package/dist/plugins/functions/range-map.js.map +2 -2
  136. package/dist/plugins/functions/request.d.ts +7 -3
  137. package/dist/plugins/functions/request.js +85 -80
  138. package/dist/plugins/functions/request.js.map +2 -2
  139. package/dist/plugins/functions/untracked-read.js +3 -2
  140. package/dist/plugins/functions/untracked-read.js.map +2 -2
  141. package/dist/plugins/functions/websocket.d.ts +3 -3
  142. package/dist/plugins/functions/websocket.js +18 -25
  143. package/dist/plugins/functions/websocket.js.map +2 -2
  144. package/dist/plugins/index.d.ts +34 -36
  145. package/dist/plugins/index.js +31 -35
  146. package/dist/plugins/index.js.map +2 -2
  147. package/dist/plugins/server-events/apply-elements.d.ts +1 -1
  148. package/dist/plugins/server-events/apply-elements.js +41 -40
  149. package/dist/plugins/server-events/apply-elements.js.map +2 -2
  150. package/dist/plugins/server-events/apply-state.js +9 -12
  151. package/dist/plugins/server-events/apply-state.js.map +2 -2
  152. package/dist/plugins/server-events/morph.d.ts +0 -2
  153. package/dist/plugins/server-events/morph.js +58 -55
  154. package/dist/plugins/server-events/morph.js.map +3 -3
  155. package/dist/presets/all.d.ts +0 -1
  156. package/dist/presets/all.js +22 -26
  157. package/dist/presets/all.js.map +2 -2
  158. package/dist/presets/essentials.d.ts +0 -1
  159. package/dist/presets/essentials.js +27 -9
  160. package/dist/presets/essentials.js.map +2 -2
  161. package/dist/presets/minimal.d.ts +0 -1
  162. package/dist/presets/minimal.js +2 -2
  163. package/dist/presets/minimal.js.map +2 -2
  164. package/dist/server.d.ts +108 -0
  165. package/dist/server.js +218 -0
  166. package/dist/server.js.map +7 -0
  167. package/dist/sigmx.standalone.js +1 -1
  168. package/dist/standalone.js.map +2 -2
  169. package/dist/vite.d.ts +2 -2
  170. package/dist/vite.js +48 -15
  171. package/dist/vite.js.map +2 -2
  172. package/package.json +21 -2
  173. package/dist/auto.d.ts +0 -1
  174. package/dist/auto.js +0 -7
  175. package/dist/auto.js.map +0 -7
  176. package/dist/core/csp.d.ts +0 -7
  177. package/dist/core/csp.js +0 -20
  178. package/dist/core/csp.js.map +0 -7
  179. package/dist/core/engine.d.ts +0 -10
  180. package/dist/core/engine.js +0 -200
  181. package/dist/core/engine.js.map +0 -7
  182. package/dist/core/expr.d.ts +0 -12
  183. package/dist/core/expr.js +0 -94
  184. package/dist/core/expr.js.map +0 -7
  185. package/dist/core/index.d.ts +0 -16
  186. package/dist/core/index.js +0 -46
  187. package/dist/core/index.js.map +0 -7
  188. package/dist/core/signals.d.ts +0 -42
  189. package/dist/core/signals.js +0 -195
  190. package/dist/core/signals.js.map +0 -7
  191. package/dist/core/store.d.ts +0 -32
  192. package/dist/core/store.js +0 -185
  193. package/dist/core/store.js.map +0 -7
  194. package/dist/core/types.d.ts +0 -101
  195. package/dist/core/types.js +0 -1
  196. package/dist/core/types.js.map +0 -7
  197. package/dist/plugins/actions/clipboard.d.ts +0 -2
  198. package/dist/plugins/actions/clipboard.js +0 -11
  199. package/dist/plugins/actions/clipboard.js.map +0 -7
  200. package/dist/plugins/actions/fit.d.ts +0 -2
  201. package/dist/plugins/actions/fit.js +0 -14
  202. package/dist/plugins/actions/fit.js.map +0 -7
  203. package/dist/plugins/actions/intl.d.ts +0 -2
  204. package/dist/plugins/actions/intl.js +0 -21
  205. package/dist/plugins/actions/intl.js.map +0 -7
  206. package/dist/plugins/actions/peek.d.ts +0 -2
  207. package/dist/plugins/actions/peek.js +0 -6
  208. package/dist/plugins/actions/peek.js.map +0 -7
  209. package/dist/plugins/actions/setAll.d.ts +0 -2
  210. package/dist/plugins/actions/setAll.js +0 -11
  211. package/dist/plugins/actions/setAll.js.map +0 -7
  212. package/dist/plugins/actions/toggleAll.d.ts +0 -2
  213. package/dist/plugins/actions/toggleAll.js +0 -11
  214. package/dist/plugins/actions/toggleAll.js.map +0 -7
  215. package/dist/plugins/attributes/attr.d.ts +0 -2
  216. package/dist/plugins/attributes/attr.js +0 -20
  217. package/dist/plugins/attributes/attr.js.map +0 -7
  218. package/dist/plugins/attributes/bind.d.ts +0 -6
  219. package/dist/plugins/attributes/bind.js +0 -81
  220. package/dist/plugins/attributes/bind.js.map +0 -7
  221. package/dist/plugins/attributes/class.d.ts +0 -2
  222. package/dist/plugins/attributes/class.js +0 -22
  223. package/dist/plugins/attributes/class.js.map +0 -7
  224. package/dist/plugins/attributes/computed.d.ts +0 -2
  225. package/dist/plugins/attributes/computed.js +0 -20
  226. package/dist/plugins/attributes/computed.js.map +0 -7
  227. package/dist/plugins/attributes/customValidity.d.ts +0 -2
  228. package/dist/plugins/attributes/customValidity.js +0 -14
  229. package/dist/plugins/attributes/customValidity.js.map +0 -7
  230. package/dist/plugins/attributes/effect.d.ts +0 -2
  231. package/dist/plugins/attributes/effect.js +0 -12
  232. package/dist/plugins/attributes/effect.js.map +0 -7
  233. package/dist/plugins/attributes/indicator.d.ts +0 -2
  234. package/dist/plugins/attributes/indicator.js +0 -20
  235. package/dist/plugins/attributes/indicator.js.map +0 -7
  236. package/dist/plugins/attributes/init.d.ts +0 -2
  237. package/dist/plugins/attributes/init.js +0 -14
  238. package/dist/plugins/attributes/init.js.map +0 -7
  239. package/dist/plugins/attributes/jsonSignals.d.ts +0 -2
  240. package/dist/plugins/attributes/jsonSignals.js +0 -15
  241. package/dist/plugins/attributes/jsonSignals.js.map +0 -7
  242. package/dist/plugins/attributes/matchMedia.d.ts +0 -2
  243. package/dist/plugins/attributes/matchMedia.js +0 -19
  244. package/dist/plugins/attributes/matchMedia.js.map +0 -7
  245. package/dist/plugins/attributes/on.d.ts +0 -5
  246. package/dist/plugins/attributes/on.js +0 -28
  247. package/dist/plugins/attributes/on.js.map +0 -7
  248. package/dist/plugins/attributes/onIntersect.d.ts +0 -2
  249. package/dist/plugins/attributes/onIntersect.js +0 -28
  250. package/dist/plugins/attributes/onIntersect.js.map +0 -7
  251. package/dist/plugins/attributes/onInterval.d.ts +0 -2
  252. package/dist/plugins/attributes/onInterval.js +0 -18
  253. package/dist/plugins/attributes/onInterval.js.map +0 -7
  254. package/dist/plugins/attributes/onRaf.d.ts +0 -1
  255. package/dist/plugins/attributes/onRaf.js +0 -19
  256. package/dist/plugins/attributes/onRaf.js.map +0 -7
  257. package/dist/plugins/attributes/onResize.d.ts +0 -1
  258. package/dist/plugins/attributes/onResize.js +0 -17
  259. package/dist/plugins/attributes/onResize.js.map +0 -7
  260. package/dist/plugins/attributes/onSignalPatch.d.ts +0 -5
  261. package/dist/plugins/attributes/onSignalPatch.js +0 -35
  262. package/dist/plugins/attributes/onSignalPatch.js.map +0 -7
  263. package/dist/plugins/attributes/persist.d.ts +0 -6
  264. package/dist/plugins/attributes/persist.js +0 -16
  265. package/dist/plugins/attributes/persist.js.map +0 -7
  266. package/dist/plugins/attributes/queryString.d.ts +0 -5
  267. package/dist/plugins/attributes/queryString.js +0 -54
  268. package/dist/plugins/attributes/queryString.js.map +0 -7
  269. package/dist/plugins/attributes/ref.d.ts +0 -2
  270. package/dist/plugins/attributes/ref.js +0 -13
  271. package/dist/plugins/attributes/ref.js.map +0 -7
  272. package/dist/plugins/attributes/replaceUrl.d.ts +0 -1
  273. package/dist/plugins/attributes/replaceUrl.js +0 -11
  274. package/dist/plugins/attributes/replaceUrl.js.map +0 -7
  275. package/dist/plugins/attributes/scrollIntoView.d.ts +0 -2
  276. package/dist/plugins/attributes/scrollIntoView.js +0 -19
  277. package/dist/plugins/attributes/scrollIntoView.js.map +0 -7
  278. package/dist/plugins/attributes/show.d.ts +0 -2
  279. package/dist/plugins/attributes/show.js +0 -19
  280. package/dist/plugins/attributes/show.js.map +0 -7
  281. package/dist/plugins/attributes/signals.d.ts +0 -2
  282. package/dist/plugins/attributes/signals.js +0 -13
  283. package/dist/plugins/attributes/signals.js.map +0 -7
  284. package/dist/plugins/attributes/style.d.ts +0 -2
  285. package/dist/plugins/attributes/style.js +0 -25
  286. package/dist/plugins/attributes/style.js.map +0 -7
  287. package/dist/plugins/attributes/text.d.ts +0 -1
  288. package/dist/plugins/attributes/text.js +0 -13
  289. package/dist/plugins/attributes/text.js.map +0 -7
  290. package/dist/plugins/attributes/viewTransition.d.ts +0 -1
  291. package/dist/plugins/attributes/viewTransition.js +0 -11
  292. package/dist/plugins/attributes/viewTransition.js.map +0 -7
  293. package/dist/plugins/directives/transition-name.d.ts +0 -1
  294. package/dist/plugins/directives/transition-name.js +0 -11
  295. package/dist/plugins/directives/transition-name.js.map +0 -7
  296. package/dist/plugins/directives/trap.d.ts +0 -5
  297. package/dist/plugins/directives/trap.js +0 -48
  298. package/dist/plugins/directives/trap.js.map +0 -7
  299. package/dist/sigmx.auto.js +0 -5
  300. package/dist/utils/object.d.ts +0 -9
  301. package/dist/utils/object.js +0 -26
  302. package/dist/utils/object.js.map +0 -7
  303. package/dist/utils/text.d.ts +0 -7
  304. package/dist/utils/text.js +0 -15
  305. package/dist/utils/text.js.map +0 -7
  306. package/dist/utils/timing.d.ts +0 -14
  307. package/dist/utils/timing.js +0 -58
  308. package/dist/utils/timing.js.map +0 -7
package/README.md CHANGED
@@ -1,21 +1,23 @@
1
1
  # sigmx
2
2
 
3
+ [![CI](https://github.com/wrux/sigmx/actions/workflows/ci.yml/badge.svg)](https://github.com/wrux/sigmx/actions/workflows/ci.yml)
4
+
3
5
  **htmx on steroids, in a runtime smaller than htmx.**
4
6
 
5
7
  > **Status: an experiment.** sigmx is a personal exploration of what a hypermedia library looks like when it starts from a tiny, plugin-based core. It is inspired by [htmx](https://htmx.org) (the server sends HTML, the browser swaps it in), [Alpine.js](https://alpinejs.dev) (declarative `data-*` attributes with a little reactivity) and [Datastar](https://data-star.dev) (which fused the two and whose attribute syntax sigmx keeps for compatibility). The code is original, the APIs may still change, and it has not been used in production. Try it, break it, and open an issue with what you find.
6
8
 
7
9
  Your server renders HTML, as it always has. The browser asks for more of it with `@get` and `@post`, and sigmx merges the response into the page with a morph that keeps focus, typed input and scroll position. When you want live updates, the same request can return a stream of patches instead. Signals, two-way binding and reactive attributes cover the client-side state. You write `data-*` attributes and register only the plugins you use.
8
10
 
9
- - **Tiny.** 4.0 KB core, 8.9 KB with the essentials, 12.0 KB with all 52 plugins (brotli). Zero runtime dependencies.
11
+ - **Tiny.** 4.5 KB core, 9.0 KB with the essentials, 11.5 KB with all 50 plugins (brotli). Zero runtime dependencies.
10
12
  - **Server-driven.** Return plain HTML from any endpoint and it is morphed into the page by id, or targeted with a header. Return JSON to merge signals, or an event stream to push many patches over one request, with `Last-Event-ID` reconnects.
11
13
  - **Reactive where it matters.** Signals declared in markup; `bind`, `show`, `class`, computed values and effects keep the page in step. No component model, no virtual DOM.
12
14
  - **Pay for what you use.** Every directive and function is an opt-in plugin, and auto mode scans your source to bundle exactly the set it finds.
13
- - **Batteries from the ecosystem.** The Alpine plugins (`collapse`, `mask`, `trap`, `teleport`, …) and htmx extensions (`boost`, `ws`, `remove-me`, …) people bolt on are built in as plugins.
15
+ - **Batteries from the ecosystem.** The Alpine plugins (`collapse`, `mask`, `teleport`, …) and htmx extensions (`boost`, `ws`, `remove-me`, …) people bolt on are built in as plugins.
14
16
  - **Datastar-compatible markup.** Existing Datastar templates run unchanged.
15
17
 
16
18
  ```html
17
19
  <div data-signals="{ count: 0, name: '' }">
18
- <input data-bind:name placeholder="Your name">
20
+ <input data-bind:name placeholder="Your name" />
19
21
  <p data-show="$name">Hello, <span data-text="$name"></span>!</p>
20
22
  <button data-on:click="$count++">Clicked <b data-text="$count"></b> times</button>
21
23
  <button data-on:click="@post('/save')" data-indicator:saving data-attr:disabled="$saving">Save</button>
@@ -29,10 +31,10 @@ npm install sigmx
29
31
  ```
30
32
 
31
33
  ```ts
32
- import { createSigmx } from 'sigmx'
33
- import { essentials } from 'sigmx/presets/essentials'
34
+ import { createSigmx } from 'sigmx';
35
+ import { essentials } from 'sigmx/presets/essentials';
34
36
 
35
- createSigmx({ plugins: essentials })
37
+ createSigmx({ plugins: essentials });
36
38
  ```
37
39
 
38
40
  Or pick plugins by hand (`sigmx/plugins` is tree-shakeable), use `sigmx/presets/minimal` or `sigmx/presets/all`, let [auto mode](#auto-mode) choose, or drop in the script-tag build:
@@ -41,7 +43,7 @@ Or pick plugins by hand (`sigmx/plugins` is tree-shakeable), use `sigmx/presets/
41
43
  <script type="module" src="/sigmx.standalone.js"></script>
42
44
  ```
43
45
 
44
- Astro users: `npm install sigmx sigmx-astro` and add `sigmx()` to the integrations; see [sdks/astro](sdks/astro).
46
+ Astro users: `npm install sigmx @sigmx/astro` and add `sigmx()` to the integrations; see [sdks/astro](sdks/astro). Hono users: `npm install sigmx @sigmx/hono`, then `app.use(sigmx())` gives every handler `c.var.sigmx`; see [sdks/hono](sdks/hono). Everyone else: `sigmx/server` has the same event builders, responses, streams and signal reading as plain Fetch-API code.
45
47
 
46
48
  ## How it works
47
49
 
@@ -50,17 +52,17 @@ Attributes are `data-<plugin>[:key][__modifier.arg]="expression"`. Expressions a
50
52
  ```html
51
53
  <button data-on:click__debounce.300ms="@get('/search')">Search</button>
52
54
  <li data-class:active="$page === 3"></li>
53
- <input data-bind:user.email>
54
- <p data-text="`${$items.length} items, ${@intl('number', $total, { style: 'currency', currency: 'EUR' })}`"></p>
55
+ <input data-bind:user.email />
56
+ <p data-text="`${$items.length} items, ${@intl('NumberFormat', $total, { style: 'currency', currency: 'EUR' })}`"></p>
55
57
  ```
56
58
 
57
59
  State is one JSON-like store addressed by dotted paths and patched with merge-patch semantics: objects merge, `null` removes, arrays notify on `push`. Requests send the store (minus `_`-prefixed paths) and apply whatever comes back, decided by the response's content type:
58
60
 
59
- | the server returns | sigmx does |
60
- |---|---|
61
- | `text/html` | morphs it into the page: top-level elements by `id`, or a target from the `sigmx-selector` and `sigmx-mode` headers |
62
- | `application/json` | merges it into the signals |
63
- | `text/event-stream` | applies each event as it arrives: `patch-elements`, `patch-signals`, or your own handlers |
61
+ | the server returns | sigmx does |
62
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------- |
63
+ | `text/html` | morphs it into the page: top-level elements by `id`, or a target from the `sigmx-selector` and `sigmx-mode` headers |
64
+ | `application/json` | merges it into the signals |
65
+ | `text/event-stream` | applies each event as it arrives: `patch-elements`, `patch-signals`, or your own handlers |
64
66
 
65
67
  The everyday case is the first row: render a partial with whatever templating you already use and return it. The morph keeps every element whose id appears on both sides, so a re-rendered form keeps what the user typed. Streams are there for progress, chat and dashboards, where one request should keep delivering:
66
68
 
@@ -76,14 +78,14 @@ data: elements <li>Validating…</li>
76
78
 
77
79
  ## Plugins
78
80
 
79
- | kind | plugins |
80
- |---|---|
81
- | state | `signals` `computed` `effect` `ref` `persist` `query-string` |
82
- | rendering | `text` `html` `show` `class` `style` `attr` `animate` `transition` `collapse` `view-transition` `cloak` |
83
- | forms | `bind` `custom-validity` `mask` |
84
- | events | `on` `init` `on-interval` `on-intersect` `on-resize` `on-raf` `on-signal-patch` `match-media` |
85
- | layout | `teleport` `trap` `scroll-into-view` `remove-me` |
86
- | server | `@get` `@post` `@put` `@patch` `@delete` `@ws` `boost` `indicator` `patch-elements` `patch-signals` |
81
+ | kind | plugins |
82
+ | --------- | -------------------------------------------------------------------------------------------------------------- |
83
+ | state | `signals` `computed` `effect` `ref` `persist` `query-string` |
84
+ | rendering | `text` `html` `show` `class` `style` `attr` `animate` `transition` `collapse` `cloak` |
85
+ | forms | `bind` `custom-validity` `mask` |
86
+ | events | `on` `init` `on-interval` `on-intersect` `on-resize` `on-raf` `on-signal-patch` `match-media` |
87
+ | layout | `teleport` `scroll-into-view` `remove-me` |
88
+ | server | `@get` `@post` `@put` `@patch` `@delete` `@ws` `boost` `indicator` `patch-elements` `patch-signals` |
87
89
  | utilities | `@peek` `@setAll` `@toggleAll` `@fit` `@clipboard` `@intl` `@dispatch` `@confirm` `json-signals` `replace-url` |
88
90
 
89
91
  Every plugin has a page in the docs with a live example and its measured size.
@@ -92,16 +94,16 @@ Every plugin has a page in the docs with a live example and its measured size.
92
94
 
93
95
  Brotli, measured from the real source by `npm run size` and `npm run compare` (nothing is quoted from memory):
94
96
 
95
- | | brotli |
96
- |---|---:|
97
- | sigmx core | 4.0 KB |
98
- | sigmx essentials (state, rendering, forms, requests, morph) | 8.9 KB |
99
- | sigmx everything, 52 plugins | 12.0 KB |
100
- | htmx 2.0.10 | 14.6 KB |
101
- | Datastar 1.0.3, free bundle (23 plugins) | 11.8 KB |
102
- | Alpine.js 3.17.1 | 17.6 KB |
97
+ | | brotli |
98
+ | ----------------------------------------------------------- | ------: |
99
+ | sigmx core | 4.5 KB |
100
+ | sigmx essentials (state, rendering, forms, requests, morph) | 9.0 KB |
101
+ | sigmx everything, 50 plugins | 11.5 KB |
102
+ | htmx 2.0.10 | 14.6 KB |
103
+ | Datastar 1.0.3 (23 plugins) | 11.8 KB |
104
+ | Alpine.js 3.17.1 | 17.6 KB |
103
105
 
104
- Precompiling expressions at build time removes the runtime compiler and any use of `new Function`, taking the core to 3.6 KB and making strict CSP trivial.
106
+ Precompiling expressions at build time removes the runtime compiler and any use of `new Function`, taking the core to 3.8 KB and making strict CSP trivial.
105
107
 
106
108
  ## Auto mode
107
109
 
@@ -109,14 +111,14 @@ Like a CSS framework scanning for class names, sigmx can scan your source for th
109
111
 
110
112
  ```js
111
113
  // vite.config.js
112
- import { sigmxAuto } from 'sigmx/vite'
113
- export default { plugins: [sigmxAuto({ always: ['signals'], custom: { upper: 'src/plugins/upper.ts' } })] }
114
+ import { sigmxAuto } from 'sigmx/vite';
115
+ export default { plugins: [sigmxAuto({ always: ['signals'], custom: { upper: 'src/plugins/upper.ts' } })] };
114
116
  ```
115
117
 
116
118
  ```ts
117
- import { createSigmx } from 'sigmx'
118
- import { plugins } from 'virtual:sigmx-plugins'
119
- createSigmx({ plugins })
119
+ import { createSigmx } from 'sigmx';
120
+ import { plugins } from 'virtual:sigmx-plugins';
121
+ createSigmx({ plugins });
120
122
  ```
121
123
 
122
124
  Without Vite: `npx sigmx scan src --out src/sigmx-plugins.js`. In Astro: `sigmx({ plugins: 'auto' })`.
@@ -124,15 +126,17 @@ Without Vite: `npx sigmx scan src --out src/sigmx-plugins.js`. In Astro: `sigmx(
124
126
  ## Writing a plugin
125
127
 
126
128
  ```ts
127
- import { attribute } from 'sigmx'
129
+ import { attribute } from 'sigmx';
128
130
 
129
131
  export const upper = attribute({
130
132
  name: 'upper',
131
133
  value: 'required',
132
134
  mount({ el, evaluate, effect }) {
133
- effect(() => { el.textContent = String(evaluate()).toUpperCase() })
135
+ effect(() => {
136
+ el.textContent = String(evaluate()).toUpperCase();
137
+ });
134
138
  },
135
- })
139
+ });
136
140
  ```
137
141
 
138
142
  Plugins are plain objects; nothing registers on import. The mount context gives you `evaluate`, `effect`, `listen`, `cleanup`, the store and the runtime, and everything registered through it is torn down when the attribute or element goes away. Functions (`@name()`) and server-event handlers work the same way with `action()` and `handler()`.
@@ -149,24 +153,31 @@ cd website && npm install && npm run dev
149
153
 
150
154
  ```
151
155
  src/kernel/ reactive graph, store, expression compiler, runtime (DOM-free core)
156
+ src/server.ts sigmx/server: event builders, sse/sseStream/html/json responses, readSignals
152
157
  src/plugins/ directives, functions, server-event handlers, the morph
153
158
  src/presets/ minimal, essentials, all
154
- sdks/astro/ sigmx-astro: integration, server helpers, auto mode, precompilation
159
+ sdks/astro/ @sigmx/astro: integration, server helpers, auto mode, precompilation
160
+ sdks/hono/ @sigmx/hono: middleware with signals(), html(), events(), stream(); serveClient()
161
+ examples/ standalone Vite, Express, Hono and Astro projects using the published packages
155
162
  website/ documentation site
156
163
  scripts/ build, size and comparison measurements, dev server
157
- tests/ node:test suite for the core; browser suite under tests/browser
164
+ tests/ unit (node:test), dom (happy-dom) and browser (Playwright) suites
158
165
  ```
159
166
 
160
167
  ```bash
161
168
  npm install
162
169
  npm run check # type-check
163
- npm test # build, then node:test over the DOM-free core
170
+ npm run lint # biome: formatting and lint rules
171
+ npm run format # biome: rewrite files in the house style
172
+ npm test # build, then unit tests and happy-dom tests for every plugin
173
+ npm run test:browser # Playwright: transitions, observers, WebSocket, the standalone build
174
+ npm run test:all # everything above plus the SDK suites
164
175
  npm run dev # dev server with SSE and WebSocket endpoints; open /tests/browser/index.html
165
176
  npm run size # bundle sizes for representative plugin sets
166
177
  npm run compare # sizes of htmx, Datastar and Alpine at pinned versions
167
178
  ```
168
179
 
169
- Contributions are welcome. Open an issue first for anything larger than a fix, keep plugins as small as their reference-page size table suggests, and run both test suites before a pull request.
180
+ Contributions are welcome. Open an issue first for anything larger than a fix, keep plugins as small as their reference-page size table suggests, and run `npm run lint` and both test suites before a pull request.
170
181
 
171
182
  ## Licence
172
183
 
package/dist/cli.js CHANGED
@@ -9,7 +9,9 @@ const opt = (name) => {
9
9
  return i < 0 ? void 0 : args.splice(i, 2)[1];
10
10
  };
11
11
  if (cmd !== "scan") {
12
- console.log("usage: sigmx scan [dirs...] [--out src/sigmx-plugins.js] [--always signals,httpGet] [--custom upper=src/plugins/upper.ts] [--prefix data-,hx-]");
12
+ console.log(
13
+ "usage: sigmx scan [dirs...] [--out src/sigmx-plugins.js] [--always signals,httpGet] [--custom upper=src/plugins/upper.ts] [--prefix data-,hx-]"
14
+ );
13
15
  process.exit(cmd ? 1 : 0);
14
16
  }
15
17
  const out = opt("out");
package/dist/cli.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/cli.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n// `sigmx scan [dirs...] [--out file] [--always a,b] [--custom name=path,...] [--prefix data-,hx-]`\nimport { writeFileSync } from 'node:fs'\nimport { generatePluginsModule } from './kernel/scan.js'\nimport { scanProject } from './vite.js'\n\nconst args = process.argv.slice(2)\nconst cmd = args.shift()\nconst opt = (name: string) => {\n const i = args.indexOf(`--${name}`)\n return i < 0 ? undefined : args.splice(i, 2)[1]\n}\nif (cmd !== 'scan') {\n console.log('usage: sigmx scan [dirs...] [--out src/sigmx-plugins.js] [--always signals,httpGet] [--custom upper=src/plugins/upper.ts] [--prefix data-,hx-]')\n process.exit(cmd ? 1 : 0)\n}\nconst out = opt('out')\nconst always = opt('always')?.split(',').filter(Boolean)\nconst prefixes = opt('prefix')?.split(',').filter(Boolean)\nconst custom = Object.fromEntries((opt('custom')?.split(',') ?? []).map((pair) => pair.split('=') as [string, string]))\nconst sel = scanProject({ include: args.length ? args : undefined, always, prefixes, custom })\nconst code = generatePluginsModule(sel)\nif (out) {\n writeFileSync(out, code)\n console.log(`wrote ${out}: ${sel.plugins.length} plugins, ${sel.unused.length} unused`)\n} else process.stdout.write(code)\nfor (const [exp, why] of Object.entries(sel.reasons)) console.error(` ${exp.padEnd(16)} ${why}`)\n"],
5
- "mappings": ";AAEA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,mBAAmB;AAE5B,MAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,MAAM,MAAM,KAAK,MAAM;AACvB,MAAM,MAAM,CAAC,SAAiB;AAC5B,QAAM,IAAI,KAAK,QAAQ,KAAK,IAAI,EAAE;AAClC,SAAO,IAAI,IAAI,SAAY,KAAK,OAAO,GAAG,CAAC,EAAE,CAAC;AAChD;AACA,IAAI,QAAQ,QAAQ;AAClB,UAAQ,IAAI,gJAAgJ;AAC5J,UAAQ,KAAK,MAAM,IAAI,CAAC;AAC1B;AACA,MAAM,MAAM,IAAI,KAAK;AACrB,MAAM,SAAS,IAAI,QAAQ,GAAG,MAAM,GAAG,EAAE,OAAO,OAAO;AACvD,MAAM,WAAW,IAAI,QAAQ,GAAG,MAAM,GAAG,EAAE,OAAO,OAAO;AACzD,MAAM,SAAS,OAAO,aAAa,IAAI,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,KAAK,MAAM,GAAG,CAAqB,CAAC;AACtH,MAAM,MAAM,YAAY,EAAE,SAAS,KAAK,SAAS,OAAO,QAAW,QAAQ,UAAU,OAAO,CAAC;AAC7F,MAAM,OAAO,sBAAsB,GAAG;AACtC,IAAI,KAAK;AACP,gBAAc,KAAK,IAAI;AACvB,UAAQ,IAAI,SAAS,GAAG,KAAK,IAAI,QAAQ,MAAM,aAAa,IAAI,OAAO,MAAM,SAAS;AACxF,MAAO,SAAQ,OAAO,MAAM,IAAI;AAChC,WAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,IAAI,OAAO,EAAG,SAAQ,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC,IAAI,GAAG,EAAE;",
4
+ "sourcesContent": ["#!/usr/bin/env node\n// `sigmx scan [dirs...] [--out file] [--always a,b] [--custom name=path,...] [--prefix data-,hx-]`\nimport { writeFileSync } from 'node:fs';\nimport { generatePluginsModule } from './kernel/scan.js';\nimport { scanProject } from './vite.js';\n\nconst args = process.argv.slice(2);\nconst cmd = args.shift();\nconst opt = (name: string) => {\n const i = args.indexOf(`--${name}`);\n return i < 0 ? undefined : args.splice(i, 2)[1];\n};\nif (cmd !== 'scan') {\n console.log(\n 'usage: sigmx scan [dirs...] [--out src/sigmx-plugins.js] [--always signals,httpGet] [--custom upper=src/plugins/upper.ts] [--prefix data-,hx-]',\n );\n process.exit(cmd ? 1 : 0);\n}\nconst out = opt('out');\nconst always = opt('always')?.split(',').filter(Boolean);\nconst prefixes = opt('prefix')?.split(',').filter(Boolean);\nconst custom = Object.fromEntries((opt('custom')?.split(',') ?? []).map((pair) => pair.split('=') as [string, string]));\nconst sel = scanProject({ include: args.length ? args : undefined, always, prefixes, custom });\nconst code = generatePluginsModule(sel);\nif (out) {\n writeFileSync(out, code);\n console.log(`wrote ${out}: ${sel.plugins.length} plugins, ${sel.unused.length} unused`);\n} else process.stdout.write(code);\nfor (const [exp, why] of Object.entries(sel.reasons)) console.error(` ${exp.padEnd(16)} ${why}`);\n"],
5
+ "mappings": ";AAEA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,mBAAmB;AAE5B,MAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,MAAM,MAAM,KAAK,MAAM;AACvB,MAAM,MAAM,CAAC,SAAiB;AAC5B,QAAM,IAAI,KAAK,QAAQ,KAAK,IAAI,EAAE;AAClC,SAAO,IAAI,IAAI,SAAY,KAAK,OAAO,GAAG,CAAC,EAAE,CAAC;AAChD;AACA,IAAI,QAAQ,QAAQ;AAClB,UAAQ;AAAA,IACN;AAAA,EACF;AACA,UAAQ,KAAK,MAAM,IAAI,CAAC;AAC1B;AACA,MAAM,MAAM,IAAI,KAAK;AACrB,MAAM,SAAS,IAAI,QAAQ,GAAG,MAAM,GAAG,EAAE,OAAO,OAAO;AACvD,MAAM,WAAW,IAAI,QAAQ,GAAG,MAAM,GAAG,EAAE,OAAO,OAAO;AACzD,MAAM,SAAS,OAAO,aAAa,IAAI,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,KAAK,MAAM,GAAG,CAAqB,CAAC;AACtH,MAAM,MAAM,YAAY,EAAE,SAAS,KAAK,SAAS,OAAO,QAAW,QAAQ,UAAU,OAAO,CAAC;AAC7F,MAAM,OAAO,sBAAsB,GAAG;AACtC,IAAI,KAAK;AACP,gBAAc,KAAK,IAAI;AACvB,UAAQ,IAAI,SAAS,GAAG,KAAK,IAAI,QAAQ,MAAM,aAAa,IAAI,OAAO,MAAM,SAAS;AACxF,MAAO,SAAQ,OAAO,MAAM,IAAI;AAChC,WAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,IAAI,OAAO,EAAG,SAAQ,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC,IAAI,GAAG,EAAE;",
6
6
  "names": []
7
7
  }
@@ -2,15 +2,14 @@ import type { ExpressionCompiler } from './contracts.js';
2
2
  export type Compiler = (params: string[], body: string) => (...args: any[]) => any;
3
3
  /** Default compiler: `new Function`. Not usable under a strict CSP; see `cspCompiler`. */
4
4
  export declare const functionCompiler: Compiler;
5
- /** Replace `@name(` with `__a.name(` outside strings and comments. */
6
- export declare const rewriteActions: (src: string) => string;
7
- /** Split on top-level semicolons. */
8
- export declare const splitStatements: (src: string) => string[];
5
+ /** `$count + @fit($x)` `$.count + __a.fit($.x)`; strings, template text, comments and regexes untouched. */
6
+ export declare const transform: (src: string) => string;
9
7
  /**
10
- * Compile an expression to `(store, actions, ...params) => any`: the body runs inside
11
- * `with (store.scope)`, so `$name` resolves through the store. With `returns`, the value of the
12
- * last statement is returned, so `$a = 1; $a * 2` works. Compiled functions are cached by source.
8
+ * Compile `src` with `compiler` as `($, __a, ...params) => any`, returning the body that compiled
9
+ * together with the function. Shared by the runtime and the build-time precompiler.
13
10
  */
14
- export declare const compile: (compiler: Compiler, src: string, params: string[], returns: boolean) => ((...args: any[]) => any);
15
- /** The default pipeline: compile at runtime inside `with (scope)` using `compiler` (`new Function` or CSP). */
11
+ export declare const compileBody: (compiler: Compiler, src: string, params: string[]) => [string, (...args: any[]) => any];
12
+ /** Compile with a cache keyed by parameter names and source. */
13
+ export declare const compile: (compiler: Compiler, src: string, params: string[]) => ((...args: any[]) => any);
14
+ /** The default pipeline: compile at runtime with `compiler` (`new Function` or the CSP compiler). */
16
15
  export declare const runtimeExpressions: (compiler: Compiler) => ExpressionCompiler;
@@ -1,96 +1,131 @@
1
1
  const functionCompiler = (params, body) => Function(...params, body);
2
- const cache = /* @__PURE__ */ new Map();
3
- const isIdent = (c) => /[A-Za-z0-9_$]/.test(c);
4
- const scan = (src, onCode) => {
5
- let i = 0;
2
+ const START = /[A-Za-z_$]/;
3
+ const IDENT = /[\w$]/;
4
+ const BEFORE_REGEX = /(^|[(,=:[!&|?{};+\-*%<>~^]|\b(?:return|typeof|case|in|of|do|else|void|delete|throw|new))$/;
5
+ const rewrite = (src, from, hole) => {
6
+ let out = "";
7
+ let i = from;
8
+ let depth = 0;
6
9
  const n = src.length;
10
+ const code = () => out.trimEnd();
7
11
  while (i < n) {
8
12
  const c = src[i];
9
- const next = src[i + 1];
10
- if (c === '"' || c === "'" || c === "`") {
13
+ const d = src[i + 1];
14
+ if (c === '"' || c === "'") {
11
15
  let j = i + 1;
12
16
  while (j < n && src[j] !== c) j += src[j] === "\\" ? 2 : 1;
13
- onCode(src.slice(i, j + 1), i);
17
+ out += src.slice(i, j + 1);
14
18
  i = j + 1;
15
- } else if (c === "/" && next === "/") {
19
+ } else if (c === "`") {
20
+ out += c;
21
+ i++;
22
+ while (i < n && src[i] !== "`") {
23
+ if (src[i] === "\\") {
24
+ out += src.slice(i, i + 2);
25
+ i += 2;
26
+ } else if (src[i] === "$" && src[i + 1] === "{") {
27
+ const [inner, j] = rewrite(src, i + 2, true);
28
+ out += `\${${inner}}`;
29
+ i = j;
30
+ } else out += src[i++];
31
+ }
32
+ out += "`";
33
+ i++;
34
+ } else if (c === "/" && d === "/") {
16
35
  const j = src.indexOf("\n", i);
17
36
  i = j < 0 ? n : j;
18
- } else if (c === "/" && next === "*") {
37
+ } else if (c === "/" && d === "*") {
19
38
  const j = src.indexOf("*/", i + 2);
20
39
  i = j < 0 ? n : j + 2;
21
- } else {
22
- const skip = onCode(c, i);
23
- i += skip || 1;
24
- }
25
- }
26
- };
27
- const rewriteActions = (src) => {
28
- let out = "";
29
- scan(src, (c, i) => {
30
- if (c === "@") {
40
+ } else if (c === "/" && BEFORE_REGEX.test(code())) {
41
+ let j = i + 1;
42
+ let cls = false;
43
+ for (; j < n && (cls || src[j] !== "/"); j++) {
44
+ if (src[j] === "\\") j++;
45
+ else if (src[j] === "[") cls = true;
46
+ else if (src[j] === "]") cls = false;
47
+ }
48
+ for (j++; j < n && /[a-z]/i.test(src[j]); j++) ;
49
+ out += src.slice(i, j);
50
+ i = j;
51
+ } else if (hole && (c === "{" || c === "}")) {
52
+ if (c === "{") depth++;
53
+ else if (depth) depth--;
54
+ else return [out, i + 1];
55
+ out += c;
56
+ i++;
57
+ } else if (c === "$" && START.test(d ?? "") && d !== "$" && !IDENT.test(out.at(-1) ?? "")) {
58
+ const before = code();
59
+ if (before.endsWith(".") && !before.endsWith("...")) {
60
+ out += c;
61
+ i++;
62
+ continue;
63
+ }
64
+ let j = i + 1;
65
+ while (j < n && IDENT.test(src[j])) j++;
66
+ out += `$.${src.slice(i + 1, j)}`;
67
+ i = j;
68
+ } else if (c === "@" && START.test(d ?? "")) {
31
69
  let j = i + 1;
32
- while (j < src.length && isIdent(src[j])) j++;
70
+ while (j < n && IDENT.test(src[j])) j++;
33
71
  let k = j;
34
- while (k < src.length && /\s/.test(src[k])) k++;
35
- if (j > i + 1 && src[k] === "(") {
72
+ while (k < n && /\s/.test(src[k])) k++;
73
+ if (src[k] === "(") {
36
74
  out += `__a.${src.slice(i + 1, j)}(`;
37
- return k + 1 - i;
75
+ i = k + 1;
76
+ } else {
77
+ out += c;
78
+ i++;
38
79
  }
80
+ } else {
81
+ out += c;
82
+ i++;
39
83
  }
40
- out += c;
41
- });
42
- return out;
84
+ }
85
+ return [out, i];
43
86
  };
44
- const splitStatements = (src) => {
45
- const parts = [];
46
- let depth = 0;
47
- let start = 0;
48
- scan(src, (c, i) => {
49
- if (c.length > 1) return;
50
- if ("([{".includes(c)) depth++;
51
- else if (")]}".includes(c)) depth--;
52
- else if (c === ";" && !depth) {
53
- parts.push(src.slice(start, i));
54
- start = i + 1;
55
- }
56
- });
57
- parts.push(src.slice(start));
58
- return parts.filter((p) => p.trim());
87
+ const transform = (src) => rewrite(src, 0, false)[0];
88
+ const candidates = (code) => {
89
+ const out = [`return(${code}
90
+ )`];
91
+ for (let i = code.lastIndexOf(";"); i >= 0; i = code.lastIndexOf(";", i - 1))
92
+ out.push(`${code.slice(0, i)};return(${code.slice(i + 1)}
93
+ )`);
94
+ out.push(code);
95
+ return out.map((b) => `"use strict";${b}`);
59
96
  };
60
- const compile = (compiler, src, params, returns) => {
61
- const key = `${+returns}|${params.join()}|${src}`;
62
- const hit = cache.get(key);
63
- if (hit) return hit;
64
- const code = rewriteActions(src.trim());
65
- const make = (body) => compiler(["$", "__a", ...params], `with($.scope){${body}
66
- }`);
67
- let fn;
68
- if (!returns) {
69
- fn = make(code);
70
- } else {
97
+ const compileBody = (compiler, src, params) => {
98
+ let error;
99
+ for (const body of candidates(transform(src.trim()))) {
71
100
  try {
72
- fn = make(`return(${code}
73
- )`);
74
- } catch {
75
- const parts = splitStatements(code);
76
- const last = parts.pop() ?? "";
77
- try {
78
- fn = make(`${parts.join(";")};return(${last}
79
- )`);
80
- } catch {
81
- fn = make(code);
82
- }
101
+ return [body, compiler(["$", "__a", ...params], body)];
102
+ } catch (e) {
103
+ error = e;
83
104
  }
84
105
  }
85
- cache.set(key, fn);
106
+ throw error;
107
+ };
108
+ const cache = /* @__PURE__ */ new Map();
109
+ const CACHE_LIMIT = 2e3;
110
+ const compile = (compiler, src, params) => {
111
+ const key = `${params.join()}|${src}`;
112
+ let fn = cache.get(key);
113
+ if (!fn) {
114
+ fn = compileBody(compiler, src, params)[1];
115
+ if (cache.size >= CACHE_LIMIT) cache.delete(cache.keys().next().value);
116
+ cache.set(key, fn);
117
+ }
86
118
  return fn;
87
119
  };
88
- const runtimeExpressions = (compiler) => (src, params, returns) => compile(compiler, src, params, returns);
120
+ const runtimeExpressions = (compiler) => (src, params) => {
121
+ const fn = compile(compiler, src, params);
122
+ return (store, actions, ...rest) => fn(store.$, actions, ...rest);
123
+ };
89
124
  export {
90
125
  compile,
126
+ compileBody,
91
127
  functionCompiler,
92
- rewriteActions,
93
128
  runtimeExpressions,
94
- splitStatements
129
+ transform
95
130
  };
96
131
  //# sourceMappingURL=compile.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/kernel/compile.ts"],
4
- "sourcesContent": ["// Expression compiler. Signals need no rewriting: the body runs inside `with (scope)`, where\n// `$count`, `$user.name` and `$` resolve through the store's scope proxy. Only `@action(` is\n// rewritten, into a call on the per-evaluation actions object.\n\nimport type { ExpressionCompiler } from './contracts.js'\n\nexport type Compiler = (params: string[], body: string) => (...args: any[]) => any\n\n/** Default compiler: `new Function`. Not usable under a strict CSP; see `cspCompiler`. */\nexport const functionCompiler: Compiler = (params, body) => Function(...params, body) as (...args: any[]) => any\n\nconst cache = new Map<string, (...args: any[]) => any>()\n\nconst isIdent = (c: string): boolean => /[A-Za-z0-9_$]/.test(c)\n\n/** Walk source text, skipping string, template and comment contents; `emit` receives code chars. */\nconst scan = (src: string, onCode: (c: string, i: number) => number | void): void => {\n let i = 0\n const n = src.length\n while (i < n) {\n const c = src[i]\n const next = src[i + 1]\n if (c === '\"' || c === \"'\" || c === '`') {\n let j = i + 1\n while (j < n && src[j] !== c) j += src[j] === '\\\\' ? 2 : 1\n onCode(src.slice(i, j + 1), i)\n i = j + 1\n } else if (c === '/' && next === '/') {\n const j = src.indexOf('\\n', i)\n i = j < 0 ? n : j\n } else if (c === '/' && next === '*') {\n const j = src.indexOf('*/', i + 2)\n i = j < 0 ? n : j + 2\n } else {\n const skip = onCode(c, i)\n i += skip || 1\n }\n }\n}\n\n/** Replace `@name(` with `__a.name(` outside strings and comments. */\nexport const rewriteActions = (src: string): string => {\n let out = ''\n scan(src, (c, i) => {\n if (c === '@') {\n let j = i + 1\n while (j < src.length && isIdent(src[j])) j++\n let k = j\n while (k < src.length && /\\s/.test(src[k])) k++\n if (j > i + 1 && src[k] === '(') {\n out += `__a.${src.slice(i + 1, j)}(`\n return k + 1 - i\n }\n }\n out += c\n })\n return out\n}\n\n/** Split on top-level semicolons. */\nexport const splitStatements = (src: string): string[] => {\n const parts: string[] = []\n let depth = 0\n let start = 0\n scan(src, (c, i) => {\n if (c.length > 1) return\n if ('([{'.includes(c)) depth++\n else if (')]}'.includes(c)) depth--\n else if (c === ';' && !depth) {\n parts.push(src.slice(start, i))\n start = i + 1\n }\n })\n parts.push(src.slice(start))\n return parts.filter((p) => p.trim())\n}\n\n/**\n * Compile an expression to `(store, actions, ...params) => any`: the body runs inside\n * `with (store.scope)`, so `$name` resolves through the store. With `returns`, the value of the\n * last statement is returned, so `$a = 1; $a * 2` works. Compiled functions are cached by source.\n */\nexport const compile = (\n compiler: Compiler,\n src: string,\n params: string[],\n returns: boolean,\n): ((...args: any[]) => any) => {\n const key = `${+returns}|${params.join()}|${src}`\n const hit = cache.get(key)\n if (hit) return hit\n const code = rewriteActions(src.trim())\n const make = (body: string) => compiler(['$', '__a', ...params], `with($.scope){${body}\\n}`)\n let fn: (...args: any[]) => any\n if (!returns) {\n fn = make(code)\n } else {\n try {\n fn = make(`return(${code}\\n)`)\n } catch {\n const parts = splitStatements(code)\n const last = parts.pop() ?? ''\n try {\n fn = make(`${parts.join(';')};return(${last}\\n)`)\n } catch {\n fn = make(code)\n }\n }\n }\n cache.set(key, fn)\n return fn\n}\n\n/** The default pipeline: compile at runtime inside `with (scope)` using `compiler` (`new Function` or CSP). */\nexport const runtimeExpressions =\n (compiler: Compiler): ExpressionCompiler =>\n (src, params, returns) =>\n compile(compiler, src, params, returns)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// Expression compiler. An attribute expression is rewritten once, `$name` \u2192 `$.name` and\n// `@name(` \u2192 `__a.name(`, then compiled as a strict-mode function of `($, __a, el, evt, ...args)`\n// where `$` is the store's root proxy. The build-time precompiler emits exactly the same bodies,\n// so precompiled and runtime expressions behave identically.\n\nimport type { ExpressionCompiler } from './contracts.js';\n\nexport type Compiler = (params: string[], body: string) => (...args: any[]) => any;\n\n/** Default compiler: `new Function`. Not usable under a strict CSP; see `cspCompiler`. */\nexport const functionCompiler: Compiler = (params, body) => Function(...params, body) as (...args: any[]) => any;\n\nconst START = /[A-Za-z_$]/;\nconst IDENT = /[\\w$]/;\n/** After these a `/` starts a regular expression rather than a division. */\nconst BEFORE_REGEX = /(^|[(,=:[!&|?{};+\\-*%<>~^]|\\b(?:return|typeof|case|in|of|do|else|void|delete|throw|new))$/;\n\n/**\n * Tokenizer-based rewrite. Copies string literals, template text, and regular expressions verbatim,\n * drops comments, recurses into `${\u2026}` holes, and rewrites signals and actions in code. In hole\n * mode it stops at the unmatched `}` and reports the index after it.\n */\nconst rewrite = (src: string, from: number, hole: boolean): [string, number] => {\n let out = '';\n let i = from;\n let depth = 0;\n const n = src.length;\n const code = () => out.trimEnd();\n while (i < n) {\n const c = src[i];\n const d = src[i + 1];\n if (c === '\"' || c === \"'\") {\n let j = i + 1;\n while (j < n && src[j] !== c) j += src[j] === '\\\\' ? 2 : 1;\n out += src.slice(i, j + 1);\n i = j + 1;\n } else if (c === '`') {\n out += c;\n i++;\n while (i < n && src[i] !== '`') {\n if (src[i] === '\\\\') {\n out += src.slice(i, i + 2);\n i += 2;\n } else if (src[i] === '$' && src[i + 1] === '{') {\n const [inner, j] = rewrite(src, i + 2, true);\n out += `\\${${inner}}`;\n i = j;\n } else out += src[i++];\n }\n out += '`';\n i++;\n } else if (c === '/' && d === '/') {\n const j = src.indexOf('\\n', i);\n i = j < 0 ? n : j;\n } else if (c === '/' && d === '*') {\n const j = src.indexOf('*/', i + 2);\n i = j < 0 ? n : j + 2;\n } else if (c === '/' && BEFORE_REGEX.test(code())) {\n let j = i + 1;\n let cls = false;\n for (; j < n && (cls || src[j] !== '/'); j++) {\n if (src[j] === '\\\\') j++;\n else if (src[j] === '[') cls = true;\n else if (src[j] === ']') cls = false;\n }\n for (j++; j < n && /[a-z]/i.test(src[j]); j++);\n out += src.slice(i, j);\n i = j;\n } else if (hole && (c === '{' || c === '}')) {\n if (c === '{') depth++;\n else if (depth) depth--;\n else return [out, i + 1];\n out += c;\n i++;\n } else if (c === '$' && START.test(d ?? '') && d !== '$' && !IDENT.test(out.at(-1) ?? '')) {\n const before = code();\n // `obj.$x` is a member access and stays; `...$x` is a spread and is a signal.\n if (before.endsWith('.') && !before.endsWith('...')) {\n out += c;\n i++;\n continue;\n }\n let j = i + 1;\n while (j < n && IDENT.test(src[j])) j++;\n out += `$.${src.slice(i + 1, j)}`;\n i = j;\n } else if (c === '@' && START.test(d ?? '')) {\n let j = i + 1;\n while (j < n && IDENT.test(src[j])) j++;\n let k = j;\n while (k < n && /\\s/.test(src[k])) k++;\n if (src[k] === '(') {\n out += `__a.${src.slice(i + 1, j)}(`;\n i = k + 1;\n } else {\n out += c;\n i++;\n }\n } else {\n out += c;\n i++;\n }\n }\n return [out, i];\n};\n\n/** `$count + @fit($x)` \u2192 `$.count + __a.fit($.x)`; strings, template text, comments and regexes untouched. */\nexport const transform = (src: string): string => rewrite(src, 0, false)[0];\n\n/**\n * The strict-mode bodies to try, best first: the whole source as an expression, then each split at a\n * top-level-looking `;` from the end (statements followed by a returned value), then plain statements.\n * A split inside a string or bracket does not compile and the next candidate is tried.\n */\nconst candidates = (code: string): string[] => {\n const out = [`return(${code}\\n)`];\n for (let i = code.lastIndexOf(';'); i >= 0; i = code.lastIndexOf(';', i - 1))\n out.push(`${code.slice(0, i)};return(${code.slice(i + 1)}\\n)`);\n out.push(code);\n return out.map((b) => `\"use strict\";${b}`);\n};\n\n/**\n * Compile `src` with `compiler` as `($, __a, ...params) => any`, returning the body that compiled\n * together with the function. Shared by the runtime and the build-time precompiler.\n */\nexport const compileBody = (compiler: Compiler, src: string, params: string[]): [string, (...args: any[]) => any] => {\n let error: unknown;\n for (const body of candidates(transform(src.trim()))) {\n try {\n return [body, compiler(['$', '__a', ...params], body)];\n } catch (e) {\n error = e;\n }\n }\n throw error;\n};\n\nconst cache = new Map<string, (...args: any[]) => any>();\nconst CACHE_LIMIT = 2000; // a long-lived page streaming unique expressions must not grow without bound\n\n/** Compile with a cache keyed by parameter names and source. */\nexport const compile = (compiler: Compiler, src: string, params: string[]): ((...args: any[]) => any) => {\n const key = `${params.join()}|${src}`;\n let fn = cache.get(key);\n if (!fn) {\n fn = compileBody(compiler, src, params)[1];\n if (cache.size >= CACHE_LIMIT) cache.delete(cache.keys().next().value as string);\n cache.set(key, fn);\n }\n return fn;\n};\n\n/** The default pipeline: compile at runtime with `compiler` (`new Function` or the CSP compiler). */\nexport const runtimeExpressions =\n (compiler: Compiler): ExpressionCompiler =>\n (src, params) => {\n const fn = compile(compiler, src, params);\n return (store, actions, ...rest) => fn(store.$, actions, ...rest);\n };\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,12 +1,12 @@
1
+ import type { CaseStyle } from '../lib/casing.js';
1
2
  import type { Compiler } from './compile.js';
2
3
  import type { Store } from './state.js';
3
- import type { CaseStyle } from '../lib/casing.js';
4
4
  export type El = HTMLElement | SVGElement | MathMLElement;
5
5
  export type Mods = Map<string, string[]>;
6
6
  /** A compiled attribute expression. `store` is the instance store; `actions` resolves `@name(...)`. */
7
7
  export type Evaluator = (store: Store, actions: any, el: El, evt: Event | undefined, ...args: any[]) => any;
8
8
  /** Turns expression source into an evaluator. The runtime compiler and the precompiled table both implement this. */
9
- export type ExpressionCompiler = (src: string, params: string[], returns: boolean) => Evaluator;
9
+ export type ExpressionCompiler = (src: string, params: string[]) => Evaluator;
10
10
  export interface Runtime {
11
11
  /** Attribute prefixes being scanned; the first is primary. */
12
12
  readonly prefixes: readonly string[];
@@ -17,7 +17,6 @@ export interface Runtime {
17
17
  readonly compiler: Compiler;
18
18
  /** Dispatch a `sigmx-<type>` CustomEvent on `document`. */
19
19
  emit(type: string, detail?: unknown): void;
20
- /** Call a registered action by name. */
21
20
  call(name: string, ctx: ActionCtx, args: any[]): any;
22
21
  /** Event-name prefixes accepted from servers; the first is what sigmx itself uses. */
23
22
  readonly eventPrefixes: readonly string[];
@@ -29,13 +28,10 @@ export interface Runtime {
29
28
  }
30
29
  export interface Ctx {
31
30
  el: El;
32
- /** Plugin name, e.g. 'on'. */
33
31
  plugin: string;
34
32
  /** Full attribute name as written, e.g. 'data-on:click__debounce.300ms'. */
35
33
  attr: string;
36
- /** The `:key` part, if any. */
37
34
  key: string | undefined;
38
- /** Attribute value (the expression source). */
39
35
  value: string;
40
36
  mods: Mods;
41
37
  /** The key recased per `__case.<style>`, defaulting to `style`. */
@@ -46,8 +42,11 @@ export interface Ctx {
46
42
  effect(fn: () => void): void;
47
43
  /** addEventListener with automatic removal on cleanup. */
48
44
  listen(target: EventTarget, type: string, fn: (e: any) => void, options?: AddEventListenerOptions): void;
49
- cleanup(fn: () => void): void;
45
+ /** Run `fn` when the attribute unmounts. Returns a function that unregisters it (for work that finished early). */
46
+ cleanup(fn: () => void): () => void;
50
47
  error(message: string, extra?: Record<string, unknown>): Error;
48
+ /** Route an error that happened later (a callback, a promise) to `onError` with this attribute's info. */
49
+ report(error: unknown): void;
51
50
  store: Store;
52
51
  runtime: Runtime;
53
52
  }
@@ -57,8 +56,10 @@ export interface ActionCtx {
57
56
  store: Store;
58
57
  runtime: Runtime;
59
58
  error(message: string, extra?: Record<string, unknown>): Error;
60
- /** Run when the attribute that invoked the action is torn down. */
61
- cleanup(fn: () => void): void;
59
+ /** Route an error that happened later (a callback, a promise) to `onError` with this attribute's info. */
60
+ report(error: unknown): void;
61
+ /** Run when the attribute that invoked the action is torn down. Returns a function that unregisters it. */
62
+ cleanup(fn: () => void): () => void;
62
63
  }
63
64
  export interface AttributePlugin {
64
65
  type: 'attribute';
@@ -66,7 +67,8 @@ export interface AttributePlugin {
66
67
  key?: 'required' | 'forbidden';
67
68
  value?: 'required' | 'forbidden';
68
69
  /** Whether the expression yields a value (default) or is a statement body. */
69
- returns?: boolean;
70
+ /** The value is a literal (a mask, a selector, a delay) rather than an expression; `__dynamic` makes it one. */
71
+ literal?: boolean;
70
72
  /** Extra parameter names the expression can reference, in the order passed to `evaluate`. */
71
73
  args?: string[];
72
74
  mount(ctx: Ctx): void | (() => void);
@@ -76,7 +78,6 @@ export interface ActionPlugin {
76
78
  name: string;
77
79
  call(ctx: ActionCtx, ...args: any[]): any;
78
80
  }
79
- /** Handles a named server event (for example a streamed patch). */
80
81
  export interface HandlerPlugin {
81
82
  type: 'handler';
82
83
  name: string;
@@ -93,7 +94,6 @@ export interface RuntimeOptions {
93
94
  prefix?: string | string[];
94
95
  /** Server event-name prefixes to accept, add a second prefix while migrating from another library. Default 'sigmx-'. */
95
96
  eventPrefix?: string | string[];
96
- /** Share a store between instances. */
97
97
  store?: Store;
98
98
  /** Called for any plugin or expression error. Default: console.error. */
99
99
  onError?: (error: unknown, info: {
@@ -113,7 +113,6 @@ export interface Sigmx {
113
113
  store: Store;
114
114
  /** Root namespace proxy for use from JavaScript: `sigmx.$.count++`. */
115
115
  $: any;
116
- /** Mount plugins under `root` and observe it for changes. */
117
116
  apply(root?: El | ShadowRoot, observe?: boolean): void;
118
117
  /** Register more plugins; attribute plugins are applied to observed roots immediately. */
119
118
  use(...plugins: Plugin[]): void;