effect-start 0.9.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.
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/package.json +57 -0
- package/src/Bundle.ts +167 -0
- package/src/BundleFiles.ts +174 -0
- package/src/BundleHttp.test.ts +160 -0
- package/src/BundleHttp.ts +259 -0
- package/src/Commander.test.ts +1378 -0
- package/src/Commander.ts +672 -0
- package/src/Datastar.test.ts +267 -0
- package/src/Datastar.ts +68 -0
- package/src/Effect_HttpRouter.test.ts +570 -0
- package/src/EncryptedCookies.test.ts +427 -0
- package/src/EncryptedCookies.ts +451 -0
- package/src/FileHttpRouter.test.ts +207 -0
- package/src/FileHttpRouter.ts +122 -0
- package/src/FileRouter.ts +405 -0
- package/src/FileRouterCodegen.test.ts +598 -0
- package/src/FileRouterCodegen.ts +251 -0
- package/src/FileRouter_files.test.ts +64 -0
- package/src/FileRouter_path.test.ts +132 -0
- package/src/FileRouter_tree.test.ts +126 -0
- package/src/FileSystemExtra.ts +102 -0
- package/src/HttpAppExtra.ts +127 -0
- package/src/Hyper.ts +194 -0
- package/src/HyperHtml.test.ts +90 -0
- package/src/HyperHtml.ts +139 -0
- package/src/HyperNode.ts +37 -0
- package/src/JsModule.test.ts +14 -0
- package/src/JsModule.ts +116 -0
- package/src/PublicDirectory.test.ts +280 -0
- package/src/PublicDirectory.ts +108 -0
- package/src/Route.test.ts +873 -0
- package/src/Route.ts +992 -0
- package/src/Router.ts +80 -0
- package/src/SseHttpResponse.ts +55 -0
- package/src/Start.ts +133 -0
- package/src/StartApp.ts +43 -0
- package/src/StartHttp.ts +42 -0
- package/src/StreamExtra.ts +146 -0
- package/src/TestHttpClient.test.ts +54 -0
- package/src/TestHttpClient.ts +100 -0
- package/src/bun/BunBundle.test.ts +277 -0
- package/src/bun/BunBundle.ts +309 -0
- package/src/bun/BunBundle_imports.test.ts +50 -0
- package/src/bun/BunFullstackServer.ts +45 -0
- package/src/bun/BunFullstackServer_httpServer.ts +541 -0
- package/src/bun/BunImportTrackerPlugin.test.ts +77 -0
- package/src/bun/BunImportTrackerPlugin.ts +97 -0
- package/src/bun/BunTailwindPlugin.test.ts +335 -0
- package/src/bun/BunTailwindPlugin.ts +322 -0
- package/src/bun/BunVirtualFilesPlugin.ts +59 -0
- package/src/bun/index.ts +4 -0
- package/src/client/Overlay.ts +34 -0
- package/src/client/ScrollState.ts +120 -0
- package/src/client/index.ts +101 -0
- package/src/index.ts +24 -0
- package/src/jsx-datastar.d.ts +63 -0
- package/src/jsx-runtime.ts +23 -0
- package/src/jsx.d.ts +4402 -0
- package/src/testing.ts +55 -0
- package/src/x/cloudflare/CloudflareTunnel.ts +110 -0
- package/src/x/cloudflare/index.ts +1 -0
- package/src/x/datastar/Datastar.test.ts +267 -0
- package/src/x/datastar/Datastar.ts +68 -0
- package/src/x/datastar/index.ts +4 -0
- package/src/x/datastar/jsx-datastar.d.ts +63 -0
package/src/testing.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as Array from "effect/Array"
|
|
2
|
+
import * as Effect from "effect/Effect"
|
|
3
|
+
import * as Function from "effect/Function"
|
|
4
|
+
import * as Layer from "effect/Layer"
|
|
5
|
+
import * as Logger from "effect/Logger"
|
|
6
|
+
import * as Scope from "effect/Scope"
|
|
7
|
+
import type { YieldWrap } from "effect/Utils"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a scoped Effects and runs is asynchronously.
|
|
11
|
+
* Useful for testing.
|
|
12
|
+
*/
|
|
13
|
+
export const effectFn = <RL>(layer?: Layer.Layer<RL, any>) =>
|
|
14
|
+
<
|
|
15
|
+
Eff extends YieldWrap<Effect.Effect<any, any, RE>>,
|
|
16
|
+
AEff,
|
|
17
|
+
RE extends RL | Scope.Scope,
|
|
18
|
+
>(
|
|
19
|
+
f: () => Generator<Eff, AEff, never>,
|
|
20
|
+
): Promise<void> =>
|
|
21
|
+
Function.pipe(
|
|
22
|
+
Effect.gen(f),
|
|
23
|
+
Effect.scoped,
|
|
24
|
+
Effect.provide(Logger.pretty),
|
|
25
|
+
Effect.provide(layer ?? Layer.empty),
|
|
26
|
+
// @ts-expect-error will have to figure out how to clear deps
|
|
27
|
+
Effect.runPromise,
|
|
28
|
+
v => v.then(() => {}, clearStackTraces),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* When effect fails, instead of throwing FiberFailure,
|
|
33
|
+
* throw a plain Error with the strack trace and hides
|
|
34
|
+
* effect internals.
|
|
35
|
+
* Otherwise, at least on Bun, the strack trace is repeated,
|
|
36
|
+
* with some junks in between taking half of the screen.
|
|
37
|
+
*
|
|
38
|
+
* Direct children that starts with a dot are excluded because
|
|
39
|
+
* some tools, like effect-start, use it to generate temporary
|
|
40
|
+
* files that are then loaded into a runtime.
|
|
41
|
+
*/
|
|
42
|
+
const clearStackTraces = (err: any | Error) => {
|
|
43
|
+
const ExternalStackTraceLineRegexp = /\(.*\/node_modules\/[^\.]/
|
|
44
|
+
|
|
45
|
+
const newErr = new Error(err.message)
|
|
46
|
+
const stack: string = err.stack ?? ""
|
|
47
|
+
|
|
48
|
+
newErr.stack = Function.pipe(
|
|
49
|
+
stack.split("\n"),
|
|
50
|
+
Array.takeWhile(s => !ExternalStackTraceLineRegexp.test(s)),
|
|
51
|
+
Array.join("\n"),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
throw newErr
|
|
55
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Command,
|
|
3
|
+
HttpServer,
|
|
4
|
+
} from "@effect/platform"
|
|
5
|
+
import {
|
|
6
|
+
Config,
|
|
7
|
+
Effect,
|
|
8
|
+
identity,
|
|
9
|
+
Layer,
|
|
10
|
+
LogLevel,
|
|
11
|
+
Option,
|
|
12
|
+
pipe,
|
|
13
|
+
Stream,
|
|
14
|
+
String,
|
|
15
|
+
} from "effect"
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Starts Cloudflare tunnel using cloudflared cli.
|
|
19
|
+
*/
|
|
20
|
+
export const start = (opts: {
|
|
21
|
+
command?: string
|
|
22
|
+
tunnelName: string
|
|
23
|
+
tunnelUrl?: string
|
|
24
|
+
cleanLogs?: false
|
|
25
|
+
logLevel?: LogLevel.LogLevel
|
|
26
|
+
logPrefix?: string
|
|
27
|
+
}) =>
|
|
28
|
+
Effect.gen(function*() {
|
|
29
|
+
const logPrefix = String.isString(opts.logPrefix)
|
|
30
|
+
? opts.logPrefix
|
|
31
|
+
: "CloudflareTunnel: "
|
|
32
|
+
const args: string[] = [
|
|
33
|
+
"tunnel",
|
|
34
|
+
"run",
|
|
35
|
+
opts.tunnelUrl
|
|
36
|
+
? [
|
|
37
|
+
"--url",
|
|
38
|
+
opts.tunnelUrl,
|
|
39
|
+
]
|
|
40
|
+
: [],
|
|
41
|
+
opts.tunnelName,
|
|
42
|
+
]
|
|
43
|
+
.flatMap(v => v)
|
|
44
|
+
|
|
45
|
+
const process = yield* pipe(
|
|
46
|
+
Command.make(opts.command ?? "cloudflared", ...args),
|
|
47
|
+
Command.start,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
yield* Effect.logInfo(
|
|
51
|
+
`Cloudflare tunnel started name=${opts.tunnelName} pid=${process.pid} tunnelUrl=${
|
|
52
|
+
opts.tunnelUrl ?? "<empty>"
|
|
53
|
+
}`,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
yield* pipe(
|
|
57
|
+
Stream.merge(
|
|
58
|
+
process.stdout,
|
|
59
|
+
process.stderr,
|
|
60
|
+
),
|
|
61
|
+
Stream.decodeText("utf-8"),
|
|
62
|
+
Stream.splitLines,
|
|
63
|
+
opts.cleanLogs ?? true
|
|
64
|
+
? Stream.map(v =>
|
|
65
|
+
v.replace(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\s\w+\s/, "")
|
|
66
|
+
)
|
|
67
|
+
: identity,
|
|
68
|
+
logPrefix
|
|
69
|
+
? Stream.map(v => logPrefix + v)
|
|
70
|
+
: identity,
|
|
71
|
+
Stream.runForEach(v =>
|
|
72
|
+
Effect.logWithLevel(opts.logLevel ?? LogLevel.Debug, v)
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
export const layer = () =>
|
|
78
|
+
Layer.scopedDiscard(Effect.gen(function*() {
|
|
79
|
+
const tunnelName = yield* pipe(
|
|
80
|
+
Config.string("CLOUDFLARE_TUNNEL_NAME"),
|
|
81
|
+
Config.option,
|
|
82
|
+
Effect.andThen(Option.getOrUndefined),
|
|
83
|
+
)
|
|
84
|
+
const tunnelUrl = yield* pipe(
|
|
85
|
+
Config.string("CLOUDFLARE_TUNNEL_URL"),
|
|
86
|
+
Config.option,
|
|
87
|
+
Effect.andThen(Option.getOrUndefined),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if (!tunnelName) {
|
|
91
|
+
yield* Effect.logWarning("CLOUDFLARE_TUNNEL_NAME not provided. Skipping.")
|
|
92
|
+
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
yield* Effect
|
|
97
|
+
.forkScoped(
|
|
98
|
+
pipe(
|
|
99
|
+
start({
|
|
100
|
+
tunnelName,
|
|
101
|
+
tunnelUrl,
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
104
|
+
)
|
|
105
|
+
.pipe(
|
|
106
|
+
Effect.catchAll(err =>
|
|
107
|
+
Effect.logError("Cloudflare tunnel failed", err)
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
}))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as CloudflareTunnel from "./CloudflareTunnel.ts"
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import * as t from "bun:test"
|
|
2
|
+
import * as HyperHtml from "../../HyperHtml.ts"
|
|
3
|
+
import * as HyperNode from "../../HyperNode.ts"
|
|
4
|
+
import { jsx } from "../../jsx-runtime.ts"
|
|
5
|
+
import * as Datastar from "./Datastar.ts"
|
|
6
|
+
|
|
7
|
+
t.it("data-signals object serialization", () => {
|
|
8
|
+
const node = HyperNode.make("div", {
|
|
9
|
+
"data-signals": { foo: 1, bar: { baz: "hello" } } as any,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
13
|
+
|
|
14
|
+
t
|
|
15
|
+
.expect(html)
|
|
16
|
+
.toBe(
|
|
17
|
+
"<div data-signals=\"{"foo":1,"bar":{"baz":"hello"}}\"></div>",
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
t.it("data-signals string passthrough", () => {
|
|
22
|
+
const node = HyperNode.make("div", {
|
|
23
|
+
"data-signals": "$mySignal",
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
27
|
+
|
|
28
|
+
t
|
|
29
|
+
.expect(html)
|
|
30
|
+
.toBe("<div data-signals=\"$mySignal\"></div>")
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
t.it("data-signals-* object serialization", () => {
|
|
34
|
+
const node = HyperNode.make("div", {
|
|
35
|
+
"data-signals-user": { name: "John", age: 30 } as any,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
39
|
+
|
|
40
|
+
t
|
|
41
|
+
.expect(html)
|
|
42
|
+
.toBe(
|
|
43
|
+
"<div data-signals-user=\"{"name":"John","age":30}\"></div>",
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
t.it("non-data attributes unchanged", () => {
|
|
48
|
+
const node = HyperNode.make("div", {
|
|
49
|
+
id: "test",
|
|
50
|
+
class: "my-class",
|
|
51
|
+
"data-text": "$count",
|
|
52
|
+
"data-signals": { count: 0 } as any,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
56
|
+
|
|
57
|
+
t
|
|
58
|
+
.expect(html)
|
|
59
|
+
.toContain("id=\"test\"")
|
|
60
|
+
t
|
|
61
|
+
.expect(html)
|
|
62
|
+
.toContain("class=\"my-class\"")
|
|
63
|
+
t
|
|
64
|
+
.expect(html)
|
|
65
|
+
.toContain("data-text=\"$count\"")
|
|
66
|
+
t
|
|
67
|
+
.expect(html)
|
|
68
|
+
.toContain("data-signals=\"{"count":0}\"")
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
t.it("null and undefined values ignored", () => {
|
|
72
|
+
const node = HyperNode.make("div", {
|
|
73
|
+
"data-signals": null,
|
|
74
|
+
"data-other": undefined,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
78
|
+
|
|
79
|
+
t
|
|
80
|
+
.expect(html)
|
|
81
|
+
.toBe("<div></div>")
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
t.it("complex nested objects serialization", () => {
|
|
85
|
+
const complexObject = {
|
|
86
|
+
user: { name: "John Doe", preferences: { theme: "dark" } },
|
|
87
|
+
items: [1, 2, 3],
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const node = HyperNode.make("div", {
|
|
91
|
+
"data-signals": complexObject as any,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
95
|
+
|
|
96
|
+
t
|
|
97
|
+
.expect(html)
|
|
98
|
+
.toContain("data-signals=")
|
|
99
|
+
t
|
|
100
|
+
.expect(html)
|
|
101
|
+
.toContain("John Doe")
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
t.it("non-signals data attributes serialized", () => {
|
|
105
|
+
const node = HyperNode.make("div", {
|
|
106
|
+
"data-class": { hidden: true, visible: false } as any,
|
|
107
|
+
"data-style": { color: "red", display: "none" } as any,
|
|
108
|
+
"data-show": true as any,
|
|
109
|
+
"data-text": "$count",
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
113
|
+
|
|
114
|
+
t
|
|
115
|
+
.expect(html)
|
|
116
|
+
.toContain(
|
|
117
|
+
"data-class=\"{"hidden":true,"visible":false}\"",
|
|
118
|
+
)
|
|
119
|
+
t
|
|
120
|
+
.expect(html)
|
|
121
|
+
.toContain(
|
|
122
|
+
"data-style=\"{"color":"red","display":"none"}\"",
|
|
123
|
+
)
|
|
124
|
+
t
|
|
125
|
+
.expect(html)
|
|
126
|
+
.toContain("data-show=\"true\"")
|
|
127
|
+
t
|
|
128
|
+
.expect(html)
|
|
129
|
+
.toContain("data-text=\"$count\"")
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
t.it("data-attr object serialization", () => {
|
|
133
|
+
const node = HyperNode.make("div", {
|
|
134
|
+
"data-attr": { disabled: true, tabindex: 0 } as any,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
138
|
+
|
|
139
|
+
t
|
|
140
|
+
.expect(html)
|
|
141
|
+
.toBe(
|
|
142
|
+
"<div data-attr=\"{"disabled":true,"tabindex":0}\"></div>",
|
|
143
|
+
)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
t.it("boolean attributes converted to strings", () => {
|
|
147
|
+
const node = HyperNode.make("div", {
|
|
148
|
+
"data-ignore": false as any,
|
|
149
|
+
"data-ignore-morph": true as any,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
153
|
+
|
|
154
|
+
t
|
|
155
|
+
.expect(html)
|
|
156
|
+
.not
|
|
157
|
+
.toContain("data-ignore=\"")
|
|
158
|
+
t
|
|
159
|
+
.expect(html)
|
|
160
|
+
.toContain("data-ignore-morph")
|
|
161
|
+
t
|
|
162
|
+
.expect(html)
|
|
163
|
+
.not
|
|
164
|
+
.toContain("data-ignore-morph=")
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
t.it("data-ignore attributes only present when true", () => {
|
|
168
|
+
const nodeTrue = HyperNode.make("div", {
|
|
169
|
+
"data-ignore": true as any,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const nodeFalse = HyperNode.make("div", {
|
|
173
|
+
"data-ignore": false as any,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const htmlTrue = HyperHtml.renderToString(nodeTrue, Datastar.HyperHooks)
|
|
177
|
+
const htmlFalse = HyperHtml.renderToString(nodeFalse, Datastar.HyperHooks)
|
|
178
|
+
|
|
179
|
+
t
|
|
180
|
+
.expect(htmlTrue)
|
|
181
|
+
.toContain("data-ignore")
|
|
182
|
+
t
|
|
183
|
+
.expect(htmlTrue)
|
|
184
|
+
.not
|
|
185
|
+
.toContain("data-ignore=")
|
|
186
|
+
t
|
|
187
|
+
.expect(htmlFalse)
|
|
188
|
+
.not
|
|
189
|
+
.toContain("data-ignore")
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
t.it("dynamic attributes with suffixes", () => {
|
|
193
|
+
const node = HyperNode.make("div", {
|
|
194
|
+
"data-class-active": "hidden" as any,
|
|
195
|
+
"data-attr-tabindex": "5" as any,
|
|
196
|
+
"data-style-opacity": "0.5" as any,
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
200
|
+
|
|
201
|
+
t
|
|
202
|
+
.expect(html)
|
|
203
|
+
.toContain("data-class-active=\"hidden\"")
|
|
204
|
+
t
|
|
205
|
+
.expect(html)
|
|
206
|
+
.toContain("data-attr-tabindex=\"5\"")
|
|
207
|
+
t
|
|
208
|
+
.expect(html)
|
|
209
|
+
.toContain("data-style-opacity=\"0.5\"")
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
t.it("JSX with data-signals object", () => {
|
|
213
|
+
const node = jsx("div", {
|
|
214
|
+
"data-signals": { isOpen: false, count: 42 } as any,
|
|
215
|
+
children: "content",
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
219
|
+
|
|
220
|
+
t
|
|
221
|
+
.expect(html)
|
|
222
|
+
.toBe(
|
|
223
|
+
"<div data-signals=\"{"isOpen":false,"count":42}\">content</div>",
|
|
224
|
+
)
|
|
225
|
+
t
|
|
226
|
+
.expect(html)
|
|
227
|
+
.not
|
|
228
|
+
.toContain("[object Object]")
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
t.it("JSX component returning element with data-signals", () => {
|
|
232
|
+
function TestComponent() {
|
|
233
|
+
return jsx("div", {
|
|
234
|
+
"data-signals": { isOpen: false } as any,
|
|
235
|
+
children: jsx("span", { children: "nested content" }),
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const node = jsx(TestComponent, {})
|
|
240
|
+
|
|
241
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
242
|
+
|
|
243
|
+
t
|
|
244
|
+
.expect(html)
|
|
245
|
+
.toBe(
|
|
246
|
+
"<div data-signals=\"{"isOpen":false}\"><span>nested content</span></div>",
|
|
247
|
+
)
|
|
248
|
+
t
|
|
249
|
+
.expect(html)
|
|
250
|
+
.not
|
|
251
|
+
.toContain("[object Object]")
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
t.it("debug hook execution", () => {
|
|
255
|
+
const node = jsx("div", {
|
|
256
|
+
"data-signals": { isOpen: false, count: 42 } as any,
|
|
257
|
+
children: "content",
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
const html = HyperHtml.renderToString(node, Datastar.HyperHooks)
|
|
261
|
+
console.log("Final HTML:", html)
|
|
262
|
+
|
|
263
|
+
t
|
|
264
|
+
.expect(html)
|
|
265
|
+
.not
|
|
266
|
+
.toContain("[object Object]")
|
|
267
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as HyperNode from "../../HyperNode.ts"
|
|
2
|
+
|
|
3
|
+
export const HyperHooks = {
|
|
4
|
+
onNode,
|
|
5
|
+
} as const
|
|
6
|
+
|
|
7
|
+
function onNode(node: HyperNode.HyperNode) {
|
|
8
|
+
const {
|
|
9
|
+
"data-signals": dataSignals,
|
|
10
|
+
"data-class": dataClass,
|
|
11
|
+
"data-attr": dataAttr,
|
|
12
|
+
"data-style": dataStyle,
|
|
13
|
+
"data-show": dataShow,
|
|
14
|
+
"data-ignore": dataIgnore,
|
|
15
|
+
"data-ignore-morph": dataIgnoreMorph,
|
|
16
|
+
} = node.props as any
|
|
17
|
+
|
|
18
|
+
if (typeof dataSignals === "object" && dataSignals !== null) {
|
|
19
|
+
node.props["data-signals"] = JSON.stringify(dataSignals)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof dataClass === "function") {
|
|
23
|
+
node.props["data-class"] = `(${dataClass.toString()})()`
|
|
24
|
+
} else if (typeof dataClass === "object" && dataClass !== null) {
|
|
25
|
+
node.props["data-class"] = JSON.stringify(dataClass)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof dataAttr === "object" && dataAttr !== null) {
|
|
29
|
+
node.props["data-attr"] = JSON.stringify(dataAttr)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof dataStyle === "function") {
|
|
33
|
+
node.props["data-style"] = `(${dataStyle.toString()})()`
|
|
34
|
+
} else if (typeof dataStyle === "object" && dataStyle !== null) {
|
|
35
|
+
node.props["data-style"] = JSON.stringify(dataStyle)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof dataShow === "boolean") {
|
|
39
|
+
node.props["data-show"] = dataShow.toString()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (dataIgnore !== true && dataIgnore !== undefined) {
|
|
43
|
+
delete node.props["data-ignore"]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (dataIgnoreMorph !== true && dataIgnoreMorph !== undefined) {
|
|
47
|
+
delete node.props["data-ignore-morph"]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle dynamic attributes with suffixes
|
|
51
|
+
for (const [key, value] of Object.entries(node.props)) {
|
|
52
|
+
if (
|
|
53
|
+
key.startsWith("data-signals-")
|
|
54
|
+
&& typeof value === "object"
|
|
55
|
+
&& value !== null
|
|
56
|
+
) {
|
|
57
|
+
node.props[key] = JSON.stringify(value)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (
|
|
61
|
+
key.startsWith("data-on-")
|
|
62
|
+
&& typeof value === "function"
|
|
63
|
+
) {
|
|
64
|
+
// @ts-ignore
|
|
65
|
+
node.props[key] = `(${value.toString()})()`
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Datastar object types for specific attributes
|
|
2
|
+
type DatastarSignalsObject = Record<string, any>
|
|
3
|
+
type DatastarClassObject = Record<string, boolean | string>
|
|
4
|
+
type DatastarAttrObject = Record<string, string | boolean | number>
|
|
5
|
+
type DatastarStyleObject = Record<
|
|
6
|
+
string,
|
|
7
|
+
string | number | boolean | null | undefined
|
|
8
|
+
>
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Datastar attributes for reactive web applications
|
|
12
|
+
* @see https://data-star.dev/reference/attributes
|
|
13
|
+
*/
|
|
14
|
+
export interface DatastarAttributes {
|
|
15
|
+
// Core attributes that can accept objects (but also strings)
|
|
16
|
+
"data-signals"?: string | DatastarSignalsObject | undefined
|
|
17
|
+
"data-class"?: string | DatastarClassObject | undefined
|
|
18
|
+
"data-attr"?: string | DatastarAttrObject | undefined
|
|
19
|
+
"data-style"?: Function | string | DatastarStyleObject | undefined
|
|
20
|
+
|
|
21
|
+
// Boolean/presence attributes (but also strings)
|
|
22
|
+
"data-show"?: string | boolean | undefined
|
|
23
|
+
"data-ignore"?: string | boolean | undefined
|
|
24
|
+
"data-ignore-morph"?: string | boolean | undefined
|
|
25
|
+
|
|
26
|
+
// All other Datastar attributes as strings only
|
|
27
|
+
"data-bind"?: string | undefined
|
|
28
|
+
"data-computed"?: string | undefined
|
|
29
|
+
"data-effect"?: string | undefined
|
|
30
|
+
"data-indicator"?: string | undefined
|
|
31
|
+
"data-json-signals"?: string | undefined
|
|
32
|
+
"data-on"?: string | undefined
|
|
33
|
+
"data-on-intersect"?: string | undefined
|
|
34
|
+
"data-on-interval"?: string | undefined
|
|
35
|
+
"data-on-load"?: string | undefined
|
|
36
|
+
"data-on-signal-patch"?: string | undefined
|
|
37
|
+
"data-on-signal-patch-filter"?: string | undefined
|
|
38
|
+
"data-preserve-attr"?: string | undefined
|
|
39
|
+
"data-ref"?: string | undefined
|
|
40
|
+
"data-text"?: string | undefined
|
|
41
|
+
|
|
42
|
+
// Pro attributes (strings only)
|
|
43
|
+
"data-animate"?: string | undefined
|
|
44
|
+
"data-custom-validity"?: string | undefined
|
|
45
|
+
"data-on-raf"?: string | undefined
|
|
46
|
+
"data-on-resize"?: string | undefined
|
|
47
|
+
"data-persist"?: string | undefined
|
|
48
|
+
"data-query-string"?: string | undefined
|
|
49
|
+
"data-replace-url"?: string | undefined
|
|
50
|
+
"data-scroll-into-view"?: string | undefined
|
|
51
|
+
"data-view-transition"?: string | undefined
|
|
52
|
+
|
|
53
|
+
// Dynamic attributes with suffixes
|
|
54
|
+
[key: `data-signals-${string}`]: string | undefined
|
|
55
|
+
[key: `data-class-${string}`]: string | undefined
|
|
56
|
+
[key: `data-attr-${string}`]: string | undefined
|
|
57
|
+
[key: `data-style-${string}`]: string | undefined
|
|
58
|
+
[key: `data-bind-${string}`]: string | undefined
|
|
59
|
+
[key: `data-computed-${string}`]: string | undefined
|
|
60
|
+
[key: `data-indicator-${string}`]: string | undefined
|
|
61
|
+
[key: `data-ref-${string}`]: string | undefined
|
|
62
|
+
[key: `data-on-${string}`]: Function | string | undefined
|
|
63
|
+
}
|