effect-start 0.18.0 → 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.
Files changed (80) hide show
  1. package/dist/Development.d.ts +7 -2
  2. package/dist/Development.js +12 -6
  3. package/dist/PlatformRuntime.d.ts +4 -0
  4. package/dist/PlatformRuntime.js +9 -0
  5. package/dist/Route.d.ts +6 -2
  6. package/dist/Route.js +22 -0
  7. package/dist/RouteHttp.d.ts +1 -1
  8. package/dist/RouteHttp.js +12 -19
  9. package/dist/RouteMount.d.ts +2 -1
  10. package/dist/Start.d.ts +1 -5
  11. package/dist/Start.js +1 -8
  12. package/dist/Unique.d.ts +50 -0
  13. package/dist/Unique.js +187 -0
  14. package/dist/bun/BunHttpServer.js +5 -6
  15. package/dist/bun/BunRoute.d.ts +1 -1
  16. package/dist/bun/BunRoute.js +2 -2
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/node/Effectify.d.ts +209 -0
  20. package/dist/node/Effectify.js +19 -0
  21. package/dist/node/FileSystem.d.ts +3 -5
  22. package/dist/node/FileSystem.js +42 -62
  23. package/dist/node/PlatformError.d.ts +46 -0
  24. package/dist/node/PlatformError.js +43 -0
  25. package/dist/testing/TestLogger.js +1 -1
  26. package/package.json +8 -1
  27. package/src/Development.ts +13 -18
  28. package/src/PlatformRuntime.ts +11 -0
  29. package/src/Route.ts +31 -2
  30. package/src/RouteHttp.ts +15 -31
  31. package/src/RouteMount.ts +1 -1
  32. package/src/Start.ts +1 -15
  33. package/src/Unique.ts +232 -0
  34. package/src/bun/BunHttpServer.ts +6 -9
  35. package/src/bun/BunRoute.ts +3 -3
  36. package/src/index.ts +1 -0
  37. package/src/node/Effectify.ts +262 -0
  38. package/src/node/FileSystem.ts +59 -97
  39. package/src/node/PlatformError.ts +102 -0
  40. package/src/testing/TestLogger.ts +1 -1
  41. package/dist/Random.d.ts +0 -5
  42. package/dist/Random.js +0 -49
  43. package/src/Commander.test.ts +0 -1639
  44. package/src/ContentNegotiation.test.ts +0 -603
  45. package/src/Development.test.ts +0 -119
  46. package/src/Entity.test.ts +0 -592
  47. package/src/FileRouterPattern.test.ts +0 -147
  48. package/src/FileRouter_files.test.ts +0 -64
  49. package/src/FileRouter_path.test.ts +0 -145
  50. package/src/FileRouter_tree.test.ts +0 -132
  51. package/src/Http.test.ts +0 -319
  52. package/src/HttpAppExtra.test.ts +0 -103
  53. package/src/HttpUtils.test.ts +0 -85
  54. package/src/PathPattern.test.ts +0 -648
  55. package/src/Random.ts +0 -59
  56. package/src/RouteBody.test.ts +0 -232
  57. package/src/RouteHook.test.ts +0 -40
  58. package/src/RouteHttp.test.ts +0 -2909
  59. package/src/RouteMount.test.ts +0 -481
  60. package/src/RouteSchema.test.ts +0 -427
  61. package/src/RouteSse.test.ts +0 -249
  62. package/src/RouteTree.test.ts +0 -494
  63. package/src/RouteTrie.test.ts +0 -322
  64. package/src/RouterPattern.test.ts +0 -676
  65. package/src/Values.test.ts +0 -263
  66. package/src/bun/BunBundle.test.ts +0 -268
  67. package/src/bun/BunBundle_imports.test.ts +0 -48
  68. package/src/bun/BunHttpServer.test.ts +0 -251
  69. package/src/bun/BunImportTrackerPlugin.test.ts +0 -77
  70. package/src/bun/BunRoute.test.ts +0 -162
  71. package/src/bundler/BundleHttp.test.ts +0 -132
  72. package/src/effect/HttpRouter.test.ts +0 -548
  73. package/src/experimental/EncryptedCookies.test.ts +0 -488
  74. package/src/hyper/HyperHtml.test.ts +0 -209
  75. package/src/hyper/HyperRoute.test.tsx +0 -197
  76. package/src/middlewares/BasicAuthMiddleware.test.ts +0 -84
  77. package/src/testing/TestHttpClient.test.ts +0 -83
  78. package/src/testing/TestLogger.test.ts +0 -51
  79. package/src/x/datastar/Datastar.test.ts +0 -266
  80. package/src/x/tailwind/TailwindPlugin.test.ts +0 -333
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-start",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "exports": {
@@ -44,6 +44,11 @@
44
44
  "types": "./dist/middlewares/index.d.ts",
