effect-start 0.32.0 → 0.34.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 (200) hide show
  1. package/dist/Entity.d.ts +6 -1
  2. package/dist/Entity.d.ts.map +1 -1
  3. package/dist/Entity.js +43 -5
  4. package/dist/Entity.js.map +1 -1
  5. package/dist/Fetch.js.map +1 -1
  6. package/dist/FileRouterCodegen.js +2 -2
  7. package/dist/FileRouterCodegen.js.map +1 -1
  8. package/dist/Html.d.ts +5 -11
  9. package/dist/Html.d.ts.map +1 -1
  10. package/dist/Html.js +21 -1
  11. package/dist/Html.js.map +1 -1
  12. package/dist/KeyValueStore.d.ts +37 -0
  13. package/dist/KeyValueStore.d.ts.map +1 -0
  14. package/dist/KeyValueStore.js +99 -0
  15. package/dist/KeyValueStore.js.map +1 -0
  16. package/dist/Route.d.ts +2 -2
  17. package/dist/Route.d.ts.map +1 -1
  18. package/dist/Route.js +1 -1
  19. package/dist/Route.js.map +1 -1
  20. package/dist/RouteBody.d.ts +2 -2
  21. package/dist/RouteBody.d.ts.map +1 -1
  22. package/dist/RouteHttp.d.ts.map +1 -1
  23. package/dist/RouteHttp.js +45 -35
  24. package/dist/RouteHttp.js.map +1 -1
  25. package/dist/RouteMount.d.ts +20 -31
  26. package/dist/RouteMount.d.ts.map +1 -1
  27. package/dist/RouteMount.js +0 -15
  28. package/dist/RouteMount.js.map +1 -1
  29. package/dist/Start.d.ts.map +1 -1
  30. package/dist/Start.js +4 -0
  31. package/dist/Start.js.map +1 -1
  32. package/dist/StaticFiles.d.ts +2 -2
  33. package/dist/StaticFiles.d.ts.map +1 -1
  34. package/dist/StaticFiles.js +7 -8
  35. package/dist/StaticFiles.js.map +1 -1
  36. package/dist/bun/BunRoute.d.ts.map +1 -1
  37. package/dist/bun/BunRoute.js +90 -78
  38. package/dist/bun/BunRoute.js.map +1 -1
  39. package/dist/bun/BunServer.d.ts +1 -1
  40. package/dist/bun/BunServer.d.ts.map +1 -1
  41. package/dist/bun/BunServer.js +8 -1
  42. package/dist/bun/BunServer.js.map +1 -1
  43. package/dist/bundler/BundleRoute.d.ts +4 -4
  44. package/dist/bundler/BundleRoute.d.ts.map +1 -1
  45. package/dist/datastar/attributes/computed.js +3 -3
  46. package/dist/datastar/attributes/computed.js.map +1 -1
  47. package/dist/datastar/attributes/on.js +11 -36
  48. package/dist/datastar/attributes/on.js.map +1 -1
  49. package/dist/datastar/engine.d.ts +9 -7
  50. package/dist/datastar/engine.d.ts.map +1 -1
  51. package/dist/datastar/engine.js +45 -29
  52. package/dist/datastar/engine.js.map +1 -1
  53. package/dist/datastar/jsx.d.ts +70 -0
  54. package/dist/datastar/jsx.d.ts.map +1 -0
  55. package/dist/datastar/jsx.js +2 -0
  56. package/dist/datastar/jsx.js.map +1 -0
  57. package/dist/datastar/window.d.ts +8 -0
  58. package/dist/datastar/window.d.ts.map +1 -0
  59. package/dist/datastar/window.js +4 -0
  60. package/dist/datastar/window.js.map +1 -0
  61. package/dist/experimental/KeyValueStore.d.ts +37 -0
  62. package/dist/experimental/KeyValueStore.d.ts.map +1 -0
  63. package/dist/experimental/KeyValueStore.js +99 -0
  64. package/dist/experimental/KeyValueStore.js.map +1 -0
  65. package/dist/experimental/SqlCache.d.ts +19 -0
  66. package/dist/experimental/SqlCache.d.ts.map +1 -0
  67. package/dist/experimental/SqlCache.js +35 -0
  68. package/dist/experimental/SqlCache.js.map +1 -0
  69. package/dist/experimental/SqlIntrospect.d.ts +92 -0
  70. package/dist/experimental/SqlIntrospect.d.ts.map +1 -0
  71. package/dist/experimental/SqlIntrospect.js +478 -0
  72. package/dist/experimental/SqlIntrospect.js.map +1 -0
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +1 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/jsx-runtime.d.ts +2 -2
  78. package/dist/jsx-runtime.d.ts.map +1 -1
  79. package/dist/jsx.d.ts +3216 -0
  80. package/dist/jsx.d.ts.map +1 -0
  81. package/dist/jsx.js +6 -0
  82. package/dist/jsx.js.map +1 -0
  83. package/dist/lint/plugin.d.ts +4 -3
  84. package/dist/lint/plugin.js +56 -17
  85. package/dist/lint/plugin.js.map +1 -1
  86. package/dist/sql/index.d.ts +0 -2
  87. package/dist/sql/index.d.ts.map +1 -1
  88. package/dist/sql/index.js +0 -2
  89. package/dist/sql/index.js.map +1 -1
  90. package/dist/studio/StudioLogger.d.ts.map +1 -1
  91. package/dist/studio/StudioLogger.js +2 -1
  92. package/dist/studio/StudioLogger.js.map +1 -1
  93. package/dist/studio/StudioStore.d.ts +3 -0
  94. package/dist/studio/StudioStore.d.ts.map +1 -1
  95. package/dist/studio/StudioStore.js +13 -0
  96. package/dist/studio/StudioStore.js.map +1 -1
  97. package/dist/studio/_Pretty.d.ts +4 -0
  98. package/dist/studio/_Pretty.d.ts.map +1 -0
  99. package/dist/studio/_Pretty.js +56 -0
  100. package/dist/studio/_Pretty.js.map +1 -0
  101. package/dist/studio/routes/errors/route.d.ts +2 -2
  102. package/dist/studio/routes/fiberDetail.d.ts +2 -2
  103. package/dist/studio/routes/fibers/route.d.ts +2 -2
  104. package/dist/studio/routes/layout.d.ts +2 -0
  105. package/dist/studio/routes/layout.d.ts.map +1 -1
  106. package/dist/studio/routes/layout.html +3 -12
  107. package/dist/studio/routes/layout.js +6 -1
  108. package/dist/studio/routes/layout.js.map +1 -1
  109. package/dist/studio/routes/logs/route.d.ts +2 -2
  110. package/dist/studio/routes/metrics/route.d.ts +2 -2
  111. package/dist/studio/routes/route.d.ts +2 -2
  112. package/dist/studio/routes/routes/route.d.ts +2 -2
  113. package/dist/studio/routes/services/route.d.ts +2 -2
  114. package/dist/studio/routes/system/route.d.ts +2 -2
  115. package/dist/studio/routes/traceDetail.d.ts +2 -2
  116. package/dist/studio/routes/traces/route.d.ts +2 -2
  117. package/dist/studio/routes/traces/route.d.ts.map +1 -1
  118. package/dist/studio/routes/traces/route.js +5 -2
  119. package/dist/studio/routes/traces/route.js.map +1 -1
  120. package/dist/studio/routes/tree.d.ts +22 -0
  121. package/dist/studio/routes/tree.d.ts.map +1 -1
  122. package/dist/studio/ui/Errors.d.ts +1 -1
  123. package/dist/studio/ui/Errors.js +1 -1
  124. package/dist/studio/ui/Errors.js.map +1 -1
  125. package/dist/studio/ui/Fibers.d.ts +2 -2
  126. package/dist/studio/ui/Fibers.d.ts.map +1 -1
  127. package/dist/studio/ui/Fibers.js +4 -3
  128. package/dist/studio/ui/Fibers.js.map +1 -1
  129. package/dist/studio/ui/Logs.d.ts +1 -1
  130. package/dist/studio/ui/Logs.d.ts.map +1 -1
  131. package/dist/studio/ui/Logs.js +2 -1
  132. package/dist/studio/ui/Logs.js.map +1 -1
  133. package/dist/studio/ui/Metrics.d.ts +1 -1
  134. package/dist/studio/ui/Routes.d.ts +1 -1
  135. package/dist/studio/ui/Routes.d.ts.map +1 -1
  136. package/dist/studio/ui/Services.d.ts +1 -1
  137. package/dist/studio/ui/Services.d.ts.map +1 -1
  138. package/dist/studio/ui/Shell.d.ts +2 -2
  139. package/dist/studio/ui/Shell.d.ts.map +1 -1
  140. package/dist/studio/ui/System.d.ts +1 -1
  141. package/dist/studio/ui/Traces.d.ts +3 -3
  142. package/dist/studio/ui/Traces.d.ts.map +1 -1
  143. package/dist/studio/ui/Traces.js +5 -11
  144. package/dist/studio/ui/Traces.js.map +1 -1
  145. package/dist/studio/ui/_PrettyValue.d.ts +10 -0
  146. package/dist/studio/ui/_PrettyValue.d.ts.map +1 -0
  147. package/dist/studio/ui/_PrettyValue.js +27 -0
  148. package/dist/studio/ui/_PrettyValue.js.map +1 -0
  149. package/dist/tailwind/TailwindPlugin.d.ts.map +1 -1
  150. package/dist/tailwind/TailwindPlugin.js +89 -62
  151. package/dist/tailwind/TailwindPlugin.js.map +1 -1
  152. package/dist/ts/import-plugin.cjs +388 -0
  153. package/dist/ts/import-plugin.cjs.map +1 -0
  154. package/dist/ts/import-plugin.d.cts +87 -0
  155. package/dist/ts/import-plugin.d.cts.map +1 -0
  156. package/dist/ts/import-plugin.d.ts +87 -0
  157. package/dist/ts/import-plugin.d.ts.map +1 -0
  158. package/dist/ts/import-plugin.js +390 -0
  159. package/dist/ts/import-plugin.js.map +1 -0
  160. package/package.json +109 -8
  161. package/src/Entity.ts +62 -8
  162. package/src/Fetch.ts +1 -1
  163. package/src/FileRouterCodegen.ts +2 -2
  164. package/src/Html.ts +28 -21
  165. package/src/Route.ts +2 -2
  166. package/src/RouteBody.ts +2 -2
  167. package/src/RouteHttp.ts +45 -47
  168. package/src/RouteMount.ts +23 -65
  169. package/src/Start.ts +4 -0
  170. package/src/StaticFiles.ts +7 -10
  171. package/src/bun/BunRoute.ts +117 -95
  172. package/src/bun/BunServer.ts +9 -2
  173. package/src/datastar/README.md +24 -8
  174. package/src/datastar/attributes/computed.ts +3 -3
  175. package/src/datastar/attributes/on.ts +11 -37
  176. package/src/datastar/engine.ts +61 -37
  177. package/src/datastar/jsx.d.ts +12 -26
  178. package/src/datastar/types.d.ts +8 -0
  179. package/src/experimental/KeyValueStore.ts +161 -0
  180. package/src/{sql → experimental}/SqlCache.ts +1 -1
  181. package/src/{sql → experimental}/SqlIntrospect.ts +1 -1
  182. package/src/index.ts +1 -0
  183. package/src/jsx-runtime.ts +1 -1
  184. package/src/jsx.d.ts +17 -2
  185. package/src/lint/plugin.js +54 -19
  186. package/src/sql/index.ts +0 -2
  187. package/src/studio/StudioLogger.ts +2 -1
  188. package/src/studio/StudioStore.ts +18 -0
  189. package/src/studio/_Pretty.ts +59 -0
  190. package/src/studio/routes/layout.html +3 -12
  191. package/src/studio/routes/layout.tsx +9 -1
  192. package/src/studio/routes/traces/route.tsx +5 -1
  193. package/src/studio/ui/Errors.tsx +1 -1
  194. package/src/studio/ui/Fibers.tsx +14 -10
  195. package/src/studio/ui/Logs.tsx +15 -10
  196. package/src/studio/ui/Traces.tsx +40 -68
  197. package/src/studio/ui/_PrettyValue.tsx +34 -0
  198. package/src/tailwind/TailwindPlugin.ts +102 -75
  199. package/src/RouteTrie.ts +0 -205
  200. package/src/experimental/index.ts +0 -1
