effect-start 0.17.2 → 0.19.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/dist/Development.d.ts +7 -2
- package/dist/Development.js +12 -6
- package/dist/PlatformRuntime.d.ts +4 -0
- package/dist/PlatformRuntime.js +9 -0
- package/dist/Route.d.ts +6 -2
- package/dist/Route.js +22 -0
- package/dist/RouteHttp.d.ts +1 -1
- package/dist/RouteHttp.js +12 -19
- package/dist/RouteMount.d.ts +2 -1
- package/dist/Start.d.ts +1 -5
- package/dist/Start.js +1 -8
- package/dist/Unique.d.ts +50 -0
- package/dist/Unique.js +187 -0
- package/dist/bun/BunHttpServer.js +5 -6
- package/dist/bun/BunRoute.d.ts +1 -1
- package/dist/bun/BunRoute.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/node/Effectify.d.ts +209 -0
- package/dist/node/Effectify.js +19 -0
- package/dist/node/FileSystem.d.ts +3 -5
- package/dist/node/FileSystem.js +42 -62
- package/dist/node/PlatformError.d.ts +46 -0
- package/dist/node/PlatformError.js +43 -0
- package/dist/testing/TestLogger.js +1 -1
- package/package.json +10 -5
- package/src/Development.ts +13 -18
- package/src/PlatformRuntime.ts +11 -0
- package/src/Route.ts +31 -2
- package/src/RouteHttp.ts +15 -31
- package/src/RouteMount.ts +1 -1
- package/src/Start.ts +1 -15
- package/src/Unique.ts +232 -0
- package/src/bun/BunHttpServer.ts +6 -9
- package/src/bun/BunRoute.ts +3 -3
- package/src/index.ts +1 -0
- package/src/node/Effectify.ts +262 -0
- package/src/node/FileSystem.ts +59 -97
- package/src/node/PlatformError.ts +102 -0
- package/src/testing/TestLogger.ts +1 -1
- package/dist/Random.d.ts +0 -5
- package/dist/Random.js +0 -49
- package/src/Commander.test.ts +0 -1639
- package/src/ContentNegotiation.test.ts +0 -603
- package/src/Development.test.ts +0 -119
- package/src/Entity.test.ts +0 -592
- package/src/FileRouterPattern.test.ts +0 -147
- package/src/FileRouter_files.test.ts +0 -64
- package/src/FileRouter_path.test.ts +0 -145
- package/src/FileRouter_tree.test.ts +0 -132
- package/src/Http.test.ts +0 -319
- package/src/HttpAppExtra.test.ts +0 -103
- package/src/HttpUtils.test.ts +0 -85
- package/src/PathPattern.test.ts +0 -648
- package/src/Random.ts +0 -59
- package/src/RouteBody.test.ts +0 -232
- package/src/RouteHook.test.ts +0 -40
- package/src/RouteHttp.test.ts +0 -2909
- package/src/RouteMount.test.ts +0 -481
- package/src/RouteSchema.test.ts +0 -427
- package/src/RouteSse.test.ts +0 -249
- package/src/RouteTree.test.ts +0 -494
- package/src/RouteTrie.test.ts +0 -322
- package/src/RouterPattern.test.ts +0 -676
- package/src/Values.test.ts +0 -263
- package/src/bun/BunBundle.test.ts +0 -268
- package/src/bun/BunBundle_imports.test.ts +0 -48
- package/src/bun/BunHttpServer.test.ts +0 -251
- package/src/bun/BunImportTrackerPlugin.test.ts +0 -77
- package/src/bun/BunRoute.test.ts +0 -162
- package/src/bundler/BundleHttp.test.ts +0 -132
- package/src/effect/HttpRouter.test.ts +0 -548
- package/src/experimental/EncryptedCookies.test.ts +0 -488
- package/src/hyper/HyperHtml.test.ts +0 -209
- package/src/hyper/HyperRoute.test.tsx +0 -197
- package/src/middlewares/BasicAuthMiddleware.test.ts +0 -84
- package/src/testing/TestHttpClient.test.ts +0 -83
- package/src/testing/TestLogger.test.ts +0 -51
- package/src/x/datastar/Datastar.test.ts +0 -266
- package/src/x/tailwind/TailwindPlugin.test.ts +0 -333
|
@@ -1,488 +0,0 @@
|
|
|
1
|
-
import * as Cookies from "@effect/platform/Cookies"
|
|
2
|
-
import * as test from "bun:test"
|
|
3
|
-
import * as ConfigProvider from "effect/ConfigProvider"
|
|
4
|
-
import * as Effect from "effect/Effect"
|
|
5
|
-
import * as EncryptedCookies from "./EncryptedCookies.ts"
|
|
6
|
-
|
|
7
|
-
test.describe(`${EncryptedCookies.encrypt.name}`, () => {
|
|
8
|
-
test.test("return encrypted string in correct format", async () => {
|
|
9
|
-
const value = "hello world"
|
|
10
|
-
|
|
11
|
-
const result = await Effect.runPromise(
|
|
12
|
-
EncryptedCookies.encrypt(value, { secret: "test-secret" }),
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
// Check format: three base64url segments separated by .
|
|
16
|
-
const segments = result.split(".")
|
|
17
|
-
test
|
|
18
|
-
.expect(segments)
|
|
19
|
-
.toHaveLength(3)
|
|
20
|
-
|
|
21
|
-
// Each segment should be base64url (no +, /, or = characters
|
|
22
|
-
// so cookie values are not escaped)
|
|
23
|
-
segments.forEach((segment: string) => {
|
|
24
|
-
test
|
|
25
|
-
.expect(segment)
|
|
26
|
-
.not
|
|
27
|
-
.toMatch(/[+/=]/)
|
|
28
|
-
// Should be valid base64url that can be decoded
|
|
29
|
-
const base64 = segment.replace(/-/g, "+").replace(/_/g, "/")
|
|
30
|
-
const paddedBase64 = base64 + "=".repeat((4 - base64.length % 4) % 4)
|
|
31
|
-
test
|
|
32
|
-
.expect(() => atob(paddedBase64))
|
|
33
|
-
.not
|
|
34
|
-
.toThrow()
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test.test("produce different results for same input due to random IV", async () => {
|
|
39
|
-
const value = "same value"
|
|
40
|
-
|
|
41
|
-
const result1 = await Effect.runPromise(
|
|
42
|
-
EncryptedCookies.encrypt(value, { secret: "test-secret" }),
|
|
43
|
-
)
|
|
44
|
-
const result2 = await Effect.runPromise(
|
|
45
|
-
EncryptedCookies.encrypt(value, { secret: "test-secret" }),
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
test
|
|
49
|
-
.expect(result1)
|
|
50
|
-
.not
|
|
51
|
-
.toBe(result2)
|
|
52
|
-
|
|
53
|
-
// But both should have correct format
|
|
54
|
-
test
|
|
55
|
-
.expect(result1.split("."))
|
|
56
|
-
.toHaveLength(3)
|
|
57
|
-
test
|
|
58
|
-
.expect(result2.split("."))
|
|
59
|
-
.toHaveLength(3)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test.test("handle empty string", async () => {
|
|
63
|
-
const value = ""
|
|
64
|
-
|
|
65
|
-
const result = await Effect.runPromise(
|
|
66
|
-
EncryptedCookies.encrypt(value, { secret: "test-secret" }),
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
test
|
|
70
|
-
.expect(result.split("."))
|
|
71
|
-
.toHaveLength(3)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test.test("handle special characters", async () => {
|
|
75
|
-
const value = "hello 世界! @#$%^&*()"
|
|
76
|
-
|
|
77
|
-
const result = await Effect.runPromise(
|
|
78
|
-
EncryptedCookies.encrypt(value, { secret: "test-secret" }),
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
test
|
|
82
|
-
.expect(result.split("."))
|
|
83
|
-
.toHaveLength(3)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
test.test("handle object with undefined properties", async () => {
|
|
87
|
-
const value = { id: "some", optional: undefined }
|
|
88
|
-
|
|
89
|
-
const encrypted = await Effect.runPromise(
|
|
90
|
-
EncryptedCookies.encrypt(value, { secret: "test-secret" }),
|
|
91
|
-
)
|
|
92
|
-
const decrypted = await Effect.runPromise(
|
|
93
|
-
EncryptedCookies.decrypt(encrypted, { secret: "test-secret" }),
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
// JSON.stringify removes undefined properties
|
|
97
|
-
test
|
|
98
|
-
.expect(decrypted)
|
|
99
|
-
.toEqual({ id: "some" })
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
test.describe(`${EncryptedCookies.decrypt.name}`, () => {
|
|
104
|
-
test.test("decrypt encrypted string successfully", async () => {
|
|
105
|
-
const originalValue = "hello world"
|
|
106
|
-
|
|
107
|
-
const encrypted = await Effect.runPromise(
|
|
108
|
-
EncryptedCookies.encrypt(originalValue, { secret: "test-secret" }),
|
|
109
|
-
)
|
|
110
|
-
const decrypted = await Effect.runPromise(
|
|
111
|
-
EncryptedCookies.decrypt(encrypted, { secret: "test-secret" }),
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
test
|
|
115
|
-
.expect(decrypted)
|
|
116
|
-
.toBe(originalValue)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
test.test("handle empty string round-trip", async () => {
|
|
120
|
-
const originalValue = ""
|
|
121
|
-
|
|
122
|
-
const encrypted = await Effect.runPromise(
|
|
123
|
-
EncryptedCookies.encrypt(originalValue, { secret: "test-secret" }),
|
|
124
|
-
)
|
|
125
|
-
const decrypted = await Effect.runPromise(
|
|
126
|
-
EncryptedCookies.decrypt(encrypted, { secret: "test-secret" }),
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
test
|
|
130
|
-
.expect(decrypted)
|
|
131
|
-
.toBe(originalValue)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
test.test("handle special characters round-trip", async () => {
|
|
135
|
-
const originalValue = "hello 世界! @#$%^&*()"
|
|
136
|
-
|
|
137
|
-
const encrypted = await Effect.runPromise(
|
|
138
|
-
EncryptedCookies.encrypt(originalValue, { secret: "test-secret" }),
|
|
139
|
-
)
|
|
140
|
-
const decrypted = await Effect.runPromise(
|
|
141
|
-
EncryptedCookies.decrypt(encrypted, { secret: "test-secret" }),
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
test
|
|
145
|
-
.expect(decrypted)
|
|
146
|
-
.toBe(originalValue)
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
test.test("fail with invalid format", () => {
|
|
150
|
-
const invalidValue = "not-encrypted"
|
|
151
|
-
|
|
152
|
-
test
|
|
153
|
-
.expect(
|
|
154
|
-
Effect.runPromise(
|
|
155
|
-
EncryptedCookies.decrypt(invalidValue, { secret: "test-secret" }),
|
|
156
|
-
),
|
|
157
|
-
)
|
|
158
|
-
.rejects
|
|
159
|
-
.toThrow()
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
test.test("fail with wrong number of segments", () => {
|
|
163
|
-
const invalidValue = "one.two"
|
|
164
|
-
|
|
165
|
-
test
|
|
166
|
-
.expect(
|
|
167
|
-
Effect.runPromise(
|
|
168
|
-
EncryptedCookies.decrypt(invalidValue, { secret: "test-secret" }),
|
|
169
|
-
),
|
|
170
|
-
)
|
|
171
|
-
.rejects
|
|
172
|
-
.toThrow()
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
test.test("fail with invalid base64", () => {
|
|
176
|
-
const invalidValue = "invalid.base64.data"
|
|
177
|
-
|
|
178
|
-
test
|
|
179
|
-
.expect(
|
|
180
|
-
Effect.runPromise(
|
|
181
|
-
EncryptedCookies.decrypt(invalidValue, { secret: "test-secret" }),
|
|
182
|
-
),
|
|
183
|
-
)
|
|
184
|
-
.rejects
|
|
185
|
-
.toThrow()
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
test.test("fail with null value", () => {
|
|
189
|
-
test
|
|
190
|
-
.expect(
|
|
191
|
-
Effect.runPromise(
|
|
192
|
-
EncryptedCookies.encrypt(null, { secret: "test-secret" }),
|
|
193
|
-
),
|
|
194
|
-
)
|
|
195
|
-
.rejects
|
|
196
|
-
.toThrow()
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
test.test("fail with undefined value", () => {
|
|
200
|
-
test
|
|
201
|
-
.expect(
|
|
202
|
-
Effect.runPromise(
|
|
203
|
-
EncryptedCookies.encrypt(undefined, { secret: "test-secret" }),
|
|
204
|
-
),
|
|
205
|
-
)
|
|
206
|
-
.rejects
|
|
207
|
-
.toThrow()
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
test.test("fail with empty encrypted value", () => {
|
|
211
|
-
test
|
|
212
|
-
.expect(
|
|
213
|
-
Effect.runPromise(
|
|
214
|
-
EncryptedCookies.decrypt("", { secret: "test-secret" }),
|
|
215
|
-
),
|
|
216
|
-
)
|
|
217
|
-
.rejects
|
|
218
|
-
.toThrow()
|
|
219
|
-
})
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
test.describe(`${EncryptedCookies.encryptCookie.name}`, () => {
|
|
223
|
-
test.test("preserve cookie properties and encrypt value", async () => {
|
|
224
|
-
const cookie = Cookies.unsafeMakeCookie("test", "hello world")
|
|
225
|
-
|
|
226
|
-
const result = await Effect.runPromise(
|
|
227
|
-
EncryptedCookies.encryptCookie(cookie, { secret: "test-secret" }),
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
// Cookie properties should be preserved
|
|
231
|
-
test
|
|
232
|
-
.expect(result.name)
|
|
233
|
-
.toBe("test")
|
|
234
|
-
|
|
235
|
-
// Value should be encrypted (different from original)
|
|
236
|
-
test
|
|
237
|
-
.expect(result.value)
|
|
238
|
-
.not
|
|
239
|
-
.toBe("hello world")
|
|
240
|
-
|
|
241
|
-
// Should be in encrypted format
|
|
242
|
-
test
|
|
243
|
-
.expect(result.value.split("."))
|
|
244
|
-
.toHaveLength(3)
|
|
245
|
-
})
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
test.describe(`${EncryptedCookies.decryptCookie.name}`, () => {
|
|
249
|
-
test.test("preserve cookie properties and decrypt value", async () => {
|
|
250
|
-
const originalCookie = Cookies.unsafeMakeCookie("test", "hello world")
|
|
251
|
-
|
|
252
|
-
const encrypted = await Effect.runPromise(
|
|
253
|
-
EncryptedCookies.encryptCookie(originalCookie, { secret: "test-secret" }),
|
|
254
|
-
)
|
|
255
|
-
const decrypted = await Effect.runPromise(
|
|
256
|
-
EncryptedCookies.decryptCookie(encrypted, { secret: "test-secret" }),
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
// Cookie properties should be preserved
|
|
260
|
-
test
|
|
261
|
-
.expect(decrypted.name)
|
|
262
|
-
.toBe("test")
|
|
263
|
-
|
|
264
|
-
// Value should be JSON stringified (string values are now always serialized)
|
|
265
|
-
test
|
|
266
|
-
.expect(decrypted.value)
|
|
267
|
-
.toBe("\"hello world\"")
|
|
268
|
-
})
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
test.describe("service", () => {
|
|
272
|
-
test.test("service uses pre-calculated key material", async () => {
|
|
273
|
-
const testSecret = "test-secret-key"
|
|
274
|
-
const testValue = "hello world"
|
|
275
|
-
|
|
276
|
-
const program = Effect.gen(function*() {
|
|
277
|
-
const service = yield* EncryptedCookies.EncryptedCookies
|
|
278
|
-
|
|
279
|
-
const encrypted = yield* service.encrypt(testValue)
|
|
280
|
-
const decrypted = yield* service.decrypt(encrypted)
|
|
281
|
-
|
|
282
|
-
return { encrypted, decrypted }
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
const result = await Effect.runPromise(
|
|
286
|
-
program.pipe(
|
|
287
|
-
Effect.provide(EncryptedCookies.layer({ secret: testSecret })),
|
|
288
|
-
),
|
|
289
|
-
)
|
|
290
|
-
|
|
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)
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
test.test("service cookie functions work with pre-calculated key", async () => {
|
|
304
|
-
const testSecret = "test-secret-key"
|
|
305
|
-
const originalCookie = Cookies.unsafeMakeCookie("test", "hello world")
|
|
306
|
-
|
|
307
|
-
const program = Effect.gen(function*() {
|
|
308
|
-
const service = yield* EncryptedCookies.EncryptedCookies
|
|
309
|
-
|
|
310
|
-
const encrypted = yield* service.encryptCookie(originalCookie)
|
|
311
|
-
const decrypted = yield* service.decryptCookie(encrypted)
|
|
312
|
-
|
|
313
|
-
return { encrypted, decrypted }
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
const result = await Effect.runPromise(
|
|
317
|
-
program.pipe(
|
|
318
|
-
Effect.provide(EncryptedCookies.layer({ secret: testSecret })),
|
|
319
|
-
),
|
|
320
|
-
)
|
|
321
|
-
|
|
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")
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
test.test("functions work with pre-derived keys passed as options", async () => {
|
|
335
|
-
const testSecret = "test-secret-key"
|
|
336
|
-
const testValue = "hello world"
|
|
337
|
-
|
|
338
|
-
// Pre-derive keys manually
|
|
339
|
-
const program = Effect.gen(function*() {
|
|
340
|
-
const keyMaterial = yield* Effect.tryPromise({
|
|
341
|
-
try: () =>
|
|
342
|
-
crypto.subtle.importKey(
|
|
343
|
-
"raw",
|
|
344
|
-
new TextEncoder().encode(testSecret),
|
|
345
|
-
{ name: "HKDF" },
|
|
346
|
-
false,
|
|
347
|
-
["deriveKey"],
|
|
348
|
-
),
|
|
349
|
-
catch: (error) => error,
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
const encryptKey = yield* Effect.tryPromise({
|
|
353
|
-
try: () =>
|
|
354
|
-
crypto.subtle.deriveKey(
|
|
355
|
-
{
|
|
356
|
-
name: "HKDF",
|
|
357
|
-
salt: new TextEncoder().encode("cookie-encryption"),
|
|
358
|
-
info: new TextEncoder().encode("aes-256-gcm"),
|
|
359
|
-
hash: "SHA-256",
|
|
360
|
-
},
|
|
361
|
-
keyMaterial,
|
|
362
|
-
{ name: "AES-GCM", length: 256 },
|
|
363
|
-
false,
|
|
364
|
-
["encrypt"],
|
|
365
|
-
),
|
|
366
|
-
catch: (error) => error,
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
const decryptKey = yield* Effect.tryPromise({
|
|
370
|
-
try: () =>
|
|
371
|
-
crypto.subtle.deriveKey(
|
|
372
|
-
{
|
|
373
|
-
name: "HKDF",
|
|
374
|
-
salt: new TextEncoder().encode("cookie-encryption"),
|
|
375
|
-
info: new TextEncoder().encode("aes-256-gcm"),
|
|
376
|
-
hash: "SHA-256",
|
|
377
|
-
},
|
|
378
|
-
keyMaterial,
|
|
379
|
-
{ name: "AES-GCM", length: 256 },
|
|
380
|
-
false,
|
|
381
|
-
["decrypt"],
|
|
382
|
-
),
|
|
383
|
-
catch: (error) => error,
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
// Use functions with pre-derived keys
|
|
387
|
-
const encrypted = yield* EncryptedCookies.encrypt(testValue, {
|
|
388
|
-
key: encryptKey,
|
|
389
|
-
})
|
|
390
|
-
const decrypted = yield* EncryptedCookies.decrypt(encrypted, {
|
|
391
|
-
key: decryptKey,
|
|
392
|
-
})
|
|
393
|
-
|
|
394
|
-
return { encrypted, decrypted }
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
const result = await Effect.runPromise(program)
|
|
398
|
-
|
|
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)
|
|
409
|
-
})
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
test.describe("layerConfig", () => {
|
|
413
|
-
test.test("succeed with valid SECRET_KEY_BASE", async () => {
|
|
414
|
-
const validSecret = "a".repeat(40)
|
|
415
|
-
|
|
416
|
-
const program = Effect.gen(function*() {
|
|
417
|
-
const service = yield* EncryptedCookies.EncryptedCookies
|
|
418
|
-
const encrypted = yield* service.encrypt("test")
|
|
419
|
-
const decrypted = yield* service.decrypt(encrypted)
|
|
420
|
-
return decrypted
|
|
421
|
-
})
|
|
422
|
-
|
|
423
|
-
const result = await Effect.runPromise(
|
|
424
|
-
program.pipe(
|
|
425
|
-
Effect.provide(
|
|
426
|
-
EncryptedCookies.layerConfig("SECRET_KEY_BASE"),
|
|
427
|
-
),
|
|
428
|
-
Effect.withConfigProvider(
|
|
429
|
-
ConfigProvider.fromMap(
|
|
430
|
-
new Map([["SECRET_KEY_BASE", validSecret]]),
|
|
431
|
-
),
|
|
432
|
-
),
|
|
433
|
-
),
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
test
|
|
437
|
-
.expect(result)
|
|
438
|
-
.toBe("test")
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
test.test("fail with short SECRET_KEY_BASE", async () => {
|
|
442
|
-
const shortSecret = "short"
|
|
443
|
-
|
|
444
|
-
const program = Effect.gen(function*() {
|
|
445
|
-
const service = yield* EncryptedCookies.EncryptedCookies
|
|
446
|
-
return yield* service.encrypt("test")
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
test
|
|
450
|
-
.expect(
|
|
451
|
-
Effect.runPromise(
|
|
452
|
-
program.pipe(
|
|
453
|
-
Effect.provide(
|
|
454
|
-
EncryptedCookies.layerConfig("SECRET_KEY_BASE"),
|
|
455
|
-
),
|
|
456
|
-
Effect.withConfigProvider(
|
|
457
|
-
ConfigProvider.fromMap(
|
|
458
|
-
new Map([["SECRET_KEY_BASE", shortSecret]]),
|
|
459
|
-
),
|
|
460
|
-
),
|
|
461
|
-
),
|
|
462
|
-
),
|
|
463
|
-
)
|
|
464
|
-
.rejects
|
|
465
|
-
.toThrow("SECRET_KEY_BASE must be at least 40 characters")
|
|
466
|
-
})
|
|
467
|
-
|
|
468
|
-
test.test("fail with missing SECRET_KEY_BASE", async () => {
|
|
469
|
-
const program = Effect.gen(function*() {
|
|
470
|
-
const service = yield* EncryptedCookies.EncryptedCookies
|
|
471
|
-
return yield* service.encrypt("test")
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
test
|
|
475
|
-
.expect(
|
|
476
|
-
Effect.runPromise(
|
|
477
|
-
program.pipe(
|
|
478
|
-
Effect.provide(
|
|
479
|
-
EncryptedCookies.layerConfig("SECRET_KEY_BASE"),
|
|
480
|
-
),
|
|
481
|
-
Effect.withConfigProvider(ConfigProvider.fromMap(new Map())),
|
|
482
|
-
),
|
|
483
|
-
),
|
|
484
|
-
)
|
|
485
|
-
.rejects
|
|
486
|
-
.toThrow("SECRET_KEY_BASE must be at least 40 characters")
|
|
487
|
-
})
|
|
488
|
-
})
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import * as test from "bun:test"
|
|
2
|
-
import * as HyperHtml from "./HyperHtml.ts"
|
|
3
|
-
import * as HyperNode from "./HyperNode.ts"
|
|
4
|
-
|
|
5
|
-
test.it("boolean true attributes render without value (React-like)", () => {
|
|
6
|
-
const node = HyperNode.make("div", {
|
|
7
|
-
hidden: true,
|
|
8
|
-
disabled: true,
|
|
9
|
-
"data-active": true,
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
const html = HyperHtml.renderToString(node)
|
|
13
|
-
|
|
14
|
-
test
|
|
15
|
-
.expect(html)
|
|
16
|
-
.toBe("<div hidden disabled data-active></div>")
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test.it("boolean false attributes are omitted", () => {
|
|
20
|
-
const node = HyperNode.make("div", {
|
|
21
|
-
hidden: false,
|
|
22
|
-
disabled: false,
|
|
23
|
-
"data-active": false,
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const html = HyperHtml.renderToString(node)
|
|
27
|
-
|
|
28
|
-
test
|
|
29
|
-
.expect(html)
|
|
30
|
-
.toBe("<div></div>")
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test.it("string attributes render with values", () => {
|
|
34
|
-
const node = HyperNode.make("div", {
|
|
35
|
-
id: "test",
|
|
36
|
-
class: "my-class",
|
|
37
|
-
"data-value": "hello",
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
const html = HyperHtml.renderToString(node)
|
|
41
|
-
|
|
42
|
-
test
|
|
43
|
-
.expect(html)
|
|
44
|
-
.toBe("<div id=\"test\" class=\"my-class\" data-value=\"hello\"></div>")
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test.it("number attributes render with values", () => {
|
|
48
|
-
const node = HyperNode.make("input", {
|
|
49
|
-
type: "number",
|
|
50
|
-
min: 0,
|
|
51
|
-
max: 100,
|
|
52
|
-
value: 50,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
const html = HyperHtml.renderToString(node)
|
|
56
|
-
|
|
57
|
-
test
|
|
58
|
-
.expect(html)
|
|
59
|
-
.toBe("<input type=\"number\" min=\"0\" max=\"100\" value=\"50\">")
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test.it("null and undefined attributes are omitted", () => {
|
|
63
|
-
const node = HyperNode.make("div", {
|
|
64
|
-
id: null,
|
|
65
|
-
class: undefined,
|
|
66
|
-
"data-test": "value",
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
const html = HyperHtml.renderToString(node)
|
|
70
|
-
|
|
71
|
-
test
|
|
72
|
-
.expect(html)
|
|
73
|
-
.toBe("<div data-test=\"value\"></div>")
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
test.it("mixed boolean and string attributes", () => {
|
|
77
|
-
const node = HyperNode.make("input", {
|
|
78
|
-
type: "checkbox",
|
|
79
|
-
checked: true,
|
|
80
|
-
disabled: false,
|
|
81
|
-
name: "test",
|
|
82
|
-
value: "on",
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
const html = HyperHtml.renderToString(node)
|
|
86
|
-
|
|
87
|
-
test
|
|
88
|
-
.expect(html)
|
|
89
|
-
.toBe("<input type=\"checkbox\" checked name=\"test\" value=\"on\">")
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
test.it("data-* attributes with object values are JSON stringified", () => {
|
|
93
|
-
const node = HyperNode.make("div", {
|
|
94
|
-
"data-signals": {
|
|
95
|
-
draft: "",
|
|
96
|
-
pendingDraft: "",
|
|
97
|
-
username: "User123",
|
|
98
|
-
},
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
const html = HyperHtml.renderToString(node)
|
|
102
|
-
|
|
103
|
-
test
|
|
104
|
-
.expect(html)
|
|
105
|
-
.toBe(
|
|
106
|
-
"<div data-signals=\"{"draft":"","pendingDraft":"","username":"User123"}\"></div>",
|
|
107
|
-
)
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
test.it("data-* attributes with array values are JSON stringified", () => {
|
|
111
|
-
const node = HyperNode.make("div", {
|
|
112
|
-
"data-items": [1, 2, 3],
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
const html = HyperHtml.renderToString(node)
|
|
116
|
-
|
|
117
|
-
test
|
|
118
|
-
.expect(html)
|
|
119
|
-
.toBe("<div data-items=\"[1,2,3]\"></div>")
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
test.it("data-* attributes with nested object values", () => {
|
|
123
|
-
const node = HyperNode.make("div", {
|
|
124
|
-
"data-config": {
|
|
125
|
-
user: { name: "John", active: true },
|
|
126
|
-
settings: { theme: "dark" },
|
|
127
|
-
},
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const html = HyperHtml.renderToString(node)
|
|
131
|
-
|
|
132
|
-
test
|
|
133
|
-
.expect(html)
|
|
134
|
-
.toBe(
|
|
135
|
-
"<div data-config=\"{"user":{"name":"John","active":true},"settings":{"theme":"dark"}}\"></div>",
|
|
136
|
-
)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
test.it("data-* string values are not JSON stringified", () => {
|
|
140
|
-
const node = HyperNode.make("div", {
|
|
141
|
-
"data-value": "hello world",
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
const html = HyperHtml.renderToString(node)
|
|
145
|
-
|
|
146
|
-
test
|
|
147
|
-
.expect(html)
|
|
148
|
-
.toBe("<div data-value=\"hello world\"></div>")
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
test.it("non-data attributes with object values are not JSON stringified", () => {
|
|
152
|
-
const node = HyperNode.make("div", {
|
|
153
|
-
style: "color: red",
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
const html = HyperHtml.renderToString(node)
|
|
157
|
-
|
|
158
|
-
test
|
|
159
|
-
.expect(html)
|
|
160
|
-
.toBe("<div style=\"color: red\"></div>")
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
test.it("script with function child renders as IIFE", () => {
|
|
164
|
-
const handler = (window: Window) => {
|
|
165
|
-
console.log("Hello from", window.document.title)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const node = HyperNode.make("script", {
|
|
169
|
-
children: handler,
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
const html = HyperHtml.renderToString(node)
|
|
173
|
-
|
|
174
|
-
test
|
|
175
|
-
.expect(html)
|
|
176
|
-
.toBe(`<script>(${handler.toString()})(window)</script>`)
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
test.it("script with arrow function child renders as IIFE", () => {
|
|
180
|
-
const node = HyperNode.make("script", {
|
|
181
|
-
children: (window: Window) => {
|
|
182
|
-
window.alert("test")
|
|
183
|
-
},
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
const html = HyperHtml.renderToString(node)
|
|
187
|
-
|
|
188
|
-
test
|
|
189
|
-
.expect(html)
|
|
190
|
-
.toContain("<script>(")
|
|
191
|
-
test
|
|
192
|
-
.expect(html)
|
|
193
|
-
.toContain(")(window)</script>")
|
|
194
|
-
test
|
|
195
|
-
.expect(html)
|
|
196
|
-
.toContain("window.alert")
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
test.it("script with string child renders normally", () => {
|
|
200
|
-
const node = HyperNode.make("script", {
|
|
201
|
-
children: "console.log('hello')",
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
const html = HyperHtml.renderToString(node)
|
|
205
|
-
|
|
206
|
-
test
|
|
207
|
-
.expect(html)
|
|
208
|
-
.toBe("<script>console.log('hello')</script>")
|
|
209
|
-
})
|