effect-start 0.22.1 → 0.23.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 (313) hide show
  1. package/dist/BlobStore.d.ts +80 -0
  2. package/dist/BlobStore.js +19 -0
  3. package/dist/ChildProcess.d.ts +60 -0
  4. package/dist/ChildProcess.js +30 -0
  5. package/dist/Commander.d.ts +3 -6
  6. package/dist/Commander.js +6 -13
  7. package/dist/ContentNegotiation.d.ts +8 -9
  8. package/dist/ContentNegotiation.js +32 -37
  9. package/dist/Cookies.d.ts +47 -0
  10. package/dist/Cookies.js +273 -363
  11. package/dist/Development.d.ts +2 -2
  12. package/dist/Development.js +3 -4
  13. package/dist/Effectify.d.ts +1 -4
  14. package/dist/FilePathPattern.d.ts +3 -3
  15. package/dist/FileRouter.d.ts +5 -8
  16. package/dist/FileRouter.js +9 -10
  17. package/dist/FileRouterCodegen.d.ts +1 -1
  18. package/dist/FileRouterCodegen.js +33 -13
  19. package/dist/FileSystem.d.ts +158 -0
  20. package/dist/FileSystem.js +64 -125
  21. package/dist/Http.js +2 -6
  22. package/dist/PathPattern.d.ts +7 -7
  23. package/dist/PathPattern.js +1 -3
  24. package/dist/PlatformError.d.ts +24 -32
  25. package/dist/PlatformError.js +3 -21
  26. package/dist/PlatformRuntime.js +5 -10
  27. package/dist/Route.d.ts +14 -19
  28. package/dist/Route.js +8 -11
  29. package/dist/RouteBody.d.ts +6 -12
  30. package/dist/RouteBody.js +2 -2
  31. package/dist/RouteError.d.ts +98 -0
  32. package/dist/RouteError.js +55 -0
  33. package/dist/RouteHook.js +6 -11
  34. package/dist/RouteHttp.d.ts +3 -3
  35. package/dist/RouteHttp.js +27 -22
  36. package/dist/RouteMount.d.ts +16 -50
  37. package/dist/RouteMount.js +6 -20
  38. package/dist/RouteSchema.d.ts +22 -1
  39. package/dist/RouteSchema.js +33 -0
  40. package/dist/RouteSse.js +4 -10
  41. package/dist/RouteTree.d.ts +2 -1
  42. package/dist/RouteTree.js +17 -15
  43. package/dist/RouteTrie.d.ts +2 -2
  44. package/dist/RouteTrie.js +4 -9
  45. package/dist/SchemaExtra.d.ts +1 -1
  46. package/dist/Socket.d.ts +27 -0
  47. package/dist/Socket.js +20 -28
  48. package/dist/Sql.d.ts +34 -0
  49. package/dist/Sql.js +5 -0
  50. package/dist/SqlIntrospect.d.ts +91 -0
  51. package/dist/SqlIntrospect.js +466 -0
  52. package/dist/Start.d.ts +4 -6
  53. package/dist/Start.js +10 -2
  54. package/dist/StreamExtra.d.ts +1 -1
  55. package/dist/StreamExtra.js +9 -9
  56. package/dist/System.d.ts +7 -0
  57. package/dist/System.js +22 -0
  58. package/dist/TuplePathPattern.js +55 -50
  59. package/dist/Unique.js +7 -7
  60. package/dist/Values.d.ts +2 -1
  61. package/dist/Values.js +19 -13
  62. package/dist/bun/BunBlobStoreDisk.d.ts +6 -0
  63. package/dist/bun/BunBlobStoreDisk.js +116 -0
  64. package/dist/bun/BunBlobStoreS3.d.ts +11 -0
  65. package/dist/bun/BunBlobStoreS3.js +89 -0
  66. package/dist/bun/BunBlobWatcherDisk.d.ts +6 -0
  67. package/dist/bun/BunBlobWatcherDisk.js +60 -0
  68. package/dist/bun/BunBlobWatcherQueue.d.ts +6 -0
  69. package/dist/bun/BunBlobWatcherQueue.js +17 -0
  70. package/dist/bun/BunBundle.d.ts +5 -6
  71. package/dist/bun/BunBundle.js +7 -15
  72. package/dist/bun/BunChildProcessSpawner.d.ts +3 -0
  73. package/dist/bun/BunChildProcessSpawner.js +103 -0
  74. package/dist/bun/BunImportTrackerPlugin.d.ts +1 -1
  75. package/dist/bun/BunImportTrackerPlugin.js +3 -5
  76. package/dist/bun/BunRoute.d.ts +3 -2
  77. package/dist/bun/BunRoute.js +5 -7
  78. package/dist/bun/BunRuntime.js +1 -1
  79. package/dist/bun/BunServer.d.ts +11 -4
  80. package/dist/bun/BunServer.js +35 -11
  81. package/dist/bun/BunSql.d.ts +4 -0
  82. package/dist/bun/BunSql.js +81 -0
  83. package/dist/bun/_BunEnhancedResolve.d.ts +3 -3
  84. package/dist/bun/_BunEnhancedResolve.js +2 -4
  85. package/dist/bun/index.d.ts +1 -0
  86. package/dist/bun/index.js +1 -0
  87. package/dist/bundler/Bundle.d.ts +2 -1
  88. package/dist/bundler/Bundle.js +1 -1
  89. package/dist/bundler/BundleFiles.d.ts +5 -5
  90. package/dist/bundler/BundleFiles.js +10 -8
  91. package/dist/bundler/BundleRoute.d.ts +27 -0
  92. package/dist/bundler/BundleRoute.js +51 -0
  93. package/dist/client/ScrollState.js +2 -6
  94. package/dist/client/index.js +6 -8
  95. package/dist/console/Console.d.ts +6 -0
  96. package/dist/console/Console.js +26 -0
  97. package/dist/console/ConsoleErrors.d.ts +3 -0
  98. package/dist/console/ConsoleErrors.js +200 -0
  99. package/dist/console/ConsoleLogger.d.ts +3 -0
  100. package/dist/console/ConsoleLogger.js +47 -0
  101. package/dist/console/ConsoleMetrics.d.ts +3 -0
  102. package/dist/console/ConsoleMetrics.js +61 -0
  103. package/dist/console/ConsoleProcess.d.ts +3 -0
  104. package/dist/console/ConsoleProcess.js +49 -0
  105. package/dist/console/ConsoleStore.d.ts +144 -0
  106. package/dist/console/ConsoleStore.js +61 -0
  107. package/dist/console/ConsoleTracer.d.ts +3 -0
  108. package/dist/console/ConsoleTracer.js +94 -0
  109. package/dist/console/Simulation.d.ts +2 -0
  110. package/dist/console/Simulation.js +633 -0
  111. package/dist/console/index.d.ts +3 -0
  112. package/dist/console/index.js +3 -0
  113. package/dist/console/routes/errors/route.d.ts +10 -0
  114. package/dist/console/routes/errors/route.js +47 -0
  115. package/dist/console/routes/fiberDetail.d.ts +16 -0
  116. package/dist/console/routes/fiberDetail.js +38 -0
  117. package/dist/console/routes/fibers/route.d.ts +10 -0
  118. package/dist/console/routes/fibers/route.js +19 -0
  119. package/dist/console/routes/git/route.d.ts +11 -0
  120. package/dist/console/routes/git/route.js +33 -0
  121. package/dist/console/routes/layout.d.ts +9 -0
  122. package/dist/console/routes/layout.js +3 -0
  123. package/dist/console/routes/logs/route.d.ts +10 -0
  124. package/dist/console/routes/logs/route.js +32 -0
  125. package/dist/console/routes/metrics/route.d.ts +10 -0
  126. package/dist/console/routes/metrics/route.js +17 -0
  127. package/dist/console/routes/route.d.ts +6 -0
  128. package/dist/console/routes/route.js +5 -0
  129. package/dist/console/routes/routes/route.d.ts +6 -0
  130. package/dist/console/routes/routes/route.js +20 -0
  131. package/dist/console/routes/services/route.d.ts +6 -0
  132. package/dist/console/routes/services/route.js +12 -0
  133. package/dist/console/routes/system/route.d.ts +10 -0
  134. package/dist/console/routes/system/route.js +18 -0
  135. package/dist/console/routes/traceDetail.d.ts +16 -0
  136. package/dist/console/routes/traceDetail.js +14 -0
  137. package/dist/console/routes/traces/route.d.ts +10 -0
  138. package/dist/console/routes/traces/route.js +39 -0
  139. package/dist/console/routes/tree.d.ts +153 -0
  140. package/dist/console/routes/tree.js +29 -0
  141. package/dist/console/ui/Errors.d.ts +4 -0
  142. package/dist/console/ui/Errors.js +15 -0
  143. package/dist/console/ui/Fibers.d.ts +24 -0
  144. package/dist/console/ui/Fibers.js +121 -0
  145. package/dist/console/ui/Git.d.ts +20 -0
  146. package/dist/console/ui/Git.js +95 -0
  147. package/dist/console/ui/Logs.d.ts +4 -0
  148. package/dist/console/ui/Logs.js +25 -0
  149. package/dist/console/ui/Metrics.d.ts +4 -0
  150. package/dist/console/ui/Metrics.js +26 -0
  151. package/dist/console/ui/Routes.d.ts +8 -0
  152. package/dist/console/ui/Routes.js +70 -0
  153. package/dist/console/ui/Services.d.ts +10 -0
  154. package/dist/console/ui/Services.js +246 -0
  155. package/dist/console/ui/Shell.d.ts +10 -0
  156. package/dist/console/ui/Shell.js +7 -0
  157. package/dist/console/ui/System.d.ts +4 -0
  158. package/dist/console/ui/System.js +35 -0
  159. package/dist/console/ui/Traces.d.ts +12 -0
  160. package/dist/console/ui/Traces.js +179 -0
  161. package/dist/datastar/actions/fetch.d.ts +1 -1
  162. package/dist/datastar/actions/fetch.js +10 -18
  163. package/dist/datastar/actions/peek.js +1 -2
  164. package/dist/datastar/actions/setAll.js +1 -2
  165. package/dist/datastar/actions/toggleAll.js +1 -2
  166. package/dist/datastar/attributes/attr.js +1 -2
  167. package/dist/datastar/attributes/bind.js +10 -18
  168. package/dist/datastar/attributes/class.js +2 -5
  169. package/dist/datastar/attributes/computed.js +2 -3
  170. package/dist/datastar/attributes/effect.js +1 -2
  171. package/dist/datastar/attributes/indicator.js +2 -4
  172. package/dist/datastar/attributes/init.js +2 -3
  173. package/dist/datastar/attributes/jsonSignals.js +1 -2
  174. package/dist/datastar/attributes/on.js +41 -22
  175. package/dist/datastar/attributes/onIntersect.js +2 -3
  176. package/dist/datastar/attributes/onInterval.js +2 -3
  177. package/dist/datastar/attributes/onSignalPatch.js +2 -4
  178. package/dist/datastar/attributes/ref.js +1 -2
  179. package/dist/datastar/attributes/show.js +1 -2
  180. package/dist/datastar/attributes/signals.js +1 -2
  181. package/dist/datastar/attributes/style.js +6 -12
  182. package/dist/datastar/attributes/text.js +1 -2
  183. package/dist/datastar/engine.d.ts +13 -7
  184. package/dist/datastar/engine.js +76 -48
  185. package/dist/datastar/happydom.d.ts +1 -0
  186. package/dist/datastar/happydom.js +8 -0
  187. package/dist/datastar/index.d.ts +1 -1
  188. package/dist/datastar/index.js +1 -1
  189. package/dist/datastar/utils.js +4 -7
  190. package/dist/datastar/watchers/patchElements.js +24 -45
  191. package/dist/datastar/watchers/patchSignals.js +1 -2
  192. package/dist/experimental/EncryptedCookies.d.ts +2 -5
  193. package/dist/experimental/EncryptedCookies.js +17 -48
  194. package/dist/experimental/index.d.ts +0 -1
  195. package/dist/experimental/index.js +0 -1
  196. package/dist/hyper/Hyper.d.ts +2 -9
  197. package/dist/hyper/Hyper.js +1 -12
  198. package/dist/hyper/HyperHtml.d.ts +1 -1
  199. package/dist/hyper/HyperHtml.js +18 -12
  200. package/dist/hyper/HyperHtml.test.d.ts +1 -0
  201. package/dist/hyper/HyperHtml.test.js +197 -0
  202. package/dist/hyper/HyperRoute.test.js +14 -3
  203. package/dist/hyper/html.d.ts +11 -0
  204. package/dist/hyper/html.js +30 -0
  205. package/dist/hyper/index.d.ts +2 -0
  206. package/dist/hyper/index.js +1 -0
  207. package/dist/hyper/jsx-runtime.d.ts +1 -1
  208. package/dist/hyper/jsx-runtime.js +1 -1
  209. package/dist/index.d.ts +1 -0
  210. package/dist/index.js +1 -0
  211. package/dist/lint/plugin.d.ts +86 -0
  212. package/dist/lint/plugin.js +341 -0
  213. package/dist/node/NodeFileSystem.d.ts +2 -2
  214. package/dist/node/NodeFileSystem.js +4 -14
  215. package/dist/sql/bun/index.d.ts +3 -0
  216. package/dist/sql/bun/index.js +75 -0
  217. package/dist/sql/mssql/docker.d.ts +2 -0
  218. package/dist/sql/mssql/docker.js +67 -0
  219. package/dist/sql/mssql/index.d.ts +21 -0
  220. package/dist/sql/mssql/index.js +113 -0
  221. package/dist/testing/TestLogger.js +4 -1
  222. package/dist/testing/index.d.ts +0 -1
  223. package/dist/testing/index.js +0 -1
  224. package/dist/testing/utils.d.ts +3 -3
  225. package/dist/testing/utils.js +4 -4
  226. package/dist/x/cloudflare/CloudflareTunnel.d.ts +2 -5
  227. package/dist/x/cloudflare/CloudflareTunnel.js +14 -27
  228. package/dist/x/datastar/Datastar.d.ts +1 -1
  229. package/dist/x/datastar/Datastar.js +13 -12
  230. package/dist/x/datastar/index.d.ts +1 -2
  231. package/dist/x/datastar/index.js +1 -2
  232. package/dist/x/tailscale/TailscaleTunnel.d.ts +15 -0
  233. package/dist/x/tailscale/TailscaleTunnel.js +68 -0
  234. package/dist/x/tailscale/index.d.ts +1 -0
  235. package/dist/x/tailscale/index.js +1 -0
  236. package/dist/x/tailwind/TailwindPlugin.js +19 -19
  237. package/dist/x/tailwind/compile.d.ts +2 -2
  238. package/dist/x/tailwind/compile.js +2 -4
  239. package/package.json +22 -9
  240. package/src/ChildProcess.ts +145 -0
  241. package/src/PlatformError.ts +27 -50
  242. package/src/Route.ts +2 -2
  243. package/src/RouteError.ts +76 -0
  244. package/src/RouteHttp.ts +13 -5
  245. package/src/RouteSchema.ts +96 -1
  246. package/src/RouteTree.ts +12 -0
  247. package/src/Sql.ts +51 -0
  248. package/src/SqlIntrospect.ts +620 -0
  249. package/src/Start.ts +15 -3
  250. package/src/System.ts +43 -0
  251. package/src/Values.ts +7 -0
  252. package/src/bun/BunChildProcessSpawner.ts +143 -0
  253. package/src/bun/BunRoute.ts +5 -2
  254. package/src/bun/BunServer.ts +22 -1
  255. package/src/bun/index.ts +1 -0
  256. package/src/bundler/BundleRoute.ts +66 -0
  257. package/src/console/Console.ts +42 -0
  258. package/src/console/ConsoleErrors.ts +213 -0
  259. package/src/console/ConsoleLogger.ts +56 -0
  260. package/src/console/ConsoleMetrics.ts +72 -0
  261. package/src/console/ConsoleProcess.ts +59 -0
  262. package/src/console/ConsoleStore.ts +187 -0
  263. package/src/console/ConsoleTracer.ts +107 -0
  264. package/src/console/Simulation.ts +814 -0
  265. package/src/console/console.html +340 -0
  266. package/src/console/index.ts +3 -0
  267. package/src/console/routes/errors/route.tsx +97 -0
  268. package/src/console/routes/fiberDetail.tsx +54 -0
  269. package/src/console/routes/fibers/route.tsx +45 -0
  270. package/src/console/routes/git/route.tsx +64 -0
  271. package/src/console/routes/layout.tsx +4 -0
  272. package/src/console/routes/logs/route.tsx +77 -0
  273. package/src/console/routes/metrics/route.tsx +36 -0
  274. package/src/console/routes/route.tsx +8 -0
  275. package/src/console/routes/routes/route.tsx +30 -0
  276. package/src/console/routes/services/route.tsx +21 -0
  277. package/src/console/routes/system/route.tsx +43 -0
  278. package/src/console/routes/traceDetail.tsx +22 -0
  279. package/src/console/routes/traces/route.tsx +81 -0
  280. package/src/console/routes/tree.ts +30 -0
  281. package/src/console/ui/Errors.tsx +76 -0
  282. package/src/console/ui/Fibers.tsx +321 -0
  283. package/src/console/ui/Git.tsx +182 -0
  284. package/src/console/ui/Logs.tsx +46 -0
  285. package/src/console/ui/Metrics.tsx +78 -0
  286. package/src/console/ui/Routes.tsx +125 -0
  287. package/src/console/ui/Services.tsx +273 -0
  288. package/src/console/ui/Shell.tsx +62 -0
  289. package/src/console/ui/System.tsx +131 -0
  290. package/src/console/ui/Traces.tsx +426 -0
  291. package/src/datastar/README.md +6 -1
  292. package/src/datastar/actions/fetch.ts +0 -1
  293. package/src/datastar/attributes/on.ts +40 -20
  294. package/src/datastar/engine.ts +51 -0
  295. package/src/datastar/jsx.d.ts +79 -0
  296. package/src/hyper/Hyper.ts +1 -16
  297. package/src/hyper/HyperHtml.ts +6 -4
  298. package/src/hyper/HyperRoute.ts +2 -1
  299. package/src/hyper/html.ts +47 -0
  300. package/src/hyper/index.ts +2 -0
  301. package/src/hyper/jsx.d.ts +5 -3
  302. package/src/index.ts +1 -0
  303. package/src/lint/plugin.js +129 -0
  304. package/src/sql/bun/index.ts +147 -0
  305. package/src/sql/mssql/docker.ts +117 -0
  306. package/src/sql/mssql/index.ts +223 -0
  307. package/src/sql/mssql/mssql.d.ts +41 -0
  308. package/src/x/cloudflare/CloudflareTunnel.ts +8 -36
  309. package/src/x/tailscale/TailscaleTunnel.ts +113 -0
  310. package/src/x/tailscale/index.ts +1 -0
  311. package/src/x/datastar/Datastar.ts +0 -61
  312. package/src/x/datastar/index.ts +0 -2
  313. package/src/x/datastar/jsx-datastar.d.ts +0 -60