45
45
  "default": "./dist/middlewares/index.js"
46
46
  },
47
+ "./Unique": {
48
+ "bun": "./src/Unique.ts",
49
+ "types": "./dist/Unique.d.ts",
50
+ "default": "./dist/Unique.js"
51
+ },
47
52
  "./experimental": {
48
53
  "bun": "./src/experimental/index.ts",
49
54
  "types": "./dist/experimental/index.d.ts",
@@ -96,6 +101,8 @@
96
101
  },
97
102
  "files": [
98
103
  "src/",
104
+ "!src/**/*.test.ts",
105
+ "!src/**/*.test.tsx",
99
106
  "dist/",
100
107
  "README.md",
101
108
  "package.json"
@@ -1,18 +1,13 @@
1
- import {
2
- Context,
3
- Effect,
4
- Function,
5
- Layer,
6
- Option,
7
- pipe,
8
- PubSub,
9
- Stream,
10
- } from "effect"
11
- import { globalValue } from "effect/GlobalValue"
12
- import {
13
- Error,
14
- FileSystem,
15
- } from "./node/FileSystem.ts"
1
+ import * as Context from "effect/Context"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Function from "effect/Function"
4
+ import * as GlobalValue from "effect/GlobalValue"
5
+ import * as Layer from "effect/Layer"
6
+ import * as Option from "effect/Option"
7
+ import * as PubSub from "effect/PubSub"
8
+ import * as Stream from "effect/Stream"
9
+ import * as Error from "./node/PlatformError.ts"
10
+ import * as FileSystem from "@effect/platform/FileSystem"
16
11
 
17
12
  export type DevelopmentEvent =
18
13
  | FileSystem.WatchEvent
@@ -20,7 +15,7 @@ export type DevelopmentEvent =
20
15
  readonly _tag: "Reload"
21
16
  }
22
17
 
