effect-start 0.14.0 → 0.16.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/package.json +8 -9
- package/src/Commander.test.ts +507 -245
- package/src/ContentNegotiation.test.ts +603 -0
- package/src/ContentNegotiation.ts +542 -0
- package/src/Entity.test.ts +592 -0
- package/src/Entity.ts +362 -0
- package/src/FileRouter.ts +16 -12
- package/src/{FileRouterCodegen.test.ts → FileRouterCodegen.todo.ts} +384 -219
- package/src/FileRouterCodegen.ts +6 -6
- package/src/FileRouterPattern.test.ts +93 -62
- package/src/FileRouter_files.test.ts +5 -5
- package/src/FileRouter_path.test.ts +121 -69
- package/src/FileRouter_tree.test.ts +62 -56
- package/src/FileSystemExtra.test.ts +46 -30
- package/src/Http.test.ts +319 -0
- package/src/Http.ts +167 -0
- package/src/HttpAppExtra.test.ts +39 -20
- package/src/HttpAppExtra.ts +0 -1
- package/src/HttpUtils.test.ts +35 -18
- package/src/HttpUtils.ts +2 -0
- package/src/PathPattern.test.ts +648 -0
- package/src/PathPattern.ts +485 -0
- package/src/Route.ts +266 -1069
- package/src/RouteBody.test.ts +234 -0
- package/src/RouteBody.ts +193 -0
- package/src/RouteHook.test.ts +40 -0
- package/src/RouteHook.ts +106 -0
- package/src/RouteHttp.test.ts +2906 -0
- package/src/RouteHttp.ts +427 -0
- package/src/RouteHttpTracer.ts +92 -0
- package/src/RouteMount.test.ts +481 -0
- package/src/RouteMount.ts +470 -0
- package/src/RouteSchema.test.ts +427 -0
- package/src/RouteSchema.ts +423 -0
- package/src/RouteTree.test.ts +494 -0
- package/src/RouteTree.ts +219 -0
- package/src/RouteTrie.test.ts +322 -0
- package/src/RouteTrie.ts +224 -0
- package/src/RouterPattern.test.ts +569 -548
- package/src/RouterPattern.ts +7 -7
- package/src/Start.ts +3 -3
- package/src/StreamExtra.ts +21 -1
- package/src/TuplePathPattern.ts +64 -0
- package/src/Values.test.ts +263 -0
- package/src/Values.ts +76 -0
- package/src/bun/BunBundle.test.ts +36 -42
- package/src/bun/BunBundle.ts +2 -2
- package/src/bun/BunBundle_imports.test.ts +4 -6
- package/src/bun/BunHttpServer.test.ts +183 -6
- package/src/bun/BunHttpServer.ts +72 -32
- package/src/bun/BunHttpServer_web.ts +18 -6
- package/src/bun/BunImportTrackerPlugin.test.ts +3 -3
- package/src/bun/BunRoute.test.ts +124 -442
- package/src/bun/BunRoute.ts +146 -286
- package/src/{BundleHttp.test.ts → bundler/BundleHttp.test.ts} +34 -60
- package/src/{BundleHttp.ts → bundler/BundleHttp.ts} +1 -2
- package/src/client/index.ts +1 -1
- package/src/{Effect_HttpRouter.test.ts → effect/HttpRouter.test.ts} +69 -90
- package/src/experimental/EncryptedCookies.test.ts +125 -64
- package/src/experimental/SseHttpResponse.ts +0 -1
- package/src/hyper/Hyper.ts +89 -0
- package/src/{HyperHtml.test.ts → hyper/HyperHtml.test.ts} +13 -13
- package/src/{HyperHtml.ts → hyper/HyperHtml.ts} +2 -2
- package/src/{jsx.d.ts → hyper/jsx.d.ts} +1 -1
- package/src/index.ts +3 -4
- package/src/middlewares/BasicAuthMiddleware.test.ts +29 -19
- package/src/{NodeFileSystem.ts → node/FileSystem.ts} +6 -2
- package/src/testing/TestHttpClient.test.ts +26 -26
- package/src/testing/TestLogger.test.ts +27 -14
- package/src/testing/TestLogger.ts +15 -9
- package/src/x/datastar/Datastar.test.ts +47 -48
- package/src/x/datastar/Datastar.ts +1 -1
- package/src/x/tailwind/TailwindPlugin.test.ts +56 -58
- package/src/x/tailwind/plugin.ts +1 -1
- package/src/FileHttpRouter.test.ts +0 -239
- package/src/FileHttpRouter.ts +0 -194
- package/src/Hyper.ts +0 -194
- package/src/Route.test.ts +0 -1370
- package/src/RouteRender.ts +0 -40
- package/src/Router.test.ts +0 -375
- package/src/Router.ts +0 -255
- package/src/bun/BunRoute_bundles.test.ts +0 -219
- /package/src/{Bundle.ts → bundler/Bundle.ts} +0 -0
- /package/src/{BundleFiles.ts → bundler/BundleFiles.ts} +0 -0
- /package/src/{HyperNode.ts → hyper/HyperNode.ts} +0 -0
- /package/src/{jsx-runtime.ts → hyper/jsx-runtime.ts} +0 -0
- /package/src/{NodeUtils.ts → node/Utils.ts} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as Cookies from "@effect/platform/Cookies"
|
|
2
|
-
import * as
|
|
2
|
+
import * as test from "bun:test"
|
|
3
3
|
import * as ConfigProvider from "effect/ConfigProvider"
|
|
4
4
|
import * as Effect from "effect/Effect"
|
|
5
5
|
import * as EncryptedCookies from "./EncryptedCookies.ts"
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
test.describe(`${EncryptedCookies.encrypt.name}`, () => {
|
|
8
|
+
test.test("return encrypted string in correct format", async () => {
|
|
9
9
|
const value = "hello world"
|
|
10
10
|
|
|
11
11
|
const result = await Effect.runPromise(
|
|
@@ -14,20 +14,28 @@ t.describe(`${EncryptedCookies.encrypt.name}`, () => {
|
|
|
14
14
|
|
|
15
15
|
// Check format: three base64url segments separated by .
|
|
16
16
|
const segments = result.split(".")
|
|
17
|
-
|
|
17
|
+
test
|
|
18
|
+
.expect(segments)
|
|
19
|
+
.toHaveLength(3)
|
|
18
20
|
|
|
19
21
|
// Each segment should be base64url (no +, /, or = characters
|
|
20
22
|
// so cookie values are not escaped)
|
|
21
23
|
segments.forEach((segment: string) => {
|
|
22
|
-
|
|
24
|
+
test
|
|
25
|
+
.expect(segment)
|
|
26
|
+
.not
|
|
27
|
+
.toMatch(/[+/=]/)
|
|
23
28
|
// Should be valid base64url that can be decoded
|
|
24
29
|
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/")
|
|
25
30
|
const paddedBase64 = base64 + "=".repeat((4 - base64.length % 4) % 4)
|
|
26
|
-
|
|
31
|
+
test
|
|
32
|
+
.expect(() => atob(paddedBase64))
|
|
33
|
+
.not
|
|
34
|
+
.toThrow()
|
|
27
35
|
})
|
|
28
36
|
})
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
test.test("produce different results for same input due to random IV", async () => {
|
|
31
39
|
const value = "same value"
|
|
32
40
|
|
|
33
41
|
const result1 = await Effect.runPromise(
|
|
@@ -37,34 +45,45 @@ t.describe(`${EncryptedCookies.encrypt.name}`, () => {
|
|
|
37
45
|
EncryptedCookies.encrypt(value, { secret: "test-secret" }),
|
|
38
46
|
)
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
test
|
|
49
|
+
.expect(result1)
|
|
50
|
+
.not
|
|
51
|
+
.toBe(result2)
|
|
41
52
|
|
|
42
53
|
// But both should have correct format
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
test
|
|
55
|
+
.expect(result1.split("."))
|
|
56
|
+
.toHaveLength(3)
|
|
57
|
+
test
|
|
58
|
+
.expect(result2.split("."))
|
|
59
|
+
.toHaveLength(3)
|
|
45
60
|
})
|
|
46
61
|
|
|
47
|
-
|
|
62
|
+
test.test("handle empty string", async () => {
|
|
48
63
|
const value = ""
|
|
49
64
|
|
|
50
65
|
const result = await Effect.runPromise(
|
|
51
66
|
EncryptedCookies.encrypt(value, { secret: "test-secret" }),
|
|
52
67
|
)
|
|
53
68
|
|
|
54
|
-
|
|
69
|
+
test
|
|
70
|
+
.expect(result.split("."))
|
|
71
|
+
.toHaveLength(3)
|
|
55
72
|
})
|
|
56
73
|
|
|
57
|
-
|
|
74
|
+
test.test("handle special characters", async () => {
|
|
58
75
|
const value = "hello 世界! @#$%^&*()"
|
|
59
76
|
|
|
60
77
|
const result = await Effect.runPromise(
|
|
61
78
|
EncryptedCookies.encrypt(value, { secret: "test-secret" }),
|
|
62
79
|
)
|
|
63
80
|
|
|
64
|
-
|
|
81
|
+
test
|
|
82
|
+
.expect(result.split("."))
|
|
83
|
+
.toHaveLength(3)
|
|
65
84
|
})
|
|
66
85
|
|
|
67
|
-
|
|
86
|
+
test.test("handle object with undefined properties", async () => {
|
|
68
87
|
const value = { id: "some", optional: undefined }
|
|
69
88
|
|
|
70
89
|
const encrypted = await Effect.runPromise(
|
|
@@ -75,12 +94,14 @@ t.describe(`${EncryptedCookies.encrypt.name}`, () => {
|
|
|
75
94
|
)
|
|
76
95
|
|
|
77
96
|
// JSON.stringify removes undefined properties
|
|
78
|
-
|
|
97
|
+
test
|
|
98
|
+
.expect(decrypted)
|
|
99
|
+
.toEqual({ id: "some" })
|
|
79
100
|
})
|
|
80
101
|
})
|
|
81
102
|
|
|
82
|
-
|
|
83
|
-
|
|
103
|
+
test.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
104
|
+
test.test("decrypt encrypted string successfully", async () => {
|
|
84
105
|
const originalValue = "hello world"
|
|
85
106
|
|
|
86
107
|
const encrypted = await Effect.runPromise(
|
|
@@ -90,10 +111,12 @@ t.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
|
90
111
|
EncryptedCookies.decrypt(encrypted, { secret: "test-secret" }),
|
|
91
112
|
)
|
|
92
113
|
|
|
93
|
-
|
|
114
|
+
test
|
|
115
|
+
.expect(decrypted)
|
|
116
|
+
.toBe(originalValue)
|
|
94
117
|
})
|
|
95
118
|
|
|
96
|
-
|
|
119
|
+
test.test("handle empty string round-trip", async () => {
|
|
97
120
|
const originalValue = ""
|
|
98
121
|
|
|
99
122
|
const encrypted = await Effect.runPromise(
|
|
@@ -103,10 +126,12 @@ t.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
|
103
126
|
EncryptedCookies.decrypt(encrypted, { secret: "test-secret" }),
|
|
104
127
|
)
|
|
105
128
|
|
|
106
|
-
|
|
129
|
+
test
|
|
130
|
+
.expect(decrypted)
|
|
131
|
+
.toBe(originalValue)
|
|
107
132
|
})
|
|
108
133
|
|
|
109
|
-
|
|
134
|
+
test.test("handle special characters round-trip", async () => {
|
|
110
135
|
const originalValue = "hello 世界! @#$%^&*()"
|
|
111
136
|
|
|
112
137
|
const encrypted = await Effect.runPromise(
|
|
@@ -116,13 +141,15 @@ t.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
|
116
141
|
EncryptedCookies.decrypt(encrypted, { secret: "test-secret" }),
|
|
117
142
|
)
|
|
118
143
|
|
|
119
|
-
|
|
144
|
+
test
|
|
145
|
+
.expect(decrypted)
|
|
146
|
+
.toBe(originalValue)
|
|
120
147
|
})
|
|
121
148
|
|
|
122
|
-
|
|
149
|
+
test.test("fail with invalid format", () => {
|
|
123
150
|
const invalidValue = "not-encrypted"
|
|
124
151
|
|
|
125
|
-
|
|
152
|
+
test
|
|
126
153
|
.expect(
|
|
127
154
|
Effect.runPromise(
|
|
128
155
|
EncryptedCookies.decrypt(invalidValue, { secret: "test-secret" }),
|
|
@@ -132,10 +159,10 @@ t.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
|
132
159
|
.toThrow()
|
|
133
160
|
})
|
|
134
161
|
|
|
135
|
-
|
|
162
|
+
test.test("fail with wrong number of segments", () => {
|
|
136
163
|
const invalidValue = "one.two"
|
|
137
164
|
|
|
138
|
-
|
|
165
|
+
test
|
|
139
166
|
.expect(
|
|
140
167
|
Effect.runPromise(
|
|
141
168
|
EncryptedCookies.decrypt(invalidValue, { secret: "test-secret" }),
|
|
@@ -145,10 +172,10 @@ t.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
|
145
172
|
.toThrow()
|
|
146
173
|
})
|
|
147
174
|
|
|
148
|
-
|
|
175
|
+
test.test("fail with invalid base64", () => {
|
|
149
176
|
const invalidValue = "invalid.base64.data"
|
|
150
177
|
|
|
151
|
-
|
|
178
|
+
test
|
|
152
179
|
.expect(
|
|
153
180
|
Effect.runPromise(
|
|
154
181
|
EncryptedCookies.decrypt(invalidValue, { secret: "test-secret" }),
|
|
@@ -158,8 +185,8 @@ t.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
|
158
185
|
.toThrow()
|
|
159
186
|
})
|
|
160
187
|
|
|
161
|
-
|
|
162
|
-
|
|
188
|
+
test.test("fail with null value", () => {
|
|
189
|
+
test
|
|
163
190
|
.expect(
|
|
164
191
|
Effect.runPromise(
|
|
165
192
|
EncryptedCookies.encrypt(null, { secret: "test-secret" }),
|
|
@@ -169,8 +196,8 @@ t.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
|
169
196
|
.toThrow()
|
|
170
197
|
})
|
|
171
198
|
|
|
172
|
-
|
|
173
|
-
|
|
199
|
+
test.test("fail with undefined value", () => {
|
|
200
|
+
test
|
|
174
201
|
.expect(
|
|
175
202
|
Effect.runPromise(
|
|
176
203
|
EncryptedCookies.encrypt(undefined, { secret: "test-secret" }),
|
|
@@ -180,8 +207,8 @@ t.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
|
180
207
|
.toThrow()
|
|
181
208
|
})
|
|
182
209
|
|
|
183
|
-
|
|
184
|
-
|
|
210
|
+
test.test("fail with empty encrypted value", () => {
|
|
211
|
+
test
|
|
185
212
|
.expect(
|
|
186
213
|
Effect.runPromise(
|
|
187
214
|
EncryptedCookies.decrypt("", { secret: "test-secret" }),
|
|
@@ -192,8 +219,8 @@ t.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
|
192
219
|
})
|
|
193
220
|
})
|
|
194
221
|
|
|
195
|
-
|
|
196
|
-
|
|
222
|
+
test.describe(`${EncryptedCookies.encryptCookie.name}`, () => {
|
|
223
|
+
test.test("preserve cookie properties and encrypt value", async () => {
|
|
197
224
|
const cookie = Cookies.unsafeMakeCookie("test", "hello world")
|
|
198
225
|
|
|
199
226
|
const result = await Effect.runPromise(
|
|
@@ -201,18 +228,25 @@ t.describe(`${EncryptedCookies.encryptCookie.name}`, () => {
|
|
|
201
228
|
)
|
|
202
229
|
|
|
203
230
|
// Cookie properties should be preserved
|
|
204
|
-
|
|
231
|
+
test
|
|
232
|
+
.expect(result.name)
|
|
233
|
+
.toBe("test")
|
|
205
234
|
|
|
206
235
|
// Value should be encrypted (different from original)
|
|
207
|
-
|
|
236
|
+
test
|
|
237
|
+
.expect(result.value)
|
|
238
|
+
.not
|
|
239
|
+
.toBe("hello world")
|
|
208
240
|
|
|
209
241
|
// Should be in encrypted format
|
|
210
|
-
|
|
242
|
+
test
|
|
243
|
+
.expect(result.value.split("."))
|
|
244
|
+
.toHaveLength(3)
|
|
211
245
|
})
|
|
212
246
|
})
|
|
213
247
|
|
|
214
|
-
|
|
215
|
-
|
|
248
|
+
test.describe(`${EncryptedCookies.decryptCookie.name}`, () => {
|
|
249
|
+
test.test("preserve cookie properties and decrypt value", async () => {
|
|
216
250
|
const originalCookie = Cookies.unsafeMakeCookie("test", "hello world")
|
|
217
251
|
|
|
218
252
|
const encrypted = await Effect.runPromise(
|
|
@@ -223,15 +257,19 @@ t.describe(`${EncryptedCookies.decryptCookie.name}`, () => {
|
|
|
223
257
|
)
|
|
224
258
|
|
|
225
259
|
// Cookie properties should be preserved
|
|
226
|
-
|
|
260
|
+
test
|
|
261
|
+
.expect(decrypted.name)
|
|
262
|
+
.toBe("test")
|
|
227
263
|
|
|
228
264
|
// Value should be JSON stringified (string values are now always serialized)
|
|
229
|
-
|
|
265
|
+
test
|
|
266
|
+
.expect(decrypted.value)
|
|
267
|
+
.toBe("\"hello world\"")
|
|
230
268
|
})
|
|
231
269
|
})
|
|
232
270
|
|
|
233
|
-
|
|
234
|
-
|
|
271
|
+
test.describe("service", () => {
|
|
272
|
+
test.test("service uses pre-calculated key material", async () => {
|
|
235
273
|
const testSecret = "test-secret-key"
|
|
236
274
|
const testValue = "hello world"
|
|
237
275
|
|
|
@@ -250,12 +288,19 @@ t.describe("service", () => {
|
|
|
250
288
|
),
|
|
251
289
|
)
|
|
252
290
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
291
|
+
test
|
|
292
|
+
.expect(result.decrypted)
|
|
293
|
+
.toBe(testValue)
|
|
294
|
+
test
|
|
295
|
+
.expect(result.encrypted)
|
|
296
|
+
.not
|
|
297
|
+
.toBe(testValue)
|
|
298
|
+
test
|
|
299
|
+
.expect(result.encrypted.split("."))
|
|
300
|
+
.toHaveLength(3)
|
|
256
301
|
})
|
|
257
302
|
|
|
258
|
-
|
|
303
|
+
test.test("service cookie functions work with pre-calculated key", async () => {
|
|
259
304
|
const testSecret = "test-secret-key"
|
|
260
305
|
const originalCookie = Cookies.unsafeMakeCookie("test", "hello world")
|
|
261
306
|
|
|
@@ -274,12 +319,19 @@ t.describe("service", () => {
|
|
|
274
319
|
),
|
|
275
320
|
)
|
|
276
321
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
322
|
+
test
|
|
323
|
+
.expect(result.decrypted.name)
|
|
324
|
+
.toBe("test")
|
|
325
|
+
test
|
|
326
|
+
.expect(result.decrypted.value)
|
|
327
|
+
.toBe("\"hello world\"")
|
|
328
|
+
test
|
|
329
|
+
.expect(result.encrypted.value)
|
|
330
|
+
.not
|
|
331
|
+
.toBe("hello world")
|
|
280
332
|
})
|
|
281
333
|
|
|
282
|
-
|
|
334
|
+
test.test("functions work with pre-derived keys passed as options", async () => {
|
|
283
335
|
const testSecret = "test-secret-key"
|
|
284
336
|
const testValue = "hello world"
|
|
285
337
|
|
|
@@ -344,14 +396,21 @@ t.describe("service", () => {
|
|
|
344
396
|
|
|
345
397
|
const result = await Effect.runPromise(program)
|
|
346
398
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
399
|
+
test
|
|
400
|
+
.expect(result.decrypted)
|
|
401
|
+
.toBe(testValue)
|
|
402
|
+
test
|
|
403
|
+
.expect(result.encrypted)
|
|
404
|
+
.not
|
|
405
|
+
.toBe(testValue)
|
|
406
|
+
test
|
|
407
|
+
.expect(result.encrypted.split("."))
|
|
408
|
+
.toHaveLength(3)
|
|
350
409
|
})
|
|
351
410
|
})
|
|
352
411
|
|
|
353
|
-
|
|
354
|
-
|
|
412
|
+
test.describe("layerConfig", () => {
|
|
413
|
+
test.test("succeed with valid SECRET_KEY_BASE", async () => {
|
|
355
414
|
const validSecret = "a".repeat(40)
|
|
356
415
|
|
|
357
416
|
const program = Effect.gen(function*() {
|
|
@@ -374,10 +433,12 @@ t.describe("layerConfig", () => {
|
|
|
374
433
|
),
|
|
375
434
|
)
|
|
376
435
|
|
|
377
|
-
|
|
436
|
+
test
|
|
437
|
+
.expect(result)
|
|
438
|
+
.toBe("test")
|
|
378
439
|
})
|
|
379
440
|
|
|
380
|
-
|
|
441
|
+
test.test("fail with short SECRET_KEY_BASE", async () => {
|
|
381
442
|
const shortSecret = "short"
|
|
382
443
|
|
|
383
444
|
const program = Effect.gen(function*() {
|
|
@@ -385,7 +446,7 @@ t.describe("layerConfig", () => {
|
|
|
385
446
|
return yield* service.encrypt("test")
|
|
386
447
|
})
|
|
387
448
|
|
|
388
|
-
|
|
449
|
+
test
|
|
389
450
|
.expect(
|
|
390
451
|
Effect.runPromise(
|
|
391
452
|
program.pipe(
|
|
@@ -404,13 +465,13 @@ t.describe("layerConfig", () => {
|
|
|
404
465
|
.toThrow("SECRET_KEY_BASE must be at least 40 characters")
|
|
405
466
|
})
|
|
406
467
|
|
|
407
|
-
|
|
468
|
+
test.test("fail with missing SECRET_KEY_BASE", async () => {
|
|
408
469
|
const program = Effect.gen(function*() {
|
|
409
470
|
const service = yield* EncryptedCookies.EncryptedCookies
|
|
410
471
|
return yield* service.encrypt("test")
|
|
411
472
|
})
|
|
412
473
|
|
|
413
|
-
|
|
474
|
+
test
|
|
414
475
|
.expect(
|
|
415
476
|
Effect.runPromise(
|
|
416
477
|
program.pipe(
|
|
@@ -6,7 +6,6 @@ import * as Schedule from "effect/Schedule"
|
|
|
6
6
|
import * as Stream from "effect/Stream"
|
|
7
7
|
import * as StreamExtra from "../StreamExtra.ts"
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
const DefaultHeartbeatInterval = Duration.seconds(5)
|
|
11
10
|
|
|
12
11
|
export const make = <T = any>(stream: Stream.Stream<T, any>, options?: {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as Context from "effect/Context"
|
|
2
|
+
import * as Fiber from "effect/Fiber"
|
|
3
|
+
import * as Layer from "effect/Layer"
|
|
4
|
+
import * as Option from "effect/Option"
|
|
5
|
+
import { HyperHooks } from "../x/datastar/index.ts"
|
|
6
|
+
import type { JSX } from "./jsx.d.ts"
|
|
7
|
+
|
|
8
|
+
type Elements = JSX.IntrinsicElements
|
|
9
|
+
|
|
10
|
+
type Children = JSX.Children
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
Children,
|
|
14
|
+
Elements,
|
|
15
|
+
JSX,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class Hyper extends Context.Tag("Hyper")<Hyper, {
|
|
19
|
+
hooks: typeof HyperHooks | undefined
|
|
20
|
+
}>() {}
|
|
21
|
+
|
|
22
|
+
export function layer(opts: {
|
|
23
|
+
hooks: typeof HyperHooks
|
|
24
|
+
}) {
|
|
25
|
+
return Layer.sync(Hyper, () => {
|
|
26
|
+
return {
|
|
27
|
+
hooks: opts.hooks,
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const NoChildren: ReadonlyArray<never> = Object.freeze([])
|
|
33
|
+
|
|
34
|
+
type Primitive = string | number | boolean | null | undefined
|
|
35
|
+
|
|
36
|
+
export type HyperType = string | HyperComponent
|
|
37
|
+
|
|
38
|
+
export type HyperProps = {
|
|
39
|
+
[key: string]:
|
|
40
|
+
| Primitive
|
|
41
|
+
| ReadonlyArray<Primitive>
|
|
42
|
+
| HyperNode
|
|
43
|
+
| HyperNode[]
|
|
44
|
+
| null
|
|
45
|
+
| undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type HyperComponent = (
|
|
49
|
+
props: HyperProps,
|
|
50
|
+
) => HyperNode | Primitive
|
|
51
|
+
|
|
52
|
+
export interface HyperNode {
|
|
53
|
+
type: HyperType
|
|
54
|
+
props: HyperProps
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function h(
|
|
58
|
+
type: HyperType,
|
|
59
|
+
props: HyperProps,
|
|
60
|
+
): HyperNode {
|
|
61
|
+
return {
|
|
62
|
+
type,
|
|
63
|
+
props: {
|
|
64
|
+
...props,
|
|
65
|
+
children: props.children ?? NoChildren,
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function unsafeUse<Value>(tag: Context.Tag<any, Value>) {
|
|
71
|
+
const currentFiber = Option.getOrThrow(
|
|
72
|
+
Fiber.getCurrentFiber(),
|
|
73
|
+
)
|
|
74
|
+
const context = currentFiber.currentContext
|
|
75
|
+
|
|
76
|
+
return Context.unsafeGet(context, tag)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type GenericJsxObject = {
|
|
80
|
+
type: any
|
|
81
|
+
props: any
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function isGenericJsxObject(value: unknown): value is GenericJsxObject {
|
|
85
|
+
return typeof value === "object"
|
|
86
|
+
&& value !== null
|
|
87
|
+
&& "type" in value
|
|
88
|
+
&& "props" in value
|
|
89
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as test from "bun:test"
|
|
2
2
|
import * as HyperHtml from "./HyperHtml.ts"
|
|
3
3
|
import * as HyperNode from "./HyperNode.ts"
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
test.it("boolean true attributes render without value (React-like)", () => {
|
|
6
6
|
const node = HyperNode.make("div", {
|
|
7
7
|
hidden: true,
|
|
8
8
|
disabled: true,
|
|
@@ -11,12 +11,12 @@ t.it("boolean true attributes render without value (React-like)", () => {
|
|
|
11
11
|
|
|
12
12
|
const html = HyperHtml.renderToString(node)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
test
|
|
15
15
|
.expect(html)
|
|
16
16
|
.toBe("<div hidden disabled data-active></div>")
|
|
17
17
|
})
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
test.it("boolean false attributes are omitted", () => {
|
|
20
20
|
const node = HyperNode.make("div", {
|
|
21
21
|
hidden: false,
|
|
22
22
|
disabled: false,
|
|
@@ -25,12 +25,12 @@ t.it("boolean false attributes are omitted", () => {
|
|
|
25
25
|
|
|
26
26
|
const html = HyperHtml.renderToString(node)
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
test
|
|
29
29
|
.expect(html)
|
|
30
30
|
.toBe("<div></div>")
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
test.it("string attributes render with values", () => {
|
|
34
34
|
const node = HyperNode.make("div", {
|
|
35
35
|
id: "test",
|
|
36
36
|
class: "my-class",
|
|
@@ -39,12 +39,12 @@ t.it("string attributes render with values", () => {
|
|
|
39
39
|
|
|
40
40
|
const html = HyperHtml.renderToString(node)
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
test
|
|
43
43
|
.expect(html)
|
|
44
44
|
.toBe("<div id=\"test\" class=\"my-class\" data-value=\"hello\"></div>")
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
test.it("number attributes render with values", () => {
|
|
48
48
|
const node = HyperNode.make("input", {
|
|
49
49
|
type: "number",
|
|
50
50
|
min: 0,
|
|
@@ -54,12 +54,12 @@ t.it("number attributes render with values", () => {
|
|
|
54
54
|
|
|
55
55
|
const html = HyperHtml.renderToString(node)
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
test
|
|
58
58
|
.expect(html)
|
|
59
59
|
.toBe("<input type=\"number\" min=\"0\" max=\"100\" value=\"50\">")
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
test.it("null and undefined attributes are omitted", () => {
|
|
63
63
|
const node = HyperNode.make("div", {
|
|
64
64
|
id: null,
|
|
65
65
|
class: undefined,
|
|
@@ -68,12 +68,12 @@ t.it("null and undefined attributes are omitted", () => {
|
|
|
68
68
|
|
|
69
69
|
const html = HyperHtml.renderToString(node)
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
test
|
|
72
72
|
.expect(html)
|
|
73
73
|
.toBe("<div data-test=\"value\"></div>")
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
test.it("mixed boolean and string attributes", () => {
|
|
77
77
|
const node = HyperNode.make("input", {
|
|
78
78
|
type: "checkbox",
|
|
79
79
|
checked: true,
|
|
@@ -84,7 +84,7 @@ t.it("mixed boolean and string attributes", () => {
|
|
|
84
84
|
|
|
85
85
|
const html = HyperHtml.renderToString(node)
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
test
|
|
88
88
|
.expect(html)
|
|
89
89
|
.toBe("<input type=\"checkbox\" checked name=\"test\" value=\"on\">")
|
|
90
90
|
})
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
* }
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type * as Hyper from "./Hyper.
|
|
20
|
+
import type * as Hyper from "./Hyper.ts"
|
|
21
21
|
import * as HyperNode from "./HyperNode.ts"
|
|
22
|
-
import { JSX } from "./jsx"
|
|
23
22
|
import type * as JsxRuntime from "./jsx-runtime.ts"
|
|
23
|
+
import type { JSX } from "./jsx.d.ts"
|
|
24
24
|
|
|
25
25
|
const EMPTY_TAGS = [
|
|
26
26
|
"area",
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export * as
|
|
2
|
-
|
|
1
|
+
export * as Bundle from "./bundler/Bundle.ts"
|
|
2
|
+
export * as Entity from "./Entity.ts"
|
|
3
3
|
export * as FileRouter from "./FileRouter.ts"
|
|
4
4
|
export * as Route from "./Route.ts"
|
|
5
|
-
|
|
6
|
-
export * as Bundle from "./Bundle.ts"
|
|
5
|
+
export * as Start from "./Start.ts"
|