@@ -0,0 +1,426 @@
1
+ import * as ConsoleStore from "../ConsoleStore.ts"
2
+
3
+ function formatDuration(ms: number | undefined): string {
4
+ if (ms == null) return "..."
5
+ if (ms < 1) return `${(ms * 1000).toFixed(0)}µs`
6
+ if (ms < 1000) return `${ms.toFixed(1)}ms`
7
+ return `${(ms / 1000).toFixed(2)}s`
8
+ }
9
+
10
+ function statusColor(status: string): string {
11
+ if (status === "ok") return "#22c55e"
12
+ if (status === "error") return "#ef4444"
13
+ return "#eab308"
14
+ }
15
+
16
+ function KeyValue({ label, value }: { label: string; value: string | undefined | null }) {
17
+ if (value == null) return null
18
+ return (
19
+ <div style="display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b;font-size:12px">
20
+ <span style="color:#64748b;min-width:120px">{label}</span>
21
+ <span style="color:#e2e8f0;font-family:monospace;word-break:break-all">{value}</span>
22
+ </div>
23
+ )
24
+ }
25
+
26
+ function StatusBadge({ status }: { status: string }) {
27
+ const bg = status === "ok" ? "#166534" : status === "error" ? "#7f1d1d" : "#713f12"
28
+ const fg = status === "ok" ? "#4ade80" : status === "error" ? "#fca5a5" : "#fde047"
29
+ return (
30
+ <span style={`font-size:11px;padding:2px 8px;border-radius:4px;background:${bg};color:${fg}`}>
31
+ {status}
32
+ </span>
33
+ )
34
+ }
35
+
36
+ // --- Tree building ---
37
+
38
+ interface TreeSpan {
39
+ span: ConsoleStore.ConsoleSpan
40
+ depth: number
41
+ childCount: number
42
+ isLastChild: boolean
43
+ ancestorHasNextSibling: Array<boolean>
44
+ }
45
+
46
+ function buildSpanTree(spans: Array<ConsoleStore.ConsoleSpan>): Array<TreeSpan> {
47
+ const byId = new Map<string, ConsoleStore.ConsoleSpan>()
48
+ const childrenOf = new Map<string, Array<ConsoleStore.ConsoleSpan>>()
49
+
50
+ for (const s of spans) {
51
+ byId.set(s.spanId, s)
52
+ }
53
+
54
+ const roots: Array<ConsoleStore.ConsoleSpan> = []
55
+ for (const s of spans) {
56
+ if (s.parentSpanId && byId.has(s.parentSpanId)) {
57
+ let children = childrenOf.get(s.parentSpanId)
58
+ if (!children) {
59
+ children = []
60
+ childrenOf.set(s.parentSpanId, children)
61
+ }
62
+ children.push(s)
63
+ } else {
64
+ roots.push(s)
65
+ }
66
+ }
67
+
68
+ const sortByStart = (a: ConsoleStore.ConsoleSpan, b: ConsoleStore.ConsoleSpan) =>
69
+ Number(a.startTime - b.startTime)
70
+
71
+ roots.sort(sortByStart)
72
+ for (const children of childrenOf.values()) {
73
+ children.sort(sortByStart)
74
+ }
75
+
76
+ const result: Array<TreeSpan> = []
77
+
78
+ function walk(
79
+ span: ConsoleStore.ConsoleSpan,
80
+ depth: number,
81
+ isLast: boolean,
82
+ ancestors: Array<boolean>,
83
+ ) {
84
+ const children = childrenOf.get(span.spanId) ?? []
85
+ result.push({
86
+ span,
87
+ depth,
88
+ childCount: children.length,
89
+ isLastChild: isLast,
90
+ ancestorHasNextSibling: [...ancestors],
91
+ })
92
+ for (let i = 0; i < children.length; i++) {
93
+ walk(children[i], depth + 1, i === children.length - 1, [...ancestors, !isLast])
94
+ }
95
+ }
96
+
97
+ for (let i = 0; i < roots.length; i++) {
98
+ walk(roots[i], 0, i === roots.length - 1, [])
99
+ }
100
+
101
+ return result
102
+ }
103
+
104
+ // --- Components ---
105
+
106
+ function TreeConnectors({ tree }: { tree: TreeSpan }) {
107
+ if (tree.depth === 0) return null
108
+
109
+ const indent = tree.depth * 20
110
+ const elements: Array<any> = []
111
+
112
+ for (let i = 0; i < tree.ancestorHasNextSibling.length; i++) {
113
+ if (tree.ancestorHasNextSibling[i]) {
114
+ elements.push(<div class="wf-vline" style={`left:${i * 20 + 6}px`} />)
115
+ }
116
+ }
117
+
118
+ if (tree.isLastChild) {
119
+ elements.push(<div class="wf-elbow" style={`left:${(tree.depth - 1) * 20 + 6}px`} />)
120
+ } else {
121
+ elements.push(<div class="wf-vline" style={`left:${(tree.depth - 1) * 20 + 6}px`} />)
122
+ }
123
+
124
+ elements.push(<div class="wf-hline" style={`left:${(tree.depth - 1) * 20 + 6}px;top:50%`} />)
125
+
126
+ return (
127
+ <div class="wf-tree" style={`width:${indent}px;position:relative`}>
128
+ {elements}
129
+ </div>
130
+ )
131
+ }
132
+
133
+ function TimeAxis({ totalMs }: { totalMs: number }) {
134
+ const ticks = 5
135
+ const labels: Array<string> = []
136
+ for (let i = 0; i <= ticks; i++) {
137
+ labels.push(formatDuration((totalMs / ticks) * i))
138
+ }
139
+ return (
140
+ <div class="wf-axis">
141
+ <div style="padding:4px 8px;color:#64748b;font-size:11px">Span</div>
142
+ <div class="wf-axis-ticks">
143
+ {labels.map((l) => (
144
+ <span>{l}</span>
145
+ ))}
146
+ </div>
147
+ </div>
148
+ )
149
+ }
150
+
151
+ function WaterfallRow({
152
+ tree,
153
+ totalMs,
154
+ rootStart,
155
+ }: {
156
+ tree: TreeSpan
157
+ totalMs: number
158
+ rootStart: bigint
159
+ }) {
160
+ const s = tree.span
161
+ const offsetMs = Number(s.startTime - rootStart) / 1_000_000
162
+ const durMs = s.durationMs ?? 0
163
+ const leftPct = totalMs > 0 ? Math.min(100, (offsetMs / totalMs) * 100) : 0
164
+ const widthPct =
165
+ totalMs > 0 ? Math.max(0.5, Math.min(100 - leftPct, (durMs / totalMs) * 100)) : 100
166
+ const color = statusColor(s.status)
167
+
168
+ const durLabelLeft = leftPct + widthPct + 0.5
169
+
170
+ return (
171
+ <div class="wf-row">
172
+ <div class="wf-name">
173
+ <TreeConnectors tree={tree} />
174
+ <span style="overflow:hidden;text-overflow:ellipsis">{s.name}</span>
175
+ {tree.childCount > 0 && <span class="wf-badge">{tree.childCount}</span>}
176
+ </div>
177
+ <div class="wf-bar-cell">
178
+ <div class="wf-bar" style={`left:${leftPct}%;width:${widthPct}%;background:${color}`} />
179
+ <div class="wf-dur" style={`left:${durLabelLeft}%`}>
180
+ {formatDuration(s.durationMs)}
181
+ </div>
182
+ </div>
183
+ </div>
184
+ )
185
+ }
186
+
187
+ function MiniWaterfall({
188
+ spans,
189
+ totalMs,
190
+ rootStart,
191
+ }: {
192
+ spans: Array<ConsoleStore.ConsoleSpan>
193
+ totalMs: number
194
+ rootStart: bigint
195
+ }) {
196
+ if (totalMs <= 0) return <div class="mini-wf" />
197
+ return (
198
+ <div class="mini-wf">
199
+ {spans.map((s) => {
200
+ const offsetMs = Number(s.startTime - rootStart) / 1_000_000
201
+ const durMs = s.durationMs ?? 0
202
+ const leftPct = Math.min(100, (offsetMs / totalMs) * 100)
203
+ const widthPct = Math.max(0.3, Math.min(100 - leftPct, (durMs / totalMs) * 100))
204
+ return (
205
+ <div
206
+ class="mini-wf-bar"
207
+ style={`left:${leftPct}%;width:${widthPct}%;background:${statusColor(s.status)}`}
208
+ />
209
+ )
210
+ })}
211
+ </div>
212
+ )
213
+ }
214
+
215
+ // --- Exports ---
216
+
217
+ export function groupByTraceId(
218
+ spans: Array<ConsoleStore.ConsoleSpan>,
219
+ ): Map<string, Array<ConsoleStore.ConsoleSpan>> {
220
+ const groups = new Map<string, Array<ConsoleStore.ConsoleSpan>>()
221
+ for (const span of spans) {
222
+ let group = groups.get(span.traceId)
223
+ if (!group) {
224
+ group = []
225
+ groups.set(span.traceId, group)
226
+ }
227
+ group.push(span)
228
+ }
229
+ return groups
230
+ }
231
+
232
+ export function TraceGroup({ spans }: { spans: Array<ConsoleStore.ConsoleSpan> }) {
233
+ if (spans.length === 0) return null
234
+ const root = spans.find((s) => !s.parentSpanId) ?? spans[0]
235
+ const traceId = root.traceId
236
+ const totalMs = root.durationMs ?? 0
237
+ const rootStart = root.startTime
238
+ const hasError = spans.some((s) => s.status === "error")
239
+ const status = hasError ? "error" : root.status
240
+
241
+ return (
242
+ <details class="tl-row">
243
+ <summary class="tl-summary tl-cols">
244
+ <span class="tl-cell tl-cell-status">
245
+ <span
246
+ style={`width:8px;height:8px;border-radius:50%;background:${statusColor(status)};display:block`}
247
+ />
248
+ </span>
249
+ <span class="tl-cell tl-cell-name">{root.name}</span>
250
+ <span class="tl-cell tl-cell-spans">{spans.length}</span>
251
+ <span class="tl-cell tl-cell-dur">{formatDuration(totalMs)}</span>
252
+ <span class="tl-cell tl-cell-id">{traceId.slice(0, 12)}</span>
253
+ </summary>
254
+ <div class="tl-body">
255
+ <div style="display:flex;gap:12px;align-items:center;margin-bottom:8px">
256
+ <a
257
+ href={`${ConsoleStore.store.prefix}/traces/${traceId}`}
258
+ style="color:#38bdf8;font-size:12px;text-decoration:none"
259
+ >
260
+ Full trace view
261
+ </a>
262
+ <StatusBadge status={status} />
263
+ <span style="color:#64748b;font-size:11px">
264
+ {spans.length} span{spans.length !== 1 ? "s" : ""}
265
+ </span>
266
+ <span style="color:#64748b;font-size:11px;font-family:monospace">
267
+ {formatDuration(totalMs)}
268
+ </span>
269
+ <span style="color:#475569;font-size:10px;font-family:monospace">{traceId}</span>
270
+ </div>
271
+ {spans.map((s) => {
272
+ const offsetMs = Number(s.startTime - rootStart) / 1_000_000
273
+ const leftPct = totalMs > 0 ? Math.min(100, (offsetMs / totalMs) * 100) : 0
274
+ const widthPct =
275
+ totalMs > 0
276
+ ? Math.max(0.5, Math.min(100 - leftPct, ((s.durationMs ?? 0) / totalMs) * 100))
277
+ : 100
278
+ return (
279
+ <div style="display:flex;align-items:center;gap:8px;padding:2px 0;font-size:11px;font-family:monospace">
280
+ <span style={`color:${statusColor(s.status)};min-width:10px`}>
281
+ {s.parentSpanId ? " " : ""}
282
+ </span>
283
+ <span style="color:#d1d5db;min-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
284
+ {s.name}
285
+ </span>
286
+ <div style="flex:1;height:10px;background:#1f2937;border-radius:2px;position:relative;overflow:hidden">
287
+ <div
288
+ style={`position:absolute;height:100%;border-radius:2px;background:${statusColor(s.status)};left:${leftPct}%;width:${widthPct}%`}
289
+ />
290
+ </div>
291
+ <span style="color:#9ca3af;min-width:60px;text-align:right">
292
+ {formatDuration(s.durationMs)}
293
+ </span>
294
+ </div>
295
+ )
296
+ })}
297
+ </div>
298
+ </details>
299
+ )
300
+ }
301
+
302
+ export function TraceGroups({ spans }: { spans: Array<ConsoleStore.ConsoleSpan> }) {
303
+ const groups = groupByTraceId(spans)
304
+ const sorted = Array.from(groups.values())
305
+ .sort((a, b) => Number(b[0].startTime) - Number(a[0].startTime))
306
+ .slice(0, 50)
307
+
308
+ if (sorted.length === 0) {
309
+ return <div class="empty">Waiting for traces...</div>
310
+ }
311
+ return (
312
+ <div class="tl-grid">
313
+ <div class="tl-header tl-cols">
314
+ <span class="tl-cell tl-cell-status" />
315
+ <span class="tl-cell tl-cell-name">Name</span>
316
+ <span class="tl-cell tl-cell-spans">Spans</span>
317
+ <span class="tl-cell tl-cell-dur">Duration</span>
318
+ <span class="tl-cell tl-cell-id">Trace</span>
319
+ </div>
320
+ {sorted.map((group) => (
321
+ <TraceGroup spans={group} />
322
+ ))}
323
+ </div>
324
+ )
325
+ }
326
+
327
+ export function TraceDetail({
328
+ prefix,
329
+ spans,
330
+ }: {
331
+ prefix: string
332
+ spans: Array<ConsoleStore.ConsoleSpan>
333
+ }) {
334
+ if (spans.length === 0) {
335
+ return <div class="empty">Trace not found</div>
336
+ }
337
+ const root = spans.find((s) => !s.parentSpanId) ?? spans[0]
338
+ const traceId = root.traceId
339
+ const totalMs = root.durationMs ?? 0
340
+ const rootStart = root.startTime
341
+ const startDate = new Date(Number(rootStart) / 1_000_000)
342
+ const tree = buildSpanTree(spans)
343
+
344
+ return (
345
+ <>
346
+ <div style="padding:12px 16px;border-bottom:1px solid #1e293b">
347
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
348
+ <a href={`${prefix}/traces`} style="color:#64748b;text-decoration:none;font-size:12px">
349
+ Traces
350
+ </a>
351
+ <span style="color:#475569">/</span>
352
+ <span style="color:#e2e8f0;font-size:13px;font-family:monospace">{root.name}</span>
353
+ </div>
354
+ <div style="display:flex;gap:16px;font-size:12px;color:#94a3b8;align-items:center">
355
+ <StatusBadge status={root.status} />
356
+ <span>
357
+ {spans.length} span{spans.length !== 1 ? "s" : ""}
358
+ </span>
359
+ <span>{formatDuration(totalMs)}</span>
360
+ <span>{startDate.toLocaleTimeString("en", { hour12: false })}</span>
361
+ <span style="color:#475569;font-family:monospace;font-size:10px">{traceId}</span>
362
+ </div>
363
+ </div>
364
+
365
+ <div style="padding:8px 16px">
366
+ <MiniWaterfall spans={spans} totalMs={totalMs} rootStart={rootStart} />
367
+ </div>
368
+
369
+ <div style="padding:0 8px">
370
+ <TimeAxis totalMs={totalMs} />
371
+ <div class="wf-grid">
372
+ {tree.map((t) => (
373
+ <WaterfallRow tree={t} totalMs={totalMs} rootStart={rootStart} />
374
+ ))}
375
+ </div>
376
+ </div>
377
+
378
+ <div style="padding:8px">
379
+ {tree.map((t) => {
380
+ const s = t.span
381
+ const stacktrace = s.attributes["code.stacktrace"] as string | undefined
382
+ const customAttrs = Object.entries(s.attributes).filter(([k]) => k !== "code.stacktrace")
383
+
384
+ return (
385
+ <details class="span-panel" style="margin-bottom:4px">
386
+ <summary class="span-panel-header">
387
+ <span
388
+ style={`width:8px;height:8px;border-radius:50%;background:${statusColor(s.status)};flex-shrink:0`}
389
+ />
390
+ <span style="color:#e2e8f0;font-family:monospace;font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1">
391
+ {s.name}
392
+ </span>
393
+ <StatusBadge status={s.status} />
394
+ <span style="color:#64748b;font-size:11px;font-family:monospace;margin-left:auto">
395
+ {formatDuration(s.durationMs)}
396
+ </span>
397
+ </summary>
398
+ <div class="span-panel-body">
399
+ <KeyValue label="Span ID" value={s.spanId} />
400
+ <KeyValue label="Kind" value={s.kind} />
401
+ {s.parentSpanId && <KeyValue label="Parent" value={s.parentSpanId} />}
402
+ {stacktrace && <KeyValue label="Source" value={stacktrace} />}
403
+ {customAttrs.map(([k, v]) => (
404
+ <KeyValue label={k} value={String(v)} />
405
+ ))}
406
+ {s.events.length > 0 && (
407
+ <div style="margin-top:4px">
408
+ <span style="color:#64748b;font-size:11px">Events:</span>
409
+ {s.events.map((ev) => (
410
+ <div style="padding:2px 0;font-size:11px;color:#94a3b8;font-family:monospace">
411
+ {ev.name}
412
+ {ev.attributes && (
413
+ <span style="color:#64748b"> {JSON.stringify(ev.attributes)}</span>
414
+ )}
415
+ </div>
416
+ ))}
417
+ </div>
418
+ )}
419
+ </div>
420
+ </details>
421
+ )
422
+ })}
423
+ </div>
424
+ </>
425
+ )
426
+ }
@@ -6,7 +6,7 @@ We experimentally place it inside Effect Start to have tighter integration with
6
6
  great out-of-the-box experience. After cleaning up the code, we're at around ~90kb of source code.