@@ -1,5 +1,4 @@
1
1
  import * as Bun from "bun"
2
- import * as NPath from "node:path"
3
2
  import * as Data from "effect/Data"
4
3
  import * as Either from "effect/Either"
5
4
  import * as Effect from "effect/Effect"
@@ -87,54 +86,21 @@ export function htmlBundle(load: () => HTMLBundleModule | Promise<HTMLBundleModu
87
86
  let status = 200
88
87
  let contentType = "text/html;charset=utf-8"
89
88
 
90
- if (bundleDepth === 0) {
91
- const bunServer = yield* BunServer.BunServer
92
- const url = new URL(originalRequest.url)
93
-
94
- const internalPath = `${bunPrefix}${url.pathname}`
95
- const internalUrl = new URL(internalPath, bunServer.server.url)
96
-
97
- const headers = new Headers(originalRequest.headers)
98
- headers.set(INTERNAL_FETCH_HEADER, "true")
99
-
100
- const proxyRequest = new Request(internalUrl, {
101
- method: originalRequest.method,
102
- headers,
103
- })
104
-
105
- const response = yield* Effect.tryPromise({
106
- try: () => fetch(proxyRequest),
107
- catch: (error) =>
108
- new BunRouteError({
109
- reason: "ProxyError",
110
- pattern: internalPath,
111
- message: `Failed to fetch internal HTML bundle: ${String(error)}`,
112
- }),
113
- })
114
-
115
- html = yield* Effect.tryPromise({
116
- try: () => response.text(),
117
- catch: (error) =>
118
- new BunRouteError({
119
- reason: "ProxyError",
120
- pattern: internalPath,
121
- message: String(error),
122
- }),
123
- })
124
- status = response.status
125
- contentType = response.headers.get("content-type") ?? contentType
126
- } else {
127
- html = yield* readBundleHtml(bunLoad).pipe(
128
- Effect.mapError(
129
- (error) =>
130
- new BunRouteError({
131
- reason: "ProxyError",
132
- pattern: bunPrefix,
133
- message: `Failed to load nested HTML bundle: ${String(error)}`,
134
- }),
135
- ),
136
- )
137
- }
89
+ const response = yield* fetchBundleResponse(
90
+ bunPrefix,
91
+ originalRequest,
92
+ )
93
+ html = yield* Effect.tryPromise({
94
+ try: () => response.text(),
95
+ catch: (error) =>
96
+ new BunRouteError({
97
+ reason: "ProxyError",
98
+ pattern: bunPrefix,
99
+ message: String(error),
100
+ }),
101
+ })
102
+ status = response.status
103
+ contentType = response.headers.get("content-type") ?? contentType
138
104
 
139
105
  const childEntity = yield* next(context).pipe(
140
106
  Effect.locally(bundleDepthRef, bundleDepth + 1),
@@ -162,7 +128,7 @@ export function htmlBundle(load: () => HTMLBundleModule | Promise<HTMLBundleModu
162
128
  }
163
129
  }
164
130
 
165
- childrenHtml = yield* stripInjectedBunScripts(childrenHtml)
131
+ childrenHtml = yield* stripDuplicateBunScripts(html, childrenHtml)
166
132
 
167
133
  html = html.replaceAll("%children%", childrenHtml)
168
134
 
@@ -186,60 +152,82 @@ export function htmlBundle(load: () => HTMLBundleModule | Promise<HTMLBundleModu
186
152
  }
187
153
  }
