effect-start 0.20.1 → 0.22.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 (105) hide show
  1. package/README.md +1 -4
  2. package/dist/Cookies.js +392 -0
  3. package/dist/FileSystem.js +131 -0
  4. package/dist/Socket.js +37 -0
  5. package/package.json +39 -40
  6. package/src/Commander.ts +73 -130
  7. package/src/ContentNegotiation.ts +68 -100
  8. package/src/Cookies.ts +408 -0
  9. package/src/Development.ts +48 -63
  10. package/src/Effectify.ts +222 -206
  11. package/src/Entity.ts +59 -86
  12. package/src/FilePathPattern.ts +5 -5
  13. package/src/FileRouter.ts +38 -63
  14. package/src/FileRouterCodegen.ts +64 -56
  15. package/src/FileSystem.ts +390 -0
  16. package/src/Http.ts +17 -50
  17. package/src/PathPattern.ts +33 -41
  18. package/src/PlatformError.ts +29 -50
  19. package/src/PlatformRuntime.ts +39 -47
  20. package/src/Route.ts +68 -187
  21. package/src/RouteBody.ts +45 -161
  22. package/src/RouteHook.ts +22 -45
  23. package/src/RouteHttp.ts +88 -142
  24. package/src/RouteHttpTracer.ts +25 -26
  25. package/src/RouteMount.ts +100 -238
  26. package/src/RouteSchema.ts +67 -201
  27. package/src/RouteSse.ts +28 -82
  28. package/src/RouteTree.ts +31 -79
  29. package/src/RouteTrie.ts +13 -32
  30. package/src/SchemaExtra.ts +3 -5
  31. package/src/Socket.ts +51 -0
  32. package/src/Start.ts +20 -21
  33. package/src/StreamExtra.ts +93 -96
  34. package/src/TuplePathPattern.ts +54 -43
  35. package/src/Unique.ts +9 -15
  36. package/src/Values.ts +26 -30
  37. package/src/bun/BunBundle.ts +27 -73
  38. package/src/bun/BunImportTrackerPlugin.ts +67 -65
  39. package/src/bun/BunRoute.ts +12 -31
  40. package/src/bun/BunRuntime.ts +3 -10
  41. package/src/bun/BunServer.ts +50 -60
  42. package/src/bun/BunVirtualFilesPlugin.ts +1 -4
  43. package/src/bun/_BunEnhancedResolve.ts +17 -42
  44. package/src/bun/_empty.html +0 -1
  45. package/src/bundler/Bundle.ts +20 -36
  46. package/src/bundler/BundleFiles.ts +36 -56
  47. package/src/client/Overlay.ts +1 -2
  48. package/src/client/ScrollState.ts +5 -9
  49. package/src/client/index.ts +10 -13
  50. package/src/datastar/actions/fetch.ts +29 -48
  51. package/src/datastar/actions/peek.ts +1 -5
  52. package/src/datastar/actions/setAll.ts +2 -2
  53. package/src/datastar/actions/toggleAll.ts +2 -2
  54. package/src/datastar/attributes/attr.ts +17 -18
  55. package/src/datastar/attributes/bind.ts +41 -61
  56. package/src/datastar/attributes/class.ts +2 -5
  57. package/src/datastar/attributes/computed.ts +2 -10
  58. package/src/datastar/attributes/effect.ts +1 -2
  59. package/src/datastar/attributes/indicator.ts +2 -8
  60. package/src/datastar/attributes/init.ts +2 -10
  61. package/src/datastar/attributes/jsonSignals.ts +1 -6
  62. package/src/datastar/attributes/on.ts +4 -13
  63. package/src/datastar/attributes/onIntersect.ts +10 -22
  64. package/src/datastar/attributes/onInterval.ts +2 -10
  65. package/src/datastar/attributes/onSignalPatch.ts +18 -28
  66. package/src/datastar/attributes/ref.ts +1 -2
  67. package/src/datastar/attributes/show.ts +1 -2
  68. package/src/datastar/attributes/signals.ts +1 -5
  69. package/src/datastar/attributes/style.ts +6 -12
  70. package/src/datastar/attributes/text.ts +1 -2
  71. package/src/datastar/engine.ts +102 -158
  72. package/src/datastar/index.ts +2 -2
  73. package/src/datastar/utils.ts +16 -51
  74. package/src/datastar/watchers/patchElements.ts +35 -93
  75. package/src/datastar/watchers/patchSignals.ts +1 -2
  76. package/src/experimental/EncryptedCookies.ts +81 -175
  77. package/src/experimental/index.ts +0 -1
  78. package/src/hyper/Hyper.ts +14 -33
  79. package/src/hyper/HyperHtml.ts +13 -10
  80. package/src/hyper/HyperNode.ts +2 -7
  81. package/src/hyper/HyperRoute.ts +2 -5
  82. package/src/hyper/jsx-runtime.ts +2 -10
  83. package/src/hyper/jsx.d.ts +171 -440
  84. package/src/lint/plugin.js +276 -0
  85. package/src/node/NodeFileSystem.ts +140 -202
  86. package/src/node/NodeUtils.ts +1 -3
  87. package/src/testing/TestLogger.ts +9 -22
  88. package/src/testing/index.ts +0 -1
  89. package/src/testing/utils.ts +30 -31
  90. package/src/x/cloudflare/CloudflareTunnel.ts +53 -65
  91. package/src/x/datastar/Datastar.ts +3 -10
  92. package/src/x/datastar/index.ts +1 -3
  93. package/src/x/datastar/jsx-datastar.d.ts +1 -4
  94. package/src/x/tailwind/TailwindPlugin.ts +119 -112
  95. package/src/x/tailwind/compile.ts +10 -33
  96. package/src/x/tailwind/plugin.ts +2 -2
  97. package/src/HttpAppExtra.ts +0 -478
  98. package/src/HttpUtils.ts +0 -17
  99. package/src/bun/BunPlatformHttpServer.ts +0 -88
  100. package/src/bun/BunServerRequest.ts +0 -396
  101. package/src/bundler/BundleHttp.ts +0 -259
  102. package/src/experimental/SseHttpResponse.ts +0 -55
  103. package/src/middlewares/BasicAuthMiddleware.ts +0 -36
  104. package/src/middlewares/index.ts +0 -1
  105. package/src/testing/TestHttpClient.ts +0 -148