7
7
  We can probably cut it down by another ~10kb if we remove DataStar expression and use JS functions directly.
8
8
 
9
- Based on `812cbe9` (2025-02-05) following changes were made:
9
+ We made following changes:
10
10
 
11
11
  - Path aliases converted to relative imports: `@engine/*` → `./engine/*`, etc.
12
12
  - Flattened `plugins/` directory: `plugins/actions/` → `actions/`, etc.
@@ -16,3 +16,8 @@ Based on `812cbe9` (2025-02-05) following changes were made:
16
16
  - Updated type declaration to conform to `erasableSyntaxOnly`:
17
17
  - Converted `enum ReactiveFlags` and `enum EffectFlags` to `const` objects with `as const`
18
18
  - Added type aliases `ReactiveFlags_X` to replace `ReactiveFlags.X` namespace types
19
+ - Extends expressions with function form handled by `genRx`
20
+ - `data-on` supports function form with optional config object:
21
+ - Function form: `data-on:click="(e) => { e.signals.count.value++ }"`
22
+ - Function form with config: `data-on:click="(e) => {...}, { debounce: 500, prevent: true }"`
23
+ - !! `__` attribute modifiers are no longer parsed by `on.ts` (now removed from imports)
@@ -285,7 +285,6 @@ const getLines = (onLine: (line: Uint8Array, fieldLength: number) => void) => {
285
285
  }