23
- const devState = globalValue(
18
+ const devState = GlobalValue.globalValue(
24
19
  Symbol.for("effect-start/Development"),
25
20
  () => ({
26
21
  count: 0,
@@ -99,7 +94,7 @@ export const watch = (
99
94
  const pubsub = yield* PubSub.unbounded<DevelopmentEvent>()
100
95
  devState.pubsub = pubsub
101
96
 
102
- yield* pipe(
97
+ yield* Function.pipe(
103
98
  watchSource({
104
99
  path: opts?.path,
105
100
  recursive: opts?.recursive,
@@ -125,7 +120,7 @@ export const layerWatch = (
125
120
 
126
121
  export const stream = (): Stream.Stream<DevelopmentEvent> =>
127
122
  Stream.unwrap(
128
- pipe(
123
+ Function.pipe(
129
124
  Effect.serviceOption(Development),
130
125
  Effect.map(
131
126
  Option.match({
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Are we running within an agent harness, like Claude Code?
3
+ */
4
+ export function isAgentHarness() {
5
+ return typeof process !== "undefined"
6
+ && !process.stdout.isTTY
7
+ && (
8
+ process.env.CLAUDECODE
9
+ || process.env.CURSOR_AGENT
10
+ )
11
+ }
package/src/Route.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as Context from "effect/Context"
2
- import type * as Effect from "effect/Effect"
2
+ import * as Effect from "effect/Effect"
3
3
  import * as Layer from "effect/Layer"
4
4
  import * as Pipeable from "effect/Pipeable"
5
5
  import * as Predicate from "effect/Predicate"
6
- import type * as Entity from "./Entity.ts"
6
+ import * as Entity from "./Entity.ts"
7
7
  import * as RouteBody from "./RouteBody.ts"
8
8
  import * as RouteTree from "./RouteTree.ts"
9
9
  import * as Values from "./Values.ts"
@@ -327,6 +327,18 @@ export {
327
327
  sse,
328
328
  } from "./RouteSse.ts"
329
329
 
330
+ export function redirect(
331
+ url: string | URL,
332
+ status: 301 | 302 | 303 | 307 | 308 = 302,
333
+ ): Entity.Entity<""> {
334
+ return Entity.make("", {
335
+ status,
336
+ headers: {
337
+ location: url instanceof URL ? url.href : url,
338
+ },
339
+ })
340
+ }
341
+
330
342
  export class Routes extends Context.Tag("effect-start/Routes")<
331
343
  Routes,
332
344
  RouteTree.RouteTree
@@ -345,3 +357,20 @@ export function layer(routes: RouteTree.RouteMap | RouteTree.RouteTree) {
345
357
  export {
346
358
  make as tree,
347
359
  } from "./RouteTree.ts"
360
+
361
+ export function lazy<T extends RouteSet.Any>(
362
+ load: () => Promise<{ default: T }>,
363
+ ): Effect.Effect<T, never, never> {
364
+ let cached: T | undefined
365
+ return Effect.suspend(() => {
366
+ if (cached !== undefined) {
367
+ return Effect.succeed(cached)
368
+ }
369
+ return Effect.promise(load).pipe(
370
+ Effect.map((mod) => {
371
+ cached = mod.default
372
+ return cached
373
+ }),
374
+ )
375
+ })
376
+ }
package/src/RouteHttp.ts CHANGED
@@ -17,14 +17,6 @@ import * as RouteMount from "./RouteMount.ts"
17
17
  import * as RouteTree from "./RouteTree.ts"
18
18
  import * as StreamExtra from "./StreamExtra.ts"
19
19
 
20
- export {
21
- currentSpanNameGenerator,
22
- currentTracerDisabledWhen,
23
- parentSpanFromHeaders,
24
- withSpanNameGenerator,
25
- withTracerDisabledWhen,
26
- } from "./RouteHttpTracer.ts"
27
-
28
20
  type UnboundedRouteWithMethod = Route.Route.With<{
29
21
  method: RouteMount.RouteMount.Method
30
22
  format?: RouteBody.Format
@@ -74,6 +66,12 @@ const getStatusFromCause = (cause: Cause.Cause<unknown>): number => {
74
66
  return 500
75
67
  }
76
68
 
69
+ const respondError = (options: { status: number; message: string }): Response =>
70
+ new Response(JSON.stringify(options, null, 2), {
71
+ status: options.status,
72
+ headers: { "content-type": "application/json" },
73
+ })
74
+
77
75
  function streamResponse(
78
76
  stream: Stream.Stream<unknown, unknown, unknown>,
79
77
  headers: Record<string, string | null | undefined>,
@@ -211,9 +209,7 @@ export const toWebHandlerRuntime = <R>(
211
209
 
212
210
  if (methodRoutes.length === 0 && wildcards.length === 0) {
213
211
  return Promise.resolve(
214
- Response.json({ status: 405, message: "method not allowed" }, {
215
- status: 405,
216
- }),
212
+ respondError({ status: 405, message: "method not allowed" }),
217
213
  )
218
214
  }
219
215
 
@@ -234,9 +230,7 @@ export const toWebHandlerRuntime = <R>(
234
230
  && !hasWildcardFormatRoutes
235
231
  ) {
236
232
  return Promise.resolve(
237
- Response.json({ status: 406, message: "not acceptable" }, {
238
- status: 406,
239
- }),
233
+ respondError({ status: 406, message: "not acceptable" }),
240
234
  )
241
235
  }
242
236
 
@@ -315,9 +309,7 @@ export const toWebHandlerRuntime = <R>(
315
309
  : Entity.make(result, { status: 200 })
316
310
 
317
311
  if (entity.status === 404 && entity.body === undefined) {
318
- return Response.json({ status: 406, message: "not acceptable" }, {
319
- status: 406,
320
- })
312
+ return respondError({ status: 406, message: "not acceptable" })
321
313
  }
322
314
 
323
315
  return yield* toResponse(entity, selectedFormat, runtime)
@@ -380,9 +372,8 @@ export const toWebHandlerRuntime = <R>(
380
372
  Effect.gen(function*() {
381
373
  yield* Effect.logError(cause)
382
374
  const status = getStatusFromCause(cause)
383
- return Response.json({ status, message: Cause.pretty(cause) }, {
384
- status,
385
- })
375
+ const message = Cause.pretty(cause, { renderErrorCause: true })
376
+ return respondError({ status, message })
386
377
  })
387
378
  ),
388
379
  ),
@@ -400,18 +391,11 @@ export const toWebHandlerRuntime = <R>(
400
391
  if (exit._tag === "Success") {
401
392
  resolve(exit.value)
402
393
  } else if (isClientAbort(exit.cause)) {
403
- resolve(
404
- Response.json({ status: 499, message: "client closed request" }, {
405
- status: 499,
406
- }),
407
- )
394
+ resolve(respondError({ status: 499, message: "client closed request" }))
408
395
  } else {
409
396
  const status = getStatusFromCause(exit.cause)
410
- resolve(
411
- Response.json({ status, message: Cause.pretty(exit.cause) }, {
412
- status,
413
- }),
414
- )
397
+ const message = Cause.pretty(exit.cause, { renderErrorCause: true })
398
+ resolve(respondError({ status, message }))
415
399
  }
416
400
  })
417
401
  })
@@ -438,6 +422,6 @@ export function* walkHandles(
438
422
 
439
423
  const toHandler = toWebHandlerRuntime(runtime)
440
424
  for (const [path, routes] of pathGroups) {
441
- yield [path, toHandler(routes as Iterable<UnboundedRouteWithMethod>)]
425
+ yield [path, toHandler(routes)]
442
426
  }
443
427
  }
package/src/RouteMount.ts CHANGED
@@ -142,7 +142,7 @@ export type MountedRoute = Route.Route.Route<
142
142
  {
143
143
  method: RouteMount.Method
144
144
  path: PathPattern.PathPattern
145
- format?: string
145
+ format?: RouteBody.Format
146
146
  },
147
147
  {},
148
148
  any,
package/src/Start.ts CHANGED
@@ -1,14 +1,8 @@
1
- import * as FetchHttpClient from "@effect/platform/FetchHttpClient"
2
- import * as FileSystem from "@effect/platform/FileSystem"
3
- import * as HttpClient from "@effect/platform/HttpClient"
4
- import * as HttpRouter from "@effect/platform/HttpRouter"
5
- import * as HttpServer from "@effect/platform/HttpServer"
6
1
  import * as Effect from "effect/Effect"
7
2
  import * as Function from "effect/Function"
8
3
  import * as Layer from "effect/Layer"
9
4
  import * as BunHttpServer from "./bun/BunHttpServer.ts"
10
5
  import * as BunRuntime from "./bun/BunRuntime.ts"
11
- import * as NodeFileSystem from "./node/FileSystem.ts"
12
6
  import * as StartApp from "./StartApp.ts"
13
7
 
14
8
  export function layer<
@@ -29,11 +23,7 @@ export function serve<ROut, E>(
29
23
  default: Layer.Layer<
30
24
  ROut,
31
25
  E,
32
- | HttpServer.HttpServer
33
- | HttpClient.HttpClient
34
- | HttpRouter.Default
35
- | FileSystem.FileSystem
36
- | BunHttpServer.BunHttpServer
26
+ BunHttpServer.BunHttpServer
37
27
  >
38
28
  }>,
39
29
  ) {
@@ -46,13 +36,9 @@ export function serve<ROut, E>(
46
36
 
47
37
  return Function.pipe(
48
38
  BunHttpServer.layerAuto(),
49
- HttpServer.withLogAddress,
50
39
  Layer.provide(appLayer),
51
40
  Layer.provide([
52
- FetchHttpClient.layer,
53
- HttpRouter.Default.Live,
54
41
  BunHttpServer.layer(),
55
- NodeFileSystem.layer,
56
42
  StartApp.layer(),
57
43
  ]),
58
44
  Layer.launch,
package/src/Unique.ts ADDED
@@ -0,0 +1,232 @@
1
+ export const ALPHABET_BASE32_CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
2
+ export const ALPHABET_BASE32_RFC4648 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
3
+ export const ALPHABET_BASE64_URL =
4
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
5
+ export const ALPHABET_HEX = "0123456789abcdef"
6
+
7
+ /**
8
+ * Generate a random string for ids, session tokens, and API keys.
9
+ * It uses human-friendly crockford base32 encoding (5 bit of entropy per char)
10
+ *
11
+ * Minimal recommended length:
12
+ * - public ids: 16 chars (~80 bits)
13
+ * - API keys: 32 chars (~160 bits)
14
+ * - session tokens: 32-40 chars (~160-200 bits)
15
+ */
16
+ export function token(length = 32): string {
17
+ if (length <= 0) return ""
18
+
19
+ const buf = new Uint8Array(length)
20
+ crypto.getRandomValues(buf)
21
+
22
+ let result = ""
23
+ for (let i = 0; i < buf.length; i++) {
24
+ result += ALPHABET_BASE32_CROCKFORD[buf[i] & 31]
25
+ }
26
+
27
+ return result
28
+ }
29
+
30
+ export function bytes(length: number): Uint8Array {
31
+ const buf = new Uint8Array(length)
32
+ crypto.getRandomValues(buf)
33
+ return buf
34
+ }
35
+
36
+ export const UUID_NIL = "00000000-0000-0000-0000-000000000000"
37
+
38
+ export function uuid4(): string {
39
+ return formatUuid(uuid4bytes())
40
+ }
41
+
42
+ export function uuid7(time: number = Date.now()): string {
43
+ return formatUuid(uuid7Bytes(time))
44
+ }
45
+
46
+ function uuid4bytes(): Uint8Array {
47
+ const buf = bytes(16)
48
+ buf[6] = (buf[6] & 0x0f) | 0x40 // version 4
49
+ buf[8] = (buf[8] & 0x3f) | 0x80 // variant
50
+
51
+ return buf
52
+ }
53
+
54
+ /**
55
+ * Decode a 48-bit Unix timestamp (ms) from UUID7 or ULID.
56
+ *
57
+ * @example
58
+ * const bytes = Unique.uuid7Bytes()
59
+ * const timestamp = Unique.toTimestamp(bytes)
60
+ *
61
+ * @example
62
+ * const bytes = Unique.ulidBytes()
63
+ * const timestamp = Unique.toTimestamp(bytes)
64
+ */
65
+ export function toTimestamp(bytes: Uint8Array): number {
66
+ if (bytes.length < 6) return 0
67
+
68
+ return (
69
+ bytes[0] * 0x10000000000
70
+ + bytes[1] * 0x100000000
71
+ + bytes[2] * 0x1000000
72
+ + bytes[3] * 0x10000
73
+ + bytes[4] * 0x100
74
+ + bytes[5]
75
+ )
76
+ }
77
+
78
+ export function uuid7Bytes(time: number = Date.now()): Uint8Array {
79
+ const buf = new Uint8Array(16)
80
+ const timestamp = BigInt(toSafeTime(time))
81
+
82
+ // 48-bit timestamp (6 bytes)
83
+ buf[0] = Number((timestamp >> 40n) & 0xffn)
84
+ buf[1] = Number((timestamp >> 32n) & 0xffn)
85
+ buf[2] = Number((timestamp >> 24n) & 0xffn)
86
+ buf[3] = Number((timestamp >> 16n) & 0xffn)
87
+ buf[4] = Number((timestamp >> 8n) & 0xffn)
88
+ buf[5] = Number(timestamp & 0xffn)
89
+
90
+ // 12-bit random A (1.5 bytes)
91
+ crypto.getRandomValues(buf.subarray(6, 8))
92
+ buf[6] = (buf[6] & 0x0f) | 0x70 // version 7
93
+
94
+ // 2-bit variant + 62-bit random B (8 bytes)
95
+ crypto.getRandomValues(buf.subarray(8, 16))
96
+ buf[8] = (buf[8] & 0x3f) | 0x80 // variant
97
+
98
+ return buf
99
+ }
100
+
101
+ /**
102
+ * Convert UUID bytes to canonical (RFC9562) representation.
103
+ *
104
+ * @example
105
+ * Unique.formatUuid(new Uint8Array(16))
106
+ */
107
+ export function formatUuid(bytes: Uint8Array): string {
108
+ if (bytes.length === 0) return ""
109
+
110
+ let result = ""
111
+
112
+ for (let i = 0; i < bytes.length; i++) {
113
+ const byte = bytes[i]
114
+ result += ALPHABET_HEX[(byte >> 4) & 0x0f]
115
+ result += ALPHABET_HEX[byte & 0x0f]
116
+
117
+ if (i === 3 || i === 5 || i === 7 || i === 9) result += "-"
118
+ }
119
+
120
+ return result
121
+ }
122
+
123
+ export function ulid(time: number = Date.now()): string {
124
+ const bytes = ulidBytes(time)
125
+ return formatUlid(bytes)
126
+ }
127
+
128
+ export function ulidBytes(time: number = Date.now()): Uint8Array {
129
+ const buf = new Uint8Array(16)
130
+ const timestamp = BigInt(toSafeTime(time))
131
+
132
+ buf[0] = Number((timestamp >> 40n) & 0xffn)
133
+ buf[1] = Number((timestamp >> 32n) & 0xffn)
134
+ buf[2] = Number((timestamp >> 24n) & 0xffn)
135
+ buf[3] = Number((timestamp >> 16n) & 0xffn)
136
+ buf[4] = Number((timestamp >> 8n) & 0xffn)
137
+ buf[5] = Number(timestamp & 0xffn)
138
+
139
+ crypto.getRandomValues(buf.subarray(6, 16))
140
+
141
+ return buf
142
+ }
143
+
144
+ function formatUlid(bytes: Uint8Array): string {
145
+ if (bytes.length !== 16) return ""
146
+
147
+ const timestamp = toTimestamp(bytes)
148
+ const timePart = encodeUlidTime(timestamp)
149
+ const randomPart = toBase32(bytes.subarray(6, 16))
150
+
151
+ return `${timePart}${randomPart}`
152
+ }
153
+
154
+ function encodeUlidTime(time: number): string {
155
+ let value = BigInt(time)
156
+ const result = new Array<string>(10)
157
+
158
+ for (let i = 9; i >= 0; i--) {
159
+ result[i] = ALPHABET_BASE32_CROCKFORD[Number(value & 31n)]
160
+ value >>= 5n
161
+ }
162
+
163
+ return result.join("")
164
+ }
165
+
166
+ function toSafeTime(time: number): number {
167
+ if (!Number.isFinite(time)) return 0
168
+
169
+ return Math.max(0, Math.trunc(time))
170
+ }
171
+
172
+ /**
173
+ * Generate a nanoid-style random string.
174
+ *
175
+ * FUN_FACT: Original nanoid implementation uses base64url alphabet
176
+ * with non-standard custom order where charater form common words found
177
+ * in source code (like use, random, strict) to make gzip/brotli more efficient.
178
+ * It's qt lil opt from the times where web developers
179
+ * were competing to have the smallest possible bundle size.
180
+ */
181
+ export function nanoid(
182
+ size = 21,
183
+ alphabet = ALPHABET_BASE64_URL,
184
+ ): string {
185
+ if (size <= 0 || alphabet.length === 0) return ""
186
+
187
+ const length = alphabet.length
188
+ const mask = (2 << Math.floor(Math.log2(length - 1))) - 1
189
+ const step = Math.ceil((1.6 * mask * size) / length)
190
+
191
+ let id = ""
192
+ while (id.length < size) {
193
+ const bytes = new Uint8Array(step)
194
+ crypto.getRandomValues(bytes)
195
+
196
+ for (let i = 0; i < step && id.length < size; i++) {
197
+ const index = bytes[i] & mask
198
+ if (index < length) id += alphabet[index]
199
+ }
200
+ }
201
+ return id
202
+ }
203
+
204
+ function toBase32(
205
+ bytes: Uint8Array,
206
+ alphabet = ALPHABET_BASE32_CROCKFORD,
207
+ ): string {
208
+ if (bytes.length === 0) return ""
209
+
210
+ let result = ""
211
+ let buffer = 0
212
+ let bits = 0
213
+
214
+ for (const byte of bytes) {
215
+ buffer = (buffer << 8) | byte
216
+ bits += 8
217
+
218
+ while (bits >= 5) {
219
+ bits -= 5
220
+ const index = (buffer >> bits) & 31
221
+ result += alphabet[index]
222
+ buffer &= (1 << bits) - 1
223
+ }
224
+ }
225
+
226
+ if (bits > 0) {
227
+ const index = (buffer << (5 - bits)) & 31
228
+ result += alphabet[index]
229
+ }
230
+
231
+ return result
232
+ }
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import * as HttpApp from "@effect/platform/HttpApp"
3
2
  import * as HttpServer from "@effect/platform/HttpServer"
4
3
  import * as HttpServerError from "@effect/platform/HttpServerError"
@@ -14,12 +13,13 @@ import * as FiberSet from "effect/FiberSet"
14
13
  import * as Layer from "effect/Layer"
15
14
  import * as Option from "effect/Option"
16
15
  import type * as Scope from "effect/Scope"
17
- import * as FileRouter from "../FileRouter.ts"
18
16
  import * as PathPattern from "../PathPattern.ts"
19
- import * as Random from "../Random.ts"
17
+ import * as PlataformRuntime from "../PlatformRuntime.ts"
20
18
  import * as Route from "../Route.ts"
21
19
  import * as RouteHttp from "../RouteHttp.ts"
20
+ import * as RouteMount from "../RouteMount.ts"
22
21
  import * as RouteTree from "../RouteTree.ts"
22
+ import * as Unique from "../Unique.ts"
23
23
  import EmptyHTML from "./_empty.html"
24
24
  import {
25
25
  makeResponse,
@@ -69,11 +69,8 @@ export const make = (
69
69
  Effect.gen(function*() {
70
70
  const port = yield* Config.number("PORT").pipe(
71
71
  Effect.catchTag("ConfigError", () => {
72
- if (
73
- typeof process !== "undefined"
74
- && !process.stdout.isTTY
75
- && process.env.CLAUDECODE
76
- ) {
72
+ if (PlataformRuntime.isAgentHarness()) {
73
+ // use random port
77
74
  return Effect.succeed(0)
78
75
  }
79
76
 
@@ -95,7 +92,7 @@ export const make = (
95
92
  // Bun HMR doesn't work on successive calls to `server.reload` if there are no routes
96
93
  // on server start. We workaround that by passing a dummy HTMLBundle [2025-11-26]
97
94
  // see: https://github.com/oven-sh/bun/issues/23564
98
- currentRoutes[`/.BunEmptyHtml-${Random.token(6)}`] = EmptyHTML
95
+ currentRoutes[`/.BunEmptyHtml-${Unique.token(10)}`] = EmptyHTML
99
96
 
100
97
  const websocket: Bun.WebSocketHandler<WebSocketContext> = {
101
98
  open(ws) {
@@ -6,7 +6,7 @@ import * as Option from "effect/Option"
6
6
  import * as Entity from "../Entity.ts"
7
7
  import * as Hyper from "../hyper/Hyper.ts"
8
8
  import * as HyperHtml from "../hyper/HyperHtml.ts"
9
- import * as Random from "../Random.ts"
9
+ import * as Unique from "../Unique.ts"
10
10
  import * as Route from "../Route.ts"
11
11
  import * as RouterPattern from "../RouterPattern.ts"
12
12
  import * as BunHttpServer from "./BunHttpServer.ts"
@@ -25,7 +25,7 @@ export type BunDescriptors = {
25
25
  }
26
26
 
27
27
  export function descriptors(
28
- route: Route.Route.Route,
28
+ route: Route.Route.Route<any, any, any, any, any>,
29
29
  ): BunDescriptors | undefined {
30
30
  const descriptor = Route.descriptor(route) as Partial<BunDescriptors>
31
31
  if (
@@ -40,7 +40,7 @@ export function descriptors(
40
40
  export function htmlBundle(
41
41
  load: () => Promise<Bun.HTMLBundle | { default: Bun.HTMLBundle }>,
42
42
  ) {
43
- const bunPrefix = `/.BunRoute-${Random.token(6)}`
43
+ const bunPrefix = `/.BunRoute-${Unique.token(10)}`
44
44
  const bunLoad = () => load().then(mod => "default" in mod ? mod.default : mod)
45
45
  const descriptors = { bunPrefix, bunLoad, format: "html" as const }
46
46
 
package/src/index.ts CHANGED
@@ -2,5 +2,6 @@ export * as Bundle from "./bundler/Bundle.ts"
2
2
  export * as Development from "./Development.ts"
3
3
  export * as Entity from "./Entity.ts"
4
4
  export * as FileRouter from "./FileRouter.ts"
5
+ export * as Unique from "./Unique.ts"
5
6
  export * as Route from "./Route.ts"
6
7
  export * as Start from "./Start.ts"