@@ -1,15 +1,8 @@
1
- import { watcher } from "../engine.ts"
2
- import type { HTMLOrSVG, WatcherContext } from "../engine.ts"
3
- import {
4
- aliasify,
5
- isHTMLOrSVG,
6
- supportsViewTransitions,
7
- } from "../utils.ts"
8
-
9
- const isValidType = <T extends readonly string[]>(
10
- arr: T,
11
- value: string,
12
- ): value is T[number] => (arr as readonly string[]).includes(value)
1
+ import { watcher, type HTMLOrSVG, type WatcherContext } from "../engine.ts"
2
+ import { aliasify, isHTMLOrSVG, supportsViewTransitions } from "../utils.ts"
3
+
4
+ const isValidType = <T extends readonly string[]>(arr: T, value: string): value is T[number] =>
5
+ (arr as ReadonlyArray<string>).includes(value)
13
6
 
14
7
  const PATCH_MODES = [
15
8
  "remove",
@@ -38,13 +31,7 @@ watcher({
38
31
  name: "datastar-patch-elements",
39
32
  apply(
40
33
  ctx,
41
- {
42
- selector = "",
43
- mode = "outer",
44
- namespace = "html",
45
- useViewTransition = "",
46
- elements = "",
47
- },
34
+ { selector = "", mode = "outer", namespace = "html", useViewTransition = "", elements = "" },
48
35
  ) {
49
36
  if (!isValidType(PATCH_MODES, mode)) {
50
37
  throw ctx.error("PatchElementsInvalidMode", { mode })
@@ -78,27 +65,16 @@ const onPatchElements = (
78
65
  { error }: WatcherContext,
79
66
  { selector, mode, namespace, elements }: PatchElementsArgs,
80
67
  ) => {
81
- const elementsWithSvgsRemoved = elements.replace(
82
- /<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,
83
- "",
84
- )
68
+ const elementsWithSvgsRemoved = elements.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, "")
85
69
  const hasHtml = /<\/html>/.test(elementsWithSvgsRemoved)
86
70
  const hasHead = /<\/head>/.test(elementsWithSvgsRemoved)
87
71
  const hasBody = /<\/body>/.test(elementsWithSvgsRemoved)
88
72
 
89
- const wrapperTag = namespace === "svg"
90
- ? "svg"
91
- : namespace === "mathml"
92
- ? "math"
93
- : ""
94
- const wrappedEls = wrapperTag
95
- ? `<${wrapperTag}>${elements}</${wrapperTag}>`
96
- : elements
73
+ const wrapperTag = namespace === "svg" ? "svg" : namespace === "mathml" ? "math" : ""
74
+ const wrappedEls = wrapperTag ? `<${wrapperTag}>${elements}</${wrapperTag}>` : elements
97
75
 
98
76
  const newDocument = new DOMParser().parseFromString(
99
- hasHtml || hasHead || hasBody
100
- ? elements
101
- : `<body><template>${wrappedEls}</template></body>`,
77
+ hasHtml || hasHead || hasBody ? elements : `<body><template>${wrappedEls}</template></body>`,
102
78
  "text/html",
103
79
  )
104
80
 
@@ -113,10 +89,7 @@ const onPatchElements = (
113
89
  } else if (hasBody) {
114
90
  newContent.appendChild(newDocument.body)
115
91
  } else if (wrapperTag) {
116
- const wrapperEl = newDocument
117
- .querySelector("template")!
118
- .content
119
- .querySelector(wrapperTag)!
92
+ const wrapperEl = newDocument.querySelector("template")!.content.querySelector(wrapperTag)!
120
93
  for (const child of wrapperEl.childNodes) {
121
94
  newContent.appendChild(child)
122
95
  }
@@ -162,9 +135,8 @@ for (const script of document.querySelectorAll("script")) {
162
135
  }
163
136
 
164
137
  const execute = (target: Element): void => {
165
- const elScripts = target instanceof HTMLScriptElement
166
- ? [target]
167
- : target.querySelectorAll("script")
138
+ const elScripts =
139
+ target instanceof HTMLScriptElement ? [target] : target.querySelectorAll("script")
168
140
  for (const old of elScripts) {
169
141
  if (!scripts.has(old)) {
170
142
  const script = document.createElement("script")
@@ -235,11 +207,11 @@ const morph = (
235
207
  mode: "outer" | "inner" = "outer",
236
208
  ): void => {
237
209
  if (
238
- (isHTMLOrSVG(oldElt)
239
- && isHTMLOrSVG(newContent)
240
- && (oldElt as HTMLOrSVG).hasAttribute(aliasedIgnoreMorph)
241
- && (newContent as HTMLOrSVG).hasAttribute(aliasedIgnoreMorph))
242
- || oldElt.parentElement?.closest(aliasedIgnoreMorphAttr)
210
+ (isHTMLOrSVG(oldElt) &&
211
+ isHTMLOrSVG(newContent) &&
212
+ (oldElt as HTMLOrSVG).hasAttribute(aliasedIgnoreMorph) &&
213
+ (newContent as HTMLOrSVG).hasAttribute(aliasedIgnoreMorph)) ||
214
+ oldElt.parentElement?.closest(aliasedIgnoreMorphAttr)
243
215
  ) {
244
216
  return
245
217
  }
@@ -286,12 +258,7 @@ const morph = (
286
258
  populateIdMapWithTree(parent, oldIdElements)
287
259
  populateIdMapWithTree(normalizedElt, newIdElements)
288
260
 
289
- morphChildren(
290
- parent,
291
- normalizedElt,
292
- mode === "outer" ? oldElt : null,
293
- oldElt.nextSibling,
294
- )
261
+ morphChildren(parent, normalizedElt, mode === "outer" ? oldElt : null, oldElt.nextSibling)
295
262
 
296
263
  ctxPantry.remove()
297
264
  }
@@ -302,10 +269,7 @@ const morphChildren = (
302
269
  insertionPoint: Node | null = null,
303
270
  endPoint: Node | null = null,
304
271
  ): void => {
305
- if (
306
- oldParent instanceof HTMLTemplateElement
307
- && newParent instanceof HTMLTemplateElement
308
- ) {
272
+ if (oldParent instanceof HTMLTemplateElement && newParent instanceof HTMLTemplateElement) {
309
273
  oldParent = oldParent.content as unknown as Element
310
274
  newParent = newParent.content as unknown as Element
311
275
  }
@@ -373,11 +337,7 @@ const morphChildren = (
373
337
  }
374
338
  }
375
339
 
376
- const findBestMatch = (
377
- node: Node,
378
- startPoint: Node | null,
379
- endPoint: Node | null,
380
- ): Node | null => {
340
+ const findBestMatch = (node: Node, startPoint: Node | null, endPoint: Node | null): Node | null => {
381
341
  let bestMatch: Node | null | undefined = null
382
342
  let nextSibling = node.nextSibling
383
343
  let siblingSoftMatchCount = 0
@@ -434,15 +394,12 @@ const findBestMatch = (
434
394
  }
435
395
 
436
396
  const isSoftMatch = (oldNode: Node, newNode: Node): boolean =>
437
- oldNode.nodeType === newNode.nodeType
438
- && (oldNode as Element).tagName === (newNode as Element).tagName
439
- && (!(oldNode as Element).id
440
- || (oldNode as Element).id === (newNode as Element).id)
397
+ oldNode.nodeType === newNode.nodeType &&
398
+ (oldNode as Element).tagName === (newNode as Element).tagName &&
399
+ (!(oldNode as Element).id || (oldNode as Element).id === (newNode as Element).id)
441
400
 
442
401
  const removeNode = (node: Node): void => {
443
- ctxIdMap.has(node)
444
- ? moveBefore(ctxPantry, node, null)
445
- : node.parentNode?.removeChild(node)
402
+ ctxIdMap.has(node) ? moveBefore(ctxPantry, node, null) : node.parentNode?.removeChild(node)
446
403
  }
447
404
 
448
405
  const moveBefore: (parentNode: Node, node: Node, after: Node | null) => void =
@@ -451,35 +408,26 @@ const moveBefore: (parentNode: Node, node: Node, after: Node | null) => void =
451
408
 
452
409
  const aliasedPreserveAttr = aliasify("preserve-attr")
453
410
 
454
- const morphNode = (
455
- oldNode: Node,
456
- newNode: Node,
457
- ): Node => {
411
+ const morphNode = (oldNode: Node, newNode: Node): Node => {
458
412
  const type = newNode.nodeType
459
413
 
460
414
  if (type === 1) {
461
415
  const oldElt = oldNode as Element
462
416
  const newElt = newNode as Element
463
417
  const shouldScopeChildren = oldElt.hasAttribute("data-scope-children")
464
- if (
465
- oldElt.hasAttribute(aliasedIgnoreMorph)
466
- && newElt.hasAttribute(aliasedIgnoreMorph)
467
- ) {
418
+ if (oldElt.hasAttribute(aliasedIgnoreMorph) && newElt.hasAttribute(aliasedIgnoreMorph)) {
468
419
  return oldNode
469
420
  }
470
421
 
471
422
  if (
472
- oldElt instanceof HTMLInputElement
473
- && newElt instanceof HTMLInputElement
474
- && newElt.type !== "file"
423
+ oldElt instanceof HTMLInputElement &&
424
+ newElt instanceof HTMLInputElement &&
425
+ newElt.type !== "file"
475
426
  ) {
476
427
  if (newElt.getAttribute("value") !== oldElt.getAttribute("value")) {
477
428
  oldElt.value = newElt.getAttribute("value") ?? ""
478
429
  }
479
- } else if (
480
- oldElt instanceof HTMLTextAreaElement
481
- && newElt instanceof HTMLTextAreaElement
482
- ) {
430
+ } else if (oldElt instanceof HTMLTextAreaElement && newElt instanceof HTMLTextAreaElement) {
483
431
  if (newElt.value !== oldElt.value) {
484
432
  oldElt.value = newElt.value
485
433
  }
@@ -488,16 +436,12 @@ const morphNode = (
488
436
  }
489
437
  }
490
438
 
491
- const preserveAttrs = (
492
- (newNode as HTMLElement).getAttribute(aliasedPreserveAttr) ?? ""
439
+ const preserveAttrs = ((newNode as HTMLElement).getAttribute(aliasedPreserveAttr) ?? "").split(
440
+ " ",
493
441
  )
494
- .split(" ")
495
442
 
496
443
  for (const { name, value } of newElt.attributes) {
497
- if (
498
- oldElt.getAttribute(name) !== value
499
- && !preserveAttrs.includes(name)
500
- ) {
444
+ if (oldElt.getAttribute(name) !== value && !preserveAttrs.includes(name)) {
501
445
  oldElt.setAttribute(name, value)
502
446
  }
503
447
  }
@@ -518,9 +462,7 @@ const morphNode = (
518
462
  }
519
463
 
520
464
  if (shouldScopeChildren) {
521
- oldElt.dispatchEvent(
522
- new CustomEvent("datastar:scope-children", { bubbles: false }),
523
- )
465
+ oldElt.dispatchEvent(new CustomEvent("datastar:scope-children", { bubbles: false }))
524
466
  }
525
467
  }
526
468
 
@@ -1,5 +1,4 @@
1
- import { watcher } from "../engine.ts"
2
- import { mergePatch } from "../engine.ts"
1
+ import { mergePatch, watcher } from "../engine.ts"
3
2
  import { jsStrToObject } from "../utils.ts"
4
3
 
5
4
  watcher({
@@ -1,15 +1,8 @@
1
- import {
2
- Cookies,
3
- HttpApp,
4
- HttpServerResponse,
5
- } from "@effect/platform"
6
- import {
7
- Effect,
8
- pipe,
9
- } from "effect"
1
+ import * as Cookies from "../Cookies.ts"
10
2
  import * as Config from "effect/Config"
11
3
  import * as Context from "effect/Context"
12
4
  import * as Data from "effect/Data"
5
+ import * as Effect from "effect/Effect"
13
6
  import * as Layer from "effect/Layer"
14
7
 
15
8
  type CookieValue =
@@ -19,43 +12,33 @@ type CookieValue =
19
12
  | null
20
13
  | undefined
21
14
  | {
22
- [key: string]:
23
- | CookieValue
24
- // some libraries, like XState, contain unknown in type
25
- // that is serializable
26
- | unknown
27
- }
15
+ [key: string]:
16
+ | CookieValue
17
+ // some libraries, like XState, contain unknown in type
18
+ // that is serializable
19
+ | unknown
20
+ }
28
21
  | CookieValue[]
29
22
 
30
- export class EncryptedCookiesError
31
- extends Data.TaggedError("EncryptedCookiesError")<{
32
- cause: unknown
33
- cookie?: Cookies.Cookie
34
- }>
35
- {}
23
+ export class EncryptedCookiesError extends Data.TaggedError("EncryptedCookiesError")<{
24
+ cause: unknown
25
+ cookie?: Cookies.Cookie
26
+ }> {}
36
27
 
37
28
  export class EncryptedCookies extends Context.Tag("EncryptedCookies")<
38
29
  EncryptedCookies,
39
30
  {
40
- encrypt: (
41
- value: CookieValue,
42
- ) => Effect.Effect<string, EncryptedCookiesError>
43
- decrypt: (
44
- encryptedValue: string,
45
- ) => Effect.Effect<CookieValue, EncryptedCookiesError>
46
- encryptCookie: (
47
- cookie: Cookies.Cookie,
48
- ) => Effect.Effect<Cookies.Cookie, EncryptedCookiesError>
49
- decryptCookie: (
50
- cookie: Cookies.Cookie,
51
- ) => Effect.Effect<Cookies.Cookie, EncryptedCookiesError>
31
+ encrypt: (value: CookieValue) => Effect.Effect<string, EncryptedCookiesError>
32
+ decrypt: (encryptedValue: string) => Effect.Effect<CookieValue, EncryptedCookiesError>
33
+ encryptCookie: (cookie: Cookies.Cookie) => Effect.Effect<Cookies.Cookie, EncryptedCookiesError>
34
+ decryptCookie: (cookie: Cookies.Cookie) => Effect.Effect<Cookies.Cookie, EncryptedCookiesError>
52
35
  }
53
36
  >() {}
54
37
 
55
38
  export function layer(options: { secret: string }) {
56
39
  return Layer.effect(
57
40
  EncryptedCookies,
58
- Effect.gen(function*() {
41
+ Effect.gen(function* () {
59
42
  const keyMaterial = yield* deriveKeyMaterial(options.secret)
60
43
 
61
44
  // Pre-derive both keys once
@@ -63,39 +46,28 @@ export function layer(options: { secret: string }) {
63
46
  const decryptKey = yield* deriveKey(keyMaterial, ["decrypt"])
64
47
 
65
48
  return EncryptedCookies.of({
66
- encrypt: (value: CookieValue) =>
67
- encryptWithDerivedKey(value, encryptKey),
68
- decrypt: (encryptedValue: string) =>
69
- decryptWithDerivedKey(encryptedValue, decryptKey),
70
- encryptCookie: (cookie: Cookies.Cookie) =>
71
- encryptCookieWithDerivedKey(cookie, encryptKey),
72
- decryptCookie: (cookie: Cookies.Cookie) =>
73
- decryptCookieWithDerivedKey(cookie, decryptKey),
49
+ encrypt: (value: CookieValue) => encryptWithDerivedKey(value, encryptKey),
50
+ decrypt: (encryptedValue: string) => decryptWithDerivedKey(encryptedValue, decryptKey),
51
+ encryptCookie: (cookie: Cookies.Cookie) => encryptCookieWithDerivedKey(cookie, encryptKey),
52
+ decryptCookie: (cookie: Cookies.Cookie) => decryptCookieWithDerivedKey(cookie, decryptKey),
74
53
  })
75
54
  }),
76
55
  )
77
56
  }
78
57
 
79
58
  export function layerConfig(name = "SECRET_KEY_BASE") {
80
- return Effect
81
- .gen(function*() {
82
- const secret = yield* pipe(
83
- Config.nonEmptyString(name),
84
- Effect.flatMap((value) => {
85
- return (value.length < 40)
86
- ? Effect.fail(new Error("ba"))
87
- : Effect.succeed(value)
88
- }),
89
- Effect.catchAll((err) => {
90
- return Effect.dieMessage(
91
- "SECRET_KEY_BASE must be at least 40 characters",
92
- )
93
- }),
94
- )
59
+ return Effect.gen(function* () {
60
+ const secret = yield* Config.nonEmptyString(name).pipe(
61
+ Effect.flatMap((value) => {
62
+ return value.length < 40 ? Effect.fail(new Error("ba")) : Effect.succeed(value)
63
+ }),
64
+ Effect.catchAll((err) => {
65
+ return Effect.dieMessage("SECRET_KEY_BASE must be at least 40 characters")
66
+ }),
67
+ )
95
68
 
96
- return layer({ secret })
97
- })
98
- .pipe(Layer.unwrapEffect)
69
+ return layer({ secret })
70
+ }).pipe(Layer.unwrapEffect)
99
71
  }
100
72
 
101
73
  function encodeToBase64Segments(
@@ -103,29 +75,21 @@ function encodeToBase64Segments(
103
75
  iv: Uint8Array,
104
76
  authTag: Uint8Array,
105
77
  ): string {
106
- return [
107
- base64urlEncode(ciphertext),
108
- base64urlEncode(iv),
109
- base64urlEncode(authTag),
110
- ]
111
- .join(".")
78
+ return [base64urlEncode(ciphertext), base64urlEncode(iv), base64urlEncode(authTag)].join(".")
112
79
  }
113
80
 
114
81
  function base64urlEncode(data: Uint8Array): string {
115
82
  const base64 = btoa(String.fromCharCode(...data))
116
- return base64
117
- .replace(/\+/g, "-")
118
- .replace(/\//g, "_")
119
- .replace(/=/g, "")
83
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
120
84
  }
121
85
 
122
86
  function decodeFromBase64Segments(
123
- segments: string[],
87
+ segments: Array<string>,
124
88
  ): Effect.Effect<
125
89
  { ciphertext: Uint8Array; iv: Uint8Array; authTag: Uint8Array },
126
90
  EncryptedCookiesError
127
91
  > {
128
- return Effect.gen(function*() {
92
+ return Effect.gen(function* () {
129
93
  const [ciphertextB64, ivB64, authTagB64] = segments
130
94
 
131
95
  const ciphertext = yield* Effect.try({
@@ -149,16 +113,14 @@ function decodeFromBase64Segments(
149
113
 
150
114
  function base64urlDecode(data: string): Uint8Array {
151
115
  // Convert base64url back to standard base64
152
- let base64 = data
153
- .replace(/-/g, "+")
154
- .replace(/_/g, "/")
116
+ let base64 = data.replace(/-/g, "+").replace(/_/g, "/")
155
117
 
156
118
  // Add padding if needed
157
119
  while (base64.length % 4) {
158
120
  base64 += "="
159
121
  }
160
122
 
161
- return Uint8Array.from(atob(base64), c => c.charCodeAt(0))
123
+ return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0))
162
124
  }
163
125
 
164
126
  /**
@@ -168,7 +130,7 @@ function encryptWithDerivedKey(
168
130
  value: CookieValue,
169
131
  derivedKey: CryptoKey,
170
132
  ): Effect.Effect<string, EncryptedCookiesError> {
171
- return Effect.gen(function*() {
133
+ return Effect.gen(function* () {
172
134
  if (value === null || value === undefined) {
173
135
  return yield* Effect.fail(
174
136
  new EncryptedCookiesError({
@@ -181,12 +143,7 @@ function encryptWithDerivedKey(
181
143
  const data = new TextEncoder().encode(JSON.stringify(value))
182
144
 
183
145
  const encrypted = yield* Effect.tryPromise({
184
- try: () =>
185
- crypto.subtle.encrypt(
186
- { name: "AES-GCM", iv },
187
- derivedKey,
188
- data,
189
- ),
146
+ try: () => crypto.subtle.encrypt({ name: "AES-GCM", iv }, derivedKey, data),
190
147
  catch: (error) => new EncryptedCookiesError({ cause: error }),
191
148
  })
192
149
 
@@ -203,7 +160,7 @@ export function encrypt(
203
160
  value: CookieValue,
204
161
  options: { key: CryptoKey } | { secret: string },
205
162
  ): Effect.Effect<string, EncryptedCookiesError> {
206
- return Effect.gen(function*() {
163
+ return Effect.gen(function* () {
207
164
  if ("key" in options) {
208
165
  return yield* encryptWithDerivedKey(value, options.key)
209
166
  }
@@ -218,10 +175,8 @@ function decryptWithDerivedKey(
218
175
  encryptedValue: string,
219
176
  derivedKey: CryptoKey,
220
177
  ): Effect.Effect<CookieValue, EncryptedCookiesError> {
221
- return Effect.gen(function*() {
222
- if (
223
- !encryptedValue || encryptedValue === null || encryptedValue === undefined
224
- ) {
178
+ return Effect.gen(function* () {
179
+ if (!encryptedValue || encryptedValue === null || encryptedValue === undefined) {
225
180
  return yield* Effect.fail(
226
181
  new EncryptedCookiesError({
227
182
  cause: "Cannot decrypt null, undefined, or empty value",
@@ -238,9 +193,7 @@ function decryptWithDerivedKey(
238
193
  )
239
194
  }
240
195
 
241
- const { ciphertext, iv, authTag } = yield* decodeFromBase64Segments(
242
- segments,
243
- )
196
+ const { ciphertext, iv, authTag } = yield* decodeFromBase64Segments(segments)
244
197
 
245
198
  const encryptedData = new Uint8Array(ciphertext.length + authTag.length)
246
199
  encryptedData.set(ciphertext)
@@ -248,11 +201,7 @@ function decryptWithDerivedKey(
248
201
 
249
202
  const decrypted = yield* Effect.tryPromise({
250
203
  try: () =>
251
- crypto.subtle.decrypt(
252
- { name: "AES-GCM", iv: iv.slice(0) },
253
- derivedKey,
254
- encryptedData,
255
- ),
204
+ crypto.subtle.decrypt({ name: "AES-GCM", iv: iv.slice(0) }, derivedKey, encryptedData),
256
205
  catch: (error) => new EncryptedCookiesError({ cause: error }),
257
206
  })
258
207
 
@@ -269,19 +218,16 @@ function encryptCookieWithDerivedKey(
269
218
  cookie: Cookies.Cookie,
270
219
  derivedKey: CryptoKey,
271
220
  ): Effect.Effect<Cookies.Cookie, EncryptedCookiesError> {
272
- return Effect.gen(function*() {
273
- const encryptedValue = yield* encryptWithDerivedKey(
274
- cookie.value,
275
- derivedKey,
276
- )
277
- .pipe(
278
- Effect.mapError(error =>
221
+ return Effect.gen(function* () {
222
+ const encryptedValue = yield* encryptWithDerivedKey(cookie.value, derivedKey).pipe(
223
+ Effect.mapError(
224
+ (error) =>
279
225
  new EncryptedCookiesError({
280
226
  cause: error.cause,
281
227
  cookie,
282
- })
283
- ),
284
- )
228
+ }),
229
+ ),
230
+ )
285
231
  return Cookies.unsafeMakeCookie(cookie.name, encryptedValue, cookie.options)
286
232
  })
287
233
  }
@@ -289,24 +235,17 @@ function decryptCookieWithDerivedKey(
289
235
  cookie: Cookies.Cookie,
290
236
  derivedKey: CryptoKey,
291
237
  ): Effect.Effect<Cookies.Cookie, EncryptedCookiesError> {
292
- return Effect.gen(function*() {
293
- const decryptedValue = yield* decryptWithDerivedKey(
294
- cookie.value,
295
- derivedKey,
296
- )
297
- .pipe(
298
- Effect.mapError(error =>
238
+ return Effect.gen(function* () {
239
+ const decryptedValue = yield* decryptWithDerivedKey(cookie.value, derivedKey).pipe(
240
+ Effect.mapError(
241
+ (error) =>
299
242
  new EncryptedCookiesError({
300
243
  cause: error.cause,
301
244
  cookie,
302
- })
303
- ),
304
- )
305
- return Cookies.unsafeMakeCookie(
306
- cookie.name,
307
- JSON.stringify(decryptedValue),
308
- cookie.options,
245
+ }),
246
+ ),
309
247
  )
248
+ return Cookies.unsafeMakeCookie(cookie.name, JSON.stringify(decryptedValue), cookie.options)
310
249
  })
311
250
  }
312
251
 
@@ -314,22 +253,22 @@ export function encryptCookie(
314
253
  cookie: Cookies.Cookie,
315
254
  options: { key: CryptoKey } | { secret: string },
316
255
  ): Effect.Effect<Cookies.Cookie, EncryptedCookiesError> {
317
- return Effect.gen(function*() {
256
+ return Effect.gen(function* () {
318
257
  if ("key" in options) {
319
258
  return yield* encryptCookieWithDerivedKey(cookie, options.key)
320
259
  }
321
260
 
322
261
  const encryptedValue = yield* encrypt(cookie.value, {
323
262
  secret: options.secret,
324
- })
325
- .pipe(
326
- Effect.mapError(error =>
263
+ }).pipe(
264
+ Effect.mapError(
265
+ (error) =>
327
266
  new EncryptedCookiesError({
328
267
  cause: error.cause,
329
268
  cookie,
330
- })
331
- ),
332
- )
269
+ }),
270
+ ),
271
+ )
333
272
  return Cookies.unsafeMakeCookie(cookie.name, encryptedValue, cookie.options)
334
273
  })
335
274
  }
@@ -338,27 +277,23 @@ export function decryptCookie(
338
277
  cookie: Cookies.Cookie,
339
278
  options: { key: CryptoKey } | { secret: string },
340
279
  ): Effect.Effect<Cookies.Cookie, EncryptedCookiesError> {
341
- return Effect.gen(function*() {
280
+ return Effect.gen(function* () {
342
281
  if ("key" in options) {
343
282
  return yield* decryptCookieWithDerivedKey(cookie, options.key)
344
283
  }
345
284
 
346
285
  const decryptedValue = yield* decrypt(cookie.value, {
347
286
  secret: options.secret,
348
- })
349
- .pipe(
350
- Effect.mapError(error =>
287
+ }).pipe(
288
+ Effect.mapError(
289
+ (error) =>
351
290
  new EncryptedCookiesError({
352
291
  cause: error.cause,
353
292
  cookie,
354
- })
355
- ),
356
- )
357
- return Cookies.unsafeMakeCookie(
358
- cookie.name,
359
- JSON.stringify(decryptedValue),
360
- cookie.options,
293
+ }),
294
+ ),
361
295
  )
296
+ return Cookies.unsafeMakeCookie(cookie.name, JSON.stringify(decryptedValue), cookie.options)
362
297
  })
363
298
  }
364
299
 
@@ -366,7 +301,7 @@ export function decrypt(
366
301
  encryptedValue: string,
367
302
  options: { key: CryptoKey } | { secret: string },
368
303
  ): Effect.Effect<CookieValue, EncryptedCookiesError> {
369
- return Effect.gen(function*() {
304
+ return Effect.gen(function* () {
370
305
  if ("key" in options) {
371
306
  return yield* decryptWithDerivedKey(encryptedValue, options.key)
372
307
  }
@@ -377,21 +312,15 @@ export function decrypt(
377
312
  })
378
313
  }
379
314
 
380
- function deriveKeyMaterial(
381
- secret: string,
382
- ): Effect.Effect<CryptoKey, EncryptedCookiesError> {
383
- return Effect.gen(function*() {
315
+ function deriveKeyMaterial(secret: string): Effect.Effect<CryptoKey, EncryptedCookiesError> {
316
+ return Effect.gen(function* () {
384
317
  const encoder = new TextEncoder()
385
318
 
386
319
  const keyMaterial = yield* Effect.tryPromise({
387
320
  try: () =>
388
- crypto.subtle.importKey(
389
- "raw",
390
- encoder.encode(secret),
391
- { name: "HKDF" },
392
- false,
393
- ["deriveKey"],
394
- ),
321
+ crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HKDF" }, false, [
322
+ "deriveKey",
323
+ ]),
395
324
  catch: (error) => new EncryptedCookiesError({ cause: error }),
396
325
  })
397
326
 
@@ -401,9 +330,9 @@ function deriveKeyMaterial(
401
330
 
402
331
  function deriveKey(
403
332
  keyMaterial: CryptoKey,
404
- usage: KeyUsage[],
333
+ usage: Array<KeyUsage>,
405
334
  ): Effect.Effect<CryptoKey, EncryptedCookiesError> {
406
- return Effect.gen(function*() {
335
+ return Effect.gen(function* () {
407
336
  const encoder = new TextEncoder()
408
337
 
409
338
  const key = yield* Effect.tryPromise({
@@ -426,26 +355,3 @@ function deriveKey(
426
355
  return key
427
356
  })
428
357
  }
429
-
430
- // TODO something si wrong with return type
431
- export function handleError<E>(
432
- app: HttpApp.Default<E | EncryptedCookiesError>,
433
- ) {
434
- return Effect.gen(function*() {
435
- const res = yield* app.pipe(
436
- Effect.catchTag("EncryptedCookiesError", (error) => {
437
- return HttpServerResponse.empty()
438
- }),
439
- )
440
-
441
- return res
442
- })
443
- }
444
-
445
- function generateFriendlyKey(bits = 128) {
446
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
447
- const length = Math.ceil(bits / Math.log2(chars.length))
448
- const bytes = crypto.getRandomValues(new Uint8Array(length))
449
-
450
- return Array.from(bytes, b => chars[b % chars.length]).join("")
451
- }