286
286
  break
287
287
  // @ts-expect-error:7029
288
- // biome-ignore lint/suspicious/noFallthroughSwitchClause: intentional fallthrough for CR to LF
289
288
  case 13:
290
289
  discardTrailingNewline = true
291
290
  case 10:
@@ -4,46 +4,66 @@ import {
4
4
  DATASTAR_FETCH_EVENT,
5
5
  DATASTAR_SIGNAL_PATCH_EVENT,
6
6
  endBatch,
7
+ type Modifiers,
7
8
  } from "../engine.ts"
8
- import { modifyCasing, modifyTiming, modifyViewTransition } from "../utils.ts"
9
+ import { modifyTiming, modifyViewTransition } from "../utils.ts"
10
+
11
+ // TODO: support leading/trailing options for debounce/throttle
12
+ // e.g. { debounce: { ms: 500, leading: true, noTrailing: true } }
13
+ const configToMods = (config: Record<string, any>): Modifiers => {
14
+ const mods: Modifiers = new Map()
15
+ for (const [k, v] of Object.entries(config)) {
16
+ if (v === true) {
17
+ mods.set(k, new Set())
18
+ } else if (typeof v === "number") {
19
+ mods.set(k, new Set([`${v}ms`]))
20
+ }
21
+ }
22
+ return mods
23
+ }
9
24
 