188
154
 
189
- function stripInjectedBunScripts(html: string) {
190
- let removeNextInlineScript = false
191
- const rewriter = new HTMLRewriter().on("script", {
192
- element(element) {
193
- const src = element.getAttribute("src")
194
- const hasDevAttribute = element.getAttribute("data-bun-dev-server-script") !== null
195
-
196
- if (hasDevAttribute || (src !== null && src.startsWith("/_bun/client/"))) {
197
- element.remove()
198
- removeNextInlineScript = true
199
- return
200
- }
155
+ function fetchBundleResponse(
156
+ bunPrefix: string,
157
+ originalRequest: Request,
158
+ ) {
159
+ return Effect.gen(function* () {
160
+ const bunServer = yield* BunServer.BunServer
161
+ const url = new URL(originalRequest.url)
162
+ const internalUrl = new URL(bunServer.server.url)
163
+
164
+ internalUrl.pathname = `${bunPrefix}${url.pathname}`
165
+ internalUrl.search = url.search
166
+
167
+ const headers = new Headers(originalRequest.headers)
168
+ headers.set(INTERNAL_FETCH_HEADER, "true")
169
+
170
+ const proxyRequest = new Request(internalUrl, {
171
+ method: originalRequest.method,
172
+ headers,
173
+ })
174
+
175
+ return yield* Effect.tryPromise({
176
+ try: () => fetch(proxyRequest),
177
+ catch: (error) =>
178
+ new BunRouteError({
179
+ reason: "ProxyError",
180
+ pattern: internalUrl.pathname,
181
+ message: `Failed to fetch internal HTML bundle: ${String(error)}`,
182
+ }),
183
+ })
184
+ })
185
+ }
201
186
 
202
- if (removeNextInlineScript && src === null) {
203
- element.remove()
204
- removeNextInlineScript = false
205
- return
187
+ function stripDuplicateBunScripts(parentHtml: string, childHtml: string) {
188
+ return getInjectedBunScriptSrcs(parentHtml).pipe(
189
+ Effect.flatMap((parentScriptSrcs) => {
190
+ if (parentScriptSrcs.size === 0) {
191
+ return Effect.succeed(childHtml)
206
192
  }
207
193
 
208
- removeNextInlineScript = false
209
- },
210
- })
194
+ let removeNextInlineScript = false
195
+ const rewriter = new HTMLRewriter().on("script", {
196
+ element(element) {
197
+ const src = element.getAttribute("src")
198
+ const hasDevAttribute = element.hasAttribute("data-bun-dev-server-script")
211
199
 
212
- return Effect.tryPromise({
213
- try: () => rewriter.transform(new Response(html)).text(),
214
- catch: (error) =>
215
- new BunRouteError({
216
- reason: "ProxyError",
217
- pattern: "stripInjectedBunScripts",
218
- message: String(error),
219
- }),
220
- })
221
- }
200
+ if (src !== null && hasDevAttribute && parentScriptSrcs.has(src)) {
201
+ element.remove()
202
+ removeNextInlineScript = true
203
+ return
204
+ }
205
+
206
+ if (removeNextInlineScript && src === null) {
207
+ element.remove()
208
+ removeNextInlineScript = false
209
+ return
210
+ }
211
+
212
+ removeNextInlineScript = false
213
+ },
214
+ })
222
215
 
223
- function readBundleHtml(bunLoad: () => Promise<Bun.HTMLBundle>) {
224
- return Effect.tryPromise({
225
- try: () => bunLoad(),
226
- catch: (error) =>
227
- new BunRouteError({
228
- reason: "ProxyError",
229
- pattern: "readBundleHtml",
230
- message: String(error),
231
- }),
232
- }).pipe(
233
- Effect.andThen((bundle) => {
234
- const indexPath = NPath.isAbsolute(bundle.index)
235
- ? bundle.index
236
- : NPath.resolve(NPath.dirname(Bun.main), bundle.index)
237
216
  return Effect.tryPromise({
238
- try: () => Bun.file(indexPath).text(),
217
+ try: () =>
218
+ rewriter
219
+ .transform(
220
+ new Response(childHtml, {
221
+ headers: {
222
+ "content-type": "text/html;charset=utf-8",
223
+ },
224
+ }),
225
+ )
226
+ .text(),
239
227
  catch: (error) =>
240
228
  new BunRouteError({
241
229
  reason: "ProxyError",
242
- pattern: indexPath,
230
+ pattern: "stripDuplicateBunScripts",
243
231
  message: String(error),
244
232
  }),
245
233
  })
@@ -247,6 +235,40 @@ function readBundleHtml(bunLoad: () => Promise<Bun.HTMLBundle>) {
247
235
  )
248
236
  }
249
237
 
238
+ function getInjectedBunScriptSrcs(html: string) {
239
+ return Effect.tryPromise({
240
+ try: async () => {
241
+ const srcs = new Set<string>()
242
+ const rewriter = new HTMLRewriter().on("script", {
243
+ element(element) {
244
+ const src = element.getAttribute("src")
245
+ if (src !== null && element.hasAttribute("data-bun-dev-server-script")) {
246
+ srcs.add(src)
247
+ }
248
+ },
249
+ })
250
+
251
+ await rewriter
252
+ .transform(
253
+ new Response(html, {
254
+ headers: {
255
+ "content-type": "text/html;charset=utf-8",
256
+ },
257
+ }),
258
+ )
259
+ .text()
260
+
261
+ return srcs
262
+ },
263
+ catch: (error) =>
264
+ new BunRouteError({
265
+ reason: "ProxyError",
266
+ pattern: "getInjectedBunScriptSrcs",
267
+ message: String(error),
268
+ }),
269
+ })
270
+ }
271
+
250
272
  type BunServerFetchHandler = (
251
273
  request: Request,
252
274
  server: Bun.Server<unknown>,
@@ -6,6 +6,7 @@ import * as Deferred from "effect/Deferred"
6
6
  import * as Effect from "effect/Effect"
7
7
  import * as Exit from "effect/Exit"
8
8
  import * as Layer from "effect/Layer"
9
+ import * as MutableRef from "effect/MutableRef"
9
10
  import * as Option from "effect/Option"
10
11
  import * as Runtime from "effect/Runtime"
11
12
  import type * as Scope from "effect/Scope"
@@ -43,7 +44,7 @@ interface BunServeOptions {
43
44
  readonly reusePort?: boolean
44
45
  readonly ipv6Only?: boolean
45
46
  readonly idleTimeout?: number
46
- readonly development?: boolean
47
+ readonly development?: Bun.Serve.Development
47
48
  }
48
49
 
49
50
  export type BunServer = {
@@ -145,9 +146,15 @@ export const make = (
145
146
  // @ts-expect-error
146
147
  service.server = server
147
148
 
149
+ const myFiber = MutableRef.get(PlatformRuntime.mainFiber)
148
150
  yield* Effect.addFinalizer(() =>
149
151
  Effect.sync(() => {
150
- server.stop()
152
+ // Only stop the server on real shutdown; and not on hot reloads
153
+ // when Bun.serve automatically swaps routes without restarting the server
154
+ const currentMain = MutableRef.get(PlatformRuntime.mainFiber)
155
+ if (currentMain === myFiber) {
156
+ server.stop()
157
+ }
151
158
  }),
152
159
  )
153
160
 
@@ -9,15 +9,31 @@ We can probably cut it down by another ~10kb if we remove DataStar expression an
9
9
  We made following changes:
10
10
 
11
11
  - Path aliases converted to relative imports: `@engine/*` → `./engine/*`, etc.
12
- - Flattened `plugins/` directory: `plugins/actions/` `actions/`, etc.
13
- - Deleted the `ALIAS` type declaration: removed `globals.d.ts`,
14
- no alias conditional in `utils/text.ts` & `applyAttributePlugin` in `engine.ts`.
12
+ - Flattened and merged the source tree:
13
+ - `plugins/actions|attributes|watchers` `actions|attributes|watchers`
14
+ - `engine/{consts,types,signals,engine}.ts` `engine.ts`
15
+ - `utils/{dom,math,paths,polyfills,tags,text,timing,view-transitions}.ts` → `utils.ts`
16
+ - Replaced upstream `bundles/` entrypoints with local `index.ts`:
17
+ - Registers all plugins as side effects
18
+ - Re-exports all of `engine.ts` instead of upstream's narrower bundle export surface
19
+ - Removed alias build support:
20
+ - Deleted `globals.d.ts`
21
+ - `aliasify()` always produces `data-${name}`
22
+ - `applyAttributePlugin` no longer accepts aliased `data-${ALIAS}-*` attributes
15
23
  - Removed plugin header comments.
16
24
  - Updated type declaration to conform to `erasableSyntaxOnly`:
17
25
  - Converted `enum ReactiveFlags` and `enum EffectFlags` to `const` objects with `as const`
18
26
  - 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)
27
+ - Extended expressions with function form handled by `genRx`:
28
+ - Function expressions are evaluated directly instead of compiled through Datastar's string-expression transform
29
+ - Function expressions receive a `DataEvent` object with `signals`, `actions`, `target`, and `window`
30
+ - Shared expression-event setup is centralized in `createDataEvent()` and reused by both `genRx` and `data-computed`
31
+ - Value-returning object literals are compiled as `return ({...});` so block-bodied arrow functions inside object expressions still parse
32
+ - `data-computed` supports object values with function leaves:
33
+ - String/object-literal form still works: `data-computed="{statusText: (e) => e.signals.state}"`
34
+ - JSX object form also works: `data-computed={{ statusText: (e) => e.signals.state }}`
35
+ - Object values only accept functions at the leaves
36
+ - JSX typing allows `data-computed` object values
37
+ - `data-on` supports function form:
38
+ - Function form: `data-on:click="(e) => { e.signals.count = e.signals.count + 1 }"`
39
+ - Event names are used literally from the attribute key; upstream `modifyCasing(..., "kebab")` normalization is no longer applied
@@ -1,4 +1,4 @@
1
- import { attribute, computed, mergePatch, mergePaths } from "../engine.ts"
1
+ import { attribute, computed, createDataEvent, mergePatch, mergePaths } from "../engine.ts"
2
2
  import { modifyCasing, updateLeaves } from "../utils.ts"
3
3
 
4
4
  attribute({
@@ -7,14 +7,14 @@ attribute({
7
7
  value: "must",
8
8
  },
9
9
  returnsValue: true,
10
- apply({ key, mods, rx, error }) {
10
+ apply({ el, key, mods, rx, error }) {
11
11
  if (key) {
12
12
  mergePaths([[modifyCasing(key, mods), computed(rx)]])
13
13
  } else {
14
14
  const patch = Object.assign({}, rx() as Record<string, () => any>)
15
15
  updateLeaves(patch, (old) => {
16
16
  if (typeof old === "function") {
17
- return computed(old)
17
+ return computed(() => old(createDataEvent({ el, cleanups: new Map(), error: () => error })))
18
18
  } else {
19
19
  throw error("ComputedExpectedFunction")
20
20
  }
@@ -4,59 +4,33 @@ import {
4
4
  DATASTAR_FETCH_EVENT,
5
5
  DATASTAR_SIGNAL_PATCH_EVENT,
6
6
  endBatch,
7
- type Modifiers,
8
7
  } from "../engine.ts"
9
8
  import { modifyTiming, modifyViewTransition } from "../utils.ts"
10
9
 
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
- }
24
-
25
10
  attribute({
26
11
  name: "on",
27
12
  requirement: "must",
28
13
  argNames: ["evt"],
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
-
14
+ apply({ el, key, mods, rx }) {
41
15
  let target: Element | Window | Document = el
42
- if (effectiveMods.has("window")) target = window
16
+ if (mods.has("window")) target = window
43
17
  let callback = (evt?: Event) => {
44
18
  if (evt) {
45
- if (effectiveMods.has("prevent")) evt.preventDefault()
46
- if (effectiveMods.has("stop")) evt.stopPropagation()
19
+ if (mods.has("prevent")) evt.preventDefault()
20
+ if (mods.has("stop")) evt.stopPropagation()
47
21
  }
48
22
  beginBatch()
49
- userFn ? userFn(evt) : rx(evt)
23
+ rx(evt)
50
24
  endBatch()
51
25
  }
52
- callback = modifyViewTransition(callback, effectiveMods)
53
- callback = modifyTiming(callback, effectiveMods)
26
+ callback = modifyViewTransition(callback, mods)
27
+ callback = modifyTiming(callback, mods)
54
28
  const evtListOpts: AddEventListenerOptions = {
55
- capture: effectiveMods.has("capture"),
56
- passive: effectiveMods.has("passive"),
57
- once: effectiveMods.has("once"),
29
+ capture: mods.has("capture"),
30
+ passive: mods.has("passive"),
31
+ once: mods.has("once"),
58
32
  }
59
- if (effectiveMods.has("outside")) {
33
+ if (mods.has("outside")) {
60
34
  target = document
61
35
  const cb = callback
62
36
  callback = (evt?: Event) => {
@@ -1,3 +1,4 @@
1
+ import type { DataEvent as _DataEvent, HTMLOrSVG as _HTMLOrSVG } from "./types.d.ts"
1
2
  import { aliasify, hasOwn, isHTMLOrSVG, isPojo, pathToObj, snake } from "./utils.ts"
2
3
 
3
4
  /*********
@@ -115,13 +116,42 @@ export type MergePatchArgs = {
115
116
  ifMissing?: boolean
116
117
  }
117
118
 
118
- export type HTMLOrSVG = HTMLElement | SVGElement | MathMLElement
119
+ export type HTMLOrSVG = _HTMLOrSVG
120
+ export type DataEvent = _DataEvent
119
121
 
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
122
+ export const createDataEvent = ({
123
+ el,
124
+ evt,
125
+ cleanups,
126
+ error,
127
+ }: {
128
+ el: HTMLOrSVG
129
+ evt?: Event
130
+ cleanups: Map<string, () => void>
131
+ error: (actionName: string) => ErrorFn
132
+ }): DataEvent => {
133
+ const actionsProxy = new Proxy({} as Record<string, any>, {
134
+ get:
135
+ (_, name: string) =>
136
+ (...actionArgs: Array<any>) => {
137
+ const err = error(name)
138
+ const fn = actions[name]
139
+ if (fn) {
140
+ return fn({ el, evt: undefined, error: err, cleanups }, ...actionArgs)
141
+ }
142
+ throw err("UndefinedAction")
143
+ },
144
+ })
145
+
146
+ const dataEvt = (evt instanceof Event ? evt : new Event("datastar:expression")) as DataEvent
147
+ Object.defineProperties(dataEvt, {
148
+ target: { value: el },
149
+ signals: { value: root },
150
+ actions: { value: actionsProxy },
151
+ window: { value: window },
152
+ })
153
+
154
+ return dataEvt
125
155
  }
126
156
 
127
157
  export type Modifiers = Map<string, Set<string>>
@@ -1092,6 +1122,7 @@ const applyAttributePlugin = (
1092
1122
  const { pluginName, key, mods } = parseAttributeKey(rawKey)
1093
1123
  const plugin = attributePlugins.get(pluginName)
1094
1124
  if ((!onlyNew || queuedAttributeNames.has(pluginName)) && plugin) {
1125
+ const cleanups = new Map<string, () => void>()
1095
1126
  const ctx = {
1096
1127
  el,
1097
1128
  rawKey,
@@ -1147,7 +1178,6 @@ const applyAttributePlugin = (
1147
1178
  }
1148
1179
  }
1149
1180
 
1150
- const cleanups = new Map<string, () => void>()
1151
1181
  if (valueProvided) {
1152
1182
  let cachedRx: GenRxFn
1153
1183
  ctx.rx = (...args: Array<any>) => {
@@ -1199,27 +1229,16 @@ const genRx = (
1199
1229
  const userFn = Function(`return (${value.trim()})`)()
1200
1230
 
1201
1231
  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 },
1232
+ const dataEvt = createDataEvent({
1233
+ el,
1234
+ evt: args[0],
1235
+ cleanups,
1236
+ error: (name) =>
1237
+ error.bind(0, {
1238
+ plugin: { type: "action", name },
1239
+ element: { id: el.id, tag: el.tagName },
1240
+ expression: { fnContent: value, value },
1241
+ }),
1223
1242
  })
1224
1243
 
1225
1244
  try {
@@ -1240,16 +1259,21 @@ const genRx = (
1240
1259
 
1241
1260
  let expr = ""
1242
1261
  if (returnsValue) {
1243
- const statementRe =
1244
- /(\/(\\\/|[^/])*\/|"(\\"|[^"])*"|'(\\'|[^'])*'|`(\\`|[^`])*`|\(\s*((function)\s*\(\s*\)|(\(\s*\))\s*=>)\s*(?:\{[\s\S]*?\}|[^;){]*)\s*\)\s*\(\s*\)|[^;])+/gm
1245
- const statements = value.trim().match(statementRe)
1246
- if (statements) {
1247
- const lastIdx = statements.length - 1
1248
- const last = statements[lastIdx].trim()
1249
- if (!last.startsWith("return")) {
1250
- statements[lastIdx] = `return (${last});`
1262
+ const trimmed = value.trim()
1263
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
1264
+ expr = `return (${trimmed});`
1265
+ } else {
1266
+ const statementRe =
1267
+ /(\/(\\\/|[^/])*\/|"(\\"|[^"])*"|'(\\'|[^'])*'|`(\\`|[^`])*`|\(\s*((function)\s*\(\s*\)|(\(\s*\))\s*=>)\s*(?:\{[\s\S]*?\}|[^;){]*)\s*\)\s*\(\s*\)|[^;])+/gm
1268
+ const statements = trimmed.match(statementRe)
1269
+ if (statements) {
1270
+ const lastIdx = statements.length - 1
1271
+ const last = statements[lastIdx].trim()
1272
+ if (!last.startsWith("return")) {
1273
+ statements[lastIdx] = `return (${last});`
1274
+ }
1275
+ expr = statements.join(";\n")
1251
1276
  }
1252
- expr = statements.join(";\n")
1253
1277
  }
1254
1278
  } else {
1255
1279
  expr = value.trim()
@@ -1,4 +1,4 @@
1
- import type { DataEvent } from "./engine.ts"
1
+ import type { DataEvent } from "./types.d.ts"
2
2
 
3
3
  // Datastar object types for specific attributes
4
4
  type DatastarSignalsObject = Record<string, any>
@@ -8,21 +8,6 @@ type DatastarStyleObject = Record<string, string | number | boolean | null | und
8
8
 
9
9
  type DatastarFn = (e: DataEvent) => any
10
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
11
  /**
27
12
  * Datastar attributes for reactive web applications
28
13
  * @see https://data-star.dev/reference/attributes
@@ -41,15 +26,16 @@ export interface DatastarAttributes {
41
26
 
42
27
  // Attributes that accept function expressions
43
28
  "data-bind"?: string | undefined
44
- "data-computed"?: string | DatastarFn | undefined
29
+ "data-computed"?: string | DatastarFn | Record<string, DatastarFn | Record<string, DatastarFn>> | undefined
45
30
  "data-effect"?: string | DatastarFn | undefined
31
+ "data-init"?: string | DatastarFn | undefined
46
32
  "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
33
+ "data-json-signals"?: true | string | undefined
34
+ "data-on"?: string | DatastarFn | undefined
35
+ "data-on-intersect"?: string | DatastarFn | undefined
36
+ "data-on-interval"?: string | DatastarFn | undefined
37
+ "data-on-load"?: string | DatastarFn | undefined
38
+ "data-on-signal-patch"?: string | DatastarFn | undefined
53
39
  "data-on-signal-patch-filter"?: string | undefined
54
40
  "data-preserve-attr"?: string | undefined
55
41
  "data-ref"?: string | undefined
@@ -58,8 +44,8 @@ export interface DatastarAttributes {
58
44
  // Pro attributes
59
45
  "data-animate"?: string | undefined
60
46
  "data-custom-validity"?: string | DatastarFn | undefined
61
- "data-on-raf"?: string | DatastarOnFn | undefined
62
- "data-on-resize"?: string | DatastarOnFn | undefined
47
+ "data-on-raf"?: string | DatastarFn | undefined
48
+ "data-on-resize"?: string | DatastarFn | undefined
63
49
  "data-persist"?: string | undefined
64
50
  "data-query-string"?: string | undefined
65
51
  "data-replace-url"?: string | undefined
@@ -75,5 +61,5 @@ export interface DatastarAttributes {
75
61
  [key: `data-computed:${string}`]: string | DatastarFn | undefined
76
62
  [key: `data-indicator:${string}`]: string | undefined
77
63
  [key: `data-ref:${string}`]: string | undefined
78
- [key: `data-on:${string}`]: string | DatastarOnFn | undefined
64
+ [key: `data-on:${string}`]: string | DatastarFn | undefined
79
65
  }
@@ -0,0 +1,8 @@
1
+ export type HTMLOrSVG = HTMLElement | SVGElement | MathMLElement
2
+
3
+ export type DataEvent = Event & {
4
+ signals: Record<string, any>
5
+ actions: Record<string, (...args: Array<any>) => any>
6
+ target: HTMLOrSVG
7
+ window: Window & typeof globalThis
8
+ }