10
25
  attribute({
11
26
  name: "on",
12
27
  requirement: "must",
13
28
  argNames: ["evt"],
14
- apply({ el, key, mods, rx }) {
29
+ apply({ el, key, mods, rx, value }) {
30
+ let userFn: Function | undefined
31
+ let effectiveMods = mods
32
+
33
+ try {
34
+ const parts = Function(`return [${value}]`)()
35
+ if (typeof parts[0] === "function") {
36
+ userFn = parts[0]
37
+ if (parts[1]) effectiveMods = configToMods(parts[1])
38
+ }
39
+ } catch {}
40
+
15
41
  let target: Element | Window | Document = el
16
- if (mods.has("window")) target = window
42
+ if (effectiveMods.has("window")) target = window
17
43
  let callback = (evt?: Event) => {
18
44
  if (evt) {
19
- if (mods.has("prevent")) {
20
- evt.preventDefault()
21
- }
22
- if (mods.has("stop")) {
23
- evt.stopPropagation()
24
- }
45
+ if (effectiveMods.has("prevent")) evt.preventDefault()
46
+ if (effectiveMods.has("stop")) evt.stopPropagation()
25
47
  }
26
48
  beginBatch()
27
- rx(evt)
49
+ userFn ? userFn(evt) : rx(evt)
28
50
  endBatch()
29
51
  }
30
- callback = modifyViewTransition(callback, mods)
31
- callback = modifyTiming(callback, mods)
52
+ callback = modifyViewTransition(callback, effectiveMods)
53
+ callback = modifyTiming(callback, effectiveMods)
32
54
  const evtListOpts: AddEventListenerOptions = {
33
- capture: mods.has("capture"),
34
- passive: mods.has("passive"),
35
- once: mods.has("once"),
55
+ capture: effectiveMods.has("capture"),
56
+ passive: effectiveMods.has("passive"),
57
+ once: effectiveMods.has("once"),
36
58
  }
37
- if (mods.has("outside")) {
59
+ if (effectiveMods.has("outside")) {
38
60
  target = document
39
61
  const cb = callback
40
62
  callback = (evt?: Event) => {
41
- if (!el.contains(evt?.target as HTMLElement)) {
42
- cb(evt)
43
- }
63
+ if (!el.contains(evt?.target as HTMLElement)) cb(evt)
44
64
  }
45
65
  }
46
- const eventName = modifyCasing(key, mods, "kebab")
66
+ const eventName = key
47
67
  if (eventName === DATASTAR_FETCH_EVENT || eventName === DATASTAR_SIGNAL_PATCH_EVENT) {
48
68
  target = document
49
69
  }
@@ -116,6 +116,14 @@ export type MergePatchArgs = {
116
116
  }
117
117
 
118
118
  export type HTMLOrSVG = HTMLElement | SVGElement | MathMLElement
119
+
120
+ export type DataEvent = Event & {
121
+ signals: Record<string, any>
122
+ actions: Record<string, (...args: Array<any>) => any>
123
+ target: HTMLOrSVG
124
+ window: Window & typeof globalThis
125
+ }
126
+
119
127
  export type Modifiers = Map<string, Set<string>>
120
128
 
121
129
  export type EventCallbackHandler = (...args: Array<any>) => void
@@ -1187,6 +1195,49 @@ const genRx = (
1187
1195
  value: string,
1188
1196
  { returnsValue = false, argNames = [], cleanups = new Map() }: GenRxOptions = {},
1189
1197
  ): GenRxFn => {
1198
+ if (/^\s*(?:async\s+)?(?:\(.*?\)\s*=>|[\w$]+\s*=>|function\s*[\w$]*\s*\()/.test(value)) {
1199
+ const userFn = Function(`return (${value.trim()})`)()
1200
+
1201
+ return (el: HTMLOrSVG, ...args: Array<any>) => {
1202
+ const actionsProxy = new Proxy({} as Record<string, any>, {
1203
+ get:
1204
+ (_, name: string) =>
1205
+ (...actionArgs: Array<any>) => {
1206
+ const err = error.bind(0, {
1207
+ plugin: { type: "action", name },
1208
+ element: { id: el.id, tag: el.tagName },
1209
+ expression: { fnContent: value, value },
1210
+ })
1211
+ const fn = actions[name]
1212
+ if (fn) return fn({ el, evt: undefined, error: err, cleanups }, ...actionArgs)
1213
+ throw err("UndefinedAction")
1214
+ },
1215
+ })
1216
+
1217
+ const dataEvt = args[0] instanceof Event ? args[0] : new Event("datastar:expression")
1218
+ Object.defineProperties(dataEvt, {
1219
+ target: { value: el },
1220
+ signals: { value: root },
1221
+ actions: { value: actionsProxy },
1222
+ window: { value: window },
1223
+ })
1224
+
1225
+ try {
1226
+ return userFn(dataEvt)
1227
+ } catch (e: any) {
1228
+ console.error(e)
1229
+ throw error(
1230
+ {
1231
+ element: { id: el.id, tag: el.tagName },
1232
+ expression: { fnContent: value, value },
1233
+ error: e.message,
1234
+ },
1235
+ "ExecuteExpression",
1236
+ )
1237
+ }
1238
+ }
1239
+ }
1240
+
1190
1241
  let expr = ""
1191
1242
  if (returnsValue) {
1192
1243
  const statementRe =
@@ -0,0 +1,79 @@
1
+ import type { DataEvent } from "./engine.ts"
2
+
3
+ // Datastar object types for specific attributes
4
+ type DatastarSignalsObject = Record<string, any>
5
+ type DatastarClassObject = Record<string, boolean | string>
6
+ type DatastarAttrObject = Record<string, string | boolean | number>
7
+ type DatastarStyleObject = Record<string, string | number | boolean | null | undefined>
8
+
9
+ type DatastarFn = (e: DataEvent) => any
10
+
11
+ // TODO: support leading/trailing options
12
+ // e.g. { debounce: { ms: 500, leading: true, noTrailing: true } }
13
+ type DatastarOnConfig = {
14
+ prevent?: boolean
15
+ stop?: boolean
16
+ capture?: boolean
17
+ passive?: boolean
18
+ once?: boolean
19
+ outside?: boolean
20
+ window?: boolean
21
+ viewTransition?: boolean
22
+ } & ({ debounce?: number; throttle?: never } | { debounce?: never; throttle?: number })
23
+
24
+ type DatastarOnFn = DatastarFn | [handler: DatastarFn, config: DatastarOnConfig]
25
+
26
+ /**
27
+ * Datastar attributes for reactive web applications
28
+ * @see https://data-star.dev/reference/attributes
29
+ */
30
+ export interface DatastarAttributes {
31
+ // Core attributes that can accept objects (but also strings)
32
+ "data-signals"?: string | DatastarSignalsObject | undefined
33
+ "data-class"?: string | DatastarFn | DatastarClassObject | undefined
34
+ "data-attr"?: string | DatastarFn | DatastarAttrObject | undefined
35
+ "data-style"?: string | DatastarFn | DatastarStyleObject | undefined
36
+
37
+ // Boolean/presence attributes (but also strings)
38
+ "data-show"?: string | DatastarFn | boolean | undefined
39
+ "data-ignore"?: string | boolean | undefined
40
+ "data-ignore-morph"?: string | boolean | undefined
41
+
42
+ // Attributes that accept function expressions
43
+ "data-bind"?: string | undefined
44
+ "data-computed"?: string | DatastarFn | undefined
45
+ "data-effect"?: string | DatastarFn | undefined
46
+ "data-indicator"?: string | undefined
47
+ "data-json-signals"?: string | undefined
48
+ "data-on"?: string | DatastarOnFn | undefined
49
+ "data-on-intersect"?: string | DatastarOnFn | undefined
50
+ "data-on-interval"?: string | DatastarOnFn | undefined
51
+ "data-on-load"?: string | DatastarOnFn | undefined
52
+ "data-on-signal-patch"?: string | DatastarOnFn | undefined
53
+ "data-on-signal-patch-filter"?: string | undefined
54
+ "data-preserve-attr"?: string | undefined
55
+ "data-ref"?: string | undefined
56
+ "data-text"?: string | DatastarFn | undefined
57
+
58
+ // Pro attributes
59
+ "data-animate"?: string | undefined
60
+ "data-custom-validity"?: string | DatastarFn | undefined
61
+ "data-on-raf"?: string | DatastarOnFn | undefined
62
+ "data-on-resize"?: string | DatastarOnFn | undefined
63
+ "data-persist"?: string | undefined
64
+ "data-query-string"?: string | undefined
65
+ "data-replace-url"?: string | undefined
66
+ "data-scroll-into-view"?: string | undefined
67
+ "data-view-transition"?: string | undefined
68
+
69
+ // Dynamic attributes with suffixes
70
+ [key: `data-signals:${string}`]: string | undefined
71
+ [key: `data-class:${string}`]: string | DatastarFn | undefined
72
+ [key: `data-attr:${string}`]: string | DatastarFn | undefined
73
+ [key: `data-style:${string}`]: string | DatastarFn | undefined
74
+ [key: `data-bind:${string}`]: string | undefined
75
+ [key: `data-computed:${string}`]: string | DatastarFn | undefined
76
+ [key: `data-indicator:${string}`]: string | undefined
77
+ [key: `data-ref:${string}`]: string | undefined
78
+ [key: `data-on:${string}`]: string | DatastarOnFn | undefined
79
+ }