effect-start 0.21.0 → 0.22.1

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 (94) 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 +34 -36
  6. package/src/Commander.ts +73 -130
  7. package/src/ContentNegotiation.ts +64 -95
  8. package/src/Cookies.ts +36 -57
  9. package/src/Development.ts +47 -62
  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 +37 -62
  14. package/src/FileRouterCodegen.ts +63 -55
  15. package/src/FileSystem.ts +46 -59
  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 +5 -2
  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 +55 -91
  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 +35 -55
  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 +79 -142
  77. package/src/hyper/Hyper.ts +14 -33
  78. package/src/hyper/HyperHtml.ts +9 -10
  79. package/src/hyper/HyperNode.ts +2 -7
  80. package/src/hyper/HyperRoute.ts +2 -5
  81. package/src/hyper/jsx-runtime.ts +2 -10
  82. package/src/hyper/jsx.d.ts +171 -440
  83. package/src/lint/plugin.js +276 -0
  84. package/src/node/NodeFileSystem.ts +138 -186
  85. package/src/node/NodeUtils.ts +1 -3
  86. package/src/testing/TestLogger.ts +9 -22
  87. package/src/testing/utils.ts +30 -31
  88. package/src/x/cloudflare/CloudflareTunnel.ts +37 -54
  89. package/src/x/datastar/Datastar.ts +3 -10
  90. package/src/x/datastar/index.ts +1 -3
  91. package/src/x/datastar/jsx-datastar.d.ts +1 -4
  92. package/src/x/tailwind/TailwindPlugin.ts +119 -112
  93. package/src/x/tailwind/compile.ts +10 -33
  94. package/src/x/tailwind/plugin.ts +2 -2
@@ -7,9 +7,7 @@ import * as FilePathPattern from "./FilePathPattern.ts"
7
7
  import * as FileRouter from "./FileRouter.ts"
8
8
  import * as SchemaExtra from "./SchemaExtra.ts"
9
9
 
10
- export function validateRouteModule(
11
- module: unknown,
12
- ): module is FileRouter.RouteModule {
10
+ export function validateRouteModule(module: unknown): module is FileRouter.RouteModule {
13
11
  if (typeof module !== "object" || module === null) {
14
12
  return false
15
13
  }
@@ -25,10 +23,7 @@ export function validateRouteModule(
25
23
  export function generatePathParamsSchema(
26
24
  segments: ReadonlyArray<FilePathPattern.Segment>,
27
25
  ): Schema.Struct<any> | null {
28
- const fields: Record<
29
- PropertyKey,
30
- Schema.Schema.Any | Schema.PropertySignature.All
31
- > = {}
26
+ const fields: Record<PropertyKey, Schema.Schema.Any | Schema.PropertySignature.All> = {}
32
27
 
33
28
  for (const segment of segments) {
34
29
  if (segment._tag === "ParamSegment") {
@@ -53,17 +48,17 @@ export function validateRouteModules(
53
48
  path: string,
54
49
  routes: FileRouter.OrderedFileRoutes,
55
50
  ): Effect.Effect<void, FileRouter.FileRouterError, FileSystem.FileSystem> {
56
- return Effect.gen(function*() {
51
+ return Effect.gen(function* () {
57
52
  const fs = yield* FileSystem.FileSystem
58
- const routeHandles = routes.filter(h => h.handle === "route")
53
+ const routeHandles = routes.filter((h) => h.handle === "route")
59
54
 
60
55
  for (const handle of routeHandles) {
61
56
  const routeModulePath = NPath.resolve(path, handle.modulePath)
62
57
  const expectedSchema = generatePathParamsSchema(handle.segments)
63
58
 
64
- const fileExists = yield* fs.exists(routeModulePath).pipe(
65
- Effect.catchAll(() => Effect.succeed(false)),
66
- )
59
+ const fileExists = yield* fs
60
+ .exists(routeModulePath)
61
+ .pipe(Effect.catchAll(() => Effect.succeed(false)))
67
62
  if (!fileExists) {
68
63
  continue
69
64
  }
@@ -79,9 +74,7 @@ export function validateRouteModules(
79
74
  })
80
75
 
81
76
  if (!validateRouteModule(module)) {
82
- yield* Effect.logWarning(
83
- `Route module ${routeModulePath} should export default Route`,
84
- )
77
+ yield* Effect.logWarning(`Route module ${routeModulePath} should export default Route`)
85
78
  continue
86
79
  }
87
80
 
@@ -89,30 +82,27 @@ export function validateRouteModules(
89
82
  // extract user schema
90
83
  const userSchema = undefined
91
84
 
92
- if (
93
- expectedSchema
94
- && userSchema
95
- && !SchemaExtra.schemaEqual(userSchema, expectedSchema)
96
- ) {
85
+ if (expectedSchema && userSchema && !SchemaExtra.schemaEqual(userSchema, expectedSchema)) {
97
86
  const relativeFilePath = NPath.relative(process.cwd(), routeModulePath)
98
87
  yield* Effect.logError(
99
- `Route '${relativeFilePath}' has incorrect PathParams schema, expected schemaPathParams(${
100
- SchemaExtra.formatSchemaCode(expectedSchema)
101
- })`,
88
+ `Route '${relativeFilePath}' has incorrect PathParams schema, expected schemaPathParams(${SchemaExtra.formatSchemaCode(
89
+ expectedSchema,
90
+ )})`,
102
91
  )
103
92
  }
104
93
  }
105
94
  })
106
95
  }
107
96
 
108
- export function generateCode(
109
- fileRoutes: FileRouter.OrderedFileRoutes,
110
- ): string | null {
97
+ export function generateCode(fileRoutes: FileRouter.OrderedFileRoutes): string | null {
111
98
  // Group routes by path to find layers
112
- const routesByPath = new Map<string, {
113
- route?: FileRouter.FileRoute
114
- layers: FileRouter.FileRoute[]
115
- }>()
99
+ const routesByPath = new Map<
100
+ string,
101
+ {
102
+ route?: FileRouter.FileRoute
103
+ layers: Array<FileRouter.FileRoute>
104
+ }
105
+ >()
116
106
 
117
107
  for (const fileRoute of fileRoutes) {
118
108
  const existing = routesByPath.get(fileRoute.routePath) || { layers: [] }
@@ -125,31 +115,26 @@ export function generateCode(
125
115
  }
126
116
 
127
117
  // Helper to check if layer's path is an ancestor of route's path
128
- const layerMatchesRoute = (
129
- layer: FileRouter.FileRoute,
130
- route: FileRouter.FileRoute,
131
- ): boolean => {
118
+ const layerMatchesRoute = (layer: FileRouter.FileRoute, route: FileRouter.FileRoute): boolean => {
132
119
  const layerDir = layer.modulePath.replace(/\/(layer)\.(tsx?|jsx?)$/, "")
133
120
  if (layerDir === "/") return true
134
121
  return route.modulePath.startsWith(layerDir + "/")
135
122
  }
136
123
 
137
124
  // Build entries for each route path
138
- const entries: Array<{ path: string; loaders: string[] }> = []
125
+ const entries: Array<{ path: string; loaders: Array<string> }> = []
139
126
 
140
127
  for (const [path, { route }] of routesByPath) {
141
128
  if (!route) continue
142
129
 
143
130
  // Collect all parent layers that match the route's groups
144
- const allLayers: FileRouter.FileRoute[] = []
131
+ const allLayers: Array<FileRouter.FileRoute> = []
145
132
  let currentPath = path
146
133
 
147
134
  while (true) {
148
135
  const pathData = routesByPath.get(currentPath)
149
136
  if (pathData?.layers) {
150
- const matchingLayers = pathData.layers.filter(layer =>
151
- layerMatchesRoute(layer, route)
152
- )
137
+ const matchingLayers = pathData.layers.filter((layer) => layerMatchesRoute(layer, route))
153
138
  allLayers.unshift(...matchingLayers)
154
139
  }
155
140
 
@@ -167,11 +152,9 @@ export function generateCode(
167
152
  const pathPattern = pathPatternResult.right
168
153
 
169
154
  // Order: route first, then layers from innermost to outermost
170
- const loaders: string[] = [
155
+ const loaders: Array<string> = [
171
156
  `() => import(".${route.modulePath}")`,
172
- ...allLayers.reverse().map(layer =>
173
- `() => import(".${layer.modulePath}")`
174
- ),
157
+ ...allLayers.reverse().map((layer) => `() => import(".${layer.modulePath}")`),
175
158
  ]
176
159
 
177
160
  entries.push({ path: pathPattern, loaders })
@@ -207,13 +190,18 @@ export function update(
207
190
  routesPath: string,
208
191
  treePath = "server.gen.ts",
209
192
  ): Effect.Effect<void, FileRouter.FileRouterError, FileSystem.FileSystem> {
210
- return Effect.gen(function*() {
193
+ return Effect.gen(function* () {
211
194
  treePath = NPath.resolve(routesPath, treePath)
212
195
 
213
196
  const fs = yield* FileSystem.FileSystem
214
197
  const files = yield* fs.readDirectory(routesPath, { recursive: true }).pipe(
215
- Effect.mapError((cause) =>
216
- new FileRouter.FileRouterError({ reason: "FileSystem", cause, path: routesPath })
198
+ Effect.mapError(
199
+ (cause) =>
200
+ new FileRouter.FileRouterError({
201
+ reason: "FileSystem",
202
+ cause,
203
+ path: routesPath,
204
+ }),
217
205
  ),
218
206
  )
219
207
  const fileRoutes = yield* FileRouter.getFileRoutes(files)
@@ -236,8 +224,13 @@ export function update(
236
224
  if (existingCode !== emptyCode) {
237
225
  yield* Effect.logDebug(`Clearing file routes tree: ${treePath}`)
238
226
  yield* fs.writeFileString(treePath, emptyCode).pipe(
239
- Effect.mapError((cause) =>
240
- new FileRouter.FileRouterError({ reason: "FileSystem", cause, path: treePath })
227
+ Effect.mapError(
228
+ (cause) =>
229
+ new FileRouter.FileRouterError({
230
+ reason: "FileSystem",
231
+ cause,
232
+ path: treePath,
233
+ }),
241
234
  ),
242
235
  )
243
236
  }
@@ -249,8 +242,13 @@ export function update(
249
242
  if (existingCode !== newCode) {
250
243
  yield* Effect.logDebug(`Updating file routes tree: ${treePath}`)
251
244
  yield* fs.writeFileString(treePath, newCode).pipe(
252
- Effect.mapError((cause) =>
253
- new FileRouter.FileRouterError({ reason: "FileSystem", cause, path: treePath })
245
+ Effect.mapError(
246
+ (cause) =>
247
+ new FileRouter.FileRouterError({
248
+ reason: "FileSystem",
249
+ cause,
250
+ path: treePath,
251
+ }),
254
252
  ),
255
253
  )
256
254
  } else {
@@ -263,13 +261,18 @@ export function dump(
263
261
  routesPath: string,
264
262
  treePath = "server.gen.ts",
265
263
  ): Effect.Effect<void, FileRouter.FileRouterError, FileSystem.FileSystem> {
266
- return Effect.gen(function*() {
264
+ return Effect.gen(function* () {
267
265
  treePath = NPath.resolve(routesPath, treePath)
268
266
 
269
267
  const fs = yield* FileSystem.FileSystem
270
268
  const files = yield* fs.readDirectory(routesPath, { recursive: true }).pipe(
271
- Effect.mapError((cause) =>
272
- new FileRouter.FileRouterError({ reason: "FileSystem", cause, path: routesPath })
269
+ Effect.mapError(
270
+ (cause) =>
271
+ new FileRouter.FileRouterError({
272
+ reason: "FileSystem",
273
+ cause,
274
+ path: routesPath,
275
+ }),
273
276
  ),
274
277
  )
275
278
  const fileRoutes = yield* FileRouter.getFileRoutes(files)
@@ -288,8 +291,13 @@ export function dump(
288
291
  yield* Effect.logDebug(`Generating file routes tree: ${treePath}`)
289
292
 
290
293
  yield* fs.writeFileString(treePath, code).pipe(
291
- Effect.mapError((cause) =>
292
- new FileRouter.FileRouterError({ reason: "FileSystem", cause, path: treePath })
294
+ Effect.mapError(
295
+ (cause) =>
296
+ new FileRouter.FileRouterError({
297
+ reason: "FileSystem",
298
+ cause,
299
+ path: treePath,
300
+ }),
293
301
  ),
294
302
  )
295
303
  })
package/src/FileSystem.ts CHANGED
@@ -28,18 +28,13 @@ export interface FileSystem {
28
28
  fromPath: string,
29
29
  toPath: string,
30
30
  ) => Effect.Effect<void, PlatformError.PlatformError>
31
- readonly chmod: (
32
- path: string,
33
- mode: number,
34
- ) => Effect.Effect<void, PlatformError.PlatformError>
31
+ readonly chmod: (path: string, mode: number) => Effect.Effect<void, PlatformError.PlatformError>
35
32
  readonly chown: (
36
33
  path: string,
37
34
  uid: number,
38
35
  gid: number,
39
36
  ) => Effect.Effect<void, PlatformError.PlatformError>
40
- readonly exists: (
41
- path: string,
42
- ) => Effect.Effect<boolean, PlatformError.PlatformError>
37
+ readonly exists: (path: string) => Effect.Effect<boolean, PlatformError.PlatformError>
43
38
  readonly link: (
44
39
  fromPath: string,
45
40
  toPath: string,
@@ -68,19 +63,13 @@ export interface FileSystem {
68
63
  path: string,
69
64
  options?: ReadDirectoryOptions,
70
65
  ) => Effect.Effect<Array<string>, PlatformError.PlatformError>
71
- readonly readFile: (
72
- path: string,
73
- ) => Effect.Effect<Uint8Array, PlatformError.PlatformError>
66
+ readonly readFile: (path: string) => Effect.Effect<Uint8Array, PlatformError.PlatformError>
74
67
  readonly readFileString: (
75
68
  path: string,
76
69
  encoding?: string,
77
70
  ) => Effect.Effect<string, PlatformError.PlatformError>
78
- readonly readLink: (
79
- path: string,
80
- ) => Effect.Effect<string, PlatformError.PlatformError>
81
- readonly realPath: (
82
- path: string,
83
- ) => Effect.Effect<string, PlatformError.PlatformError>
71
+ readonly readLink: (path: string) => Effect.Effect<string, PlatformError.PlatformError>
72
+ readonly realPath: (path: string) => Effect.Effect<string, PlatformError.PlatformError>
84
73
  readonly remove: (
85
74
  path: string,
86
75
  options?: RemoveOptions,
@@ -93,9 +82,7 @@ export interface FileSystem {
93
82
  path: string,
94
83
  options?: SinkOptions,
95
84
  ) => Sink.Sink<void, Uint8Array, never, PlatformError.PlatformError>
96
- readonly stat: (
97
- path: string,
98
- ) => Effect.Effect<File.Info, PlatformError.PlatformError>
85
+ readonly stat: (path: string) => Effect.Effect<File.Info, PlatformError.PlatformError>
99
86
  readonly stream: (
100
87
  path: string,
101
88
  options?: StreamOptions,
@@ -138,19 +125,9 @@ export type Size = Brand.Branded<bigint, "Size">
138
125
  export type SizeInput = bigint | number | Size
139
126
 
140
127
  export const Size = (bytes: SizeInput): Size =>
141
- typeof bytes === "bigint" ? bytes as Size : BigInt(bytes) as Size
142
-
143
- export type OpenFlag =
144
- | "r"
145
- | "r+"
146
- | "w"
147
- | "wx"
148
- | "w+"
149
- | "wx+"
150
- | "a"
151
- | "ax"
152
- | "a+"
153
- | "ax+"
128
+ typeof bytes === "bigint" ? (bytes as Size) : (BigInt(bytes) as Size)
129
+
130
+ export type OpenFlag = "r" | "r+" | "w" | "wx" | "w+" | "wx+" | "a" | "ax" | "a+" | "ax+"
154
131
 
155
132
  export type SeekMode = "start" | "current"
156
133
 
@@ -229,7 +206,9 @@ export interface File {
229
206
  readonly seek: (offset: SizeInput, from: SeekMode) => Effect.Effect<void>
230
207
  readonly sync: Effect.Effect<void, PlatformError.PlatformError>
231
208
  readonly read: (buffer: Uint8Array) => Effect.Effect<Size, PlatformError.PlatformError>
232
- readonly readAlloc: (size: SizeInput) => Effect.Effect<Option.Option<Uint8Array>, PlatformError.PlatformError>
209
+ readonly readAlloc: (
210
+ size: SizeInput,
211
+ ) => Effect.Effect<Option.Option<Uint8Array>, PlatformError.PlatformError>
233
212
  readonly truncate: (length?: SizeInput) => Effect.Effect<void, PlatformError.PlatformError>
234
213
  readonly write: (buffer: Uint8Array) => Effect.Effect<Size, PlatformError.PlatformError>
235
214
  readonly writeAll: (buffer: Uint8Array) => Effect.Effect<void, PlatformError.PlatformError>
@@ -287,17 +266,14 @@ export declare namespace WatchEvent {
287
266
  }
288
267
  }
289
268
 
290
- export const WatchEventCreate: Data.Case.Constructor<WatchEvent.Create, "_tag"> = Data.tagged<WatchEvent.Create>(
291
- "Create",
292
- )
269
+ export const WatchEventCreate: Data.Case.Constructor<WatchEvent.Create, "_tag"> =
270
+ Data.tagged<WatchEvent.Create>("Create")
293
271
 
294
- export const WatchEventUpdate: Data.Case.Constructor<WatchEvent.Update, "_tag"> = Data.tagged<WatchEvent.Update>(
295
- "Update",
296
- )
272
+ export const WatchEventUpdate: Data.Case.Constructor<WatchEvent.Update, "_tag"> =
273
+ Data.tagged<WatchEvent.Update>("Update")
297
274
 
298
- export const WatchEventRemove: Data.Case.Constructor<WatchEvent.Remove, "_tag"> = Data.tagged<WatchEvent.Remove>(
299
- "Remove",
300
- )
275
+ export const WatchEventRemove: Data.Case.Constructor<WatchEvent.Remove, "_tag"> =
276
+ Data.tagged<WatchEvent.Remove>("Remove")
301
277
 
302
278
  export class WatchBackend extends Context.Tag("@effect/platform/FileSystem/WatchBackend")<
303
279
  WatchBackend,
@@ -319,7 +295,9 @@ export const make = (
319
295
  Function.pipe(
320
296
  impl.access(path),
321
297
  Effect.as(true),
322
- Effect.catchTag("SystemError", (e) => e.reason === "NotFound" ? Effect.succeed(false) : Effect.fail(e)),
298
+ Effect.catchTag("SystemError", (e) =>
299
+ e.reason === "NotFound" ? Effect.succeed(false) : Effect.fail(e),
300
+ ),
323
301
  ),
324
302
  readFileString: (path, encoding) =>
325
303
  Effect.tryMap(impl.readFile(path), {
@@ -364,40 +342,49 @@ export const make = (
364
342
  })
365
343
  }
366
344
 
367
- const fileStream = (file: File, {
368
- bufferSize = 16,
369
- bytesToRead: bytesToRead_,
370
- chunkSize: chunkSize_ = Size(64 * 1024),
371
- }: StreamOptions = {}) => {
345
+ const fileStream = (
346
+ file: File,
347
+ {
348
+ bufferSize = 16,
349
+ bytesToRead: bytesToRead_,
350
+ chunkSize: chunkSize_ = Size(64 * 1024),
351
+ }: StreamOptions = {},
352
+ ) => {
372
353
  const bytesToRead = bytesToRead_ !== undefined ? Size(bytesToRead_) : undefined
373
354
  const chunkSize = Size(chunkSize_)
374
355
 
375
356
  function loop(
376
357
  totalBytesRead: bigint,
377
- ): Channel.Channel<Chunk.Chunk<Uint8Array>, unknown, PlatformError.PlatformError, unknown, void, unknown> {
358
+ ): Channel.Channel<
359
+ Chunk.Chunk<Uint8Array>,
360
+ unknown,
361
+ PlatformError.PlatformError,
362
+ unknown,
363
+ void,
364
+ unknown
365
+ > {
378
366
  if (bytesToRead !== undefined && bytesToRead <= totalBytesRead) {
379
367
  return Channel.void
380
368
  }
381
369
 
382
- const toRead = bytesToRead !== undefined && (bytesToRead - totalBytesRead) < chunkSize
383
- ? bytesToRead - totalBytesRead
384
- : chunkSize
370
+ const toRead =
371
+ bytesToRead !== undefined && bytesToRead - totalBytesRead < chunkSize
372
+ ? bytesToRead - totalBytesRead
373
+ : chunkSize
385
374
 
386
375
  return Channel.flatMap(
387
376
  file.readAlloc(toRead),
388
377
  Option.match({
389
378
  onNone: () => Channel.void,
390
379
  onSome: (buf) =>
391
- Channel.flatMap(
392
- Channel.write(Chunk.of(buf)),
393
- () => loop(totalBytesRead + BigInt(buf.length)),
380
+ Channel.flatMap(Channel.write(Chunk.of(buf)), () =>
381
+ loop(totalBytesRead + BigInt(buf.length)),
394
382
  ),
395
383
  }),
396
384
  )
397
385
  }
398
386
 
399
- return Stream.bufferChunks(
400
- Stream.fromChannel(loop(BigInt(0))),
401
- { capacity: bufferSize },
402
- )
387
+ return Stream.bufferChunks(Stream.fromChannel(loop(BigInt(0))), {
388
+ capacity: bufferSize,
389
+ })
403
390
  }
package/src/Http.ts CHANGED
@@ -1,38 +1,21 @@
1
1
  import * as Values from "./Values.ts"
2
2
 
3
- export type Method =
4
- | "GET"
5
- | "POST"
6
- | "PUT"
7
- | "DELETE"
8
- | "PATCH"
9
- | "HEAD"
10
- | "OPTIONS"
11
-
12
- type Respondable =
13
- | Response
14
- | Promise<Response>
3
+ export type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS"
4
+
5
+ type Respondable = Response | Promise<Response>
15
6
 
16
7
  export type WebHandler = (request: Request) => Respondable
17
8
 
18
- export type WebMiddleware = (
19
- request: Request,
20
- next: WebHandler,
21
- ) => Respondable
9
+ export type WebMiddleware = (request: Request, next: WebHandler) => Respondable
22
10
 
23
11
  export function fetch(
24
12
  handler: WebHandler,
25
- init:
26
- & Omit<RequestInit, "body">
27
- & (
28
- | { url: string }
29
- | { path: `/${string}` }
30
- )
31
- & { body?: RequestInit["body"] | Record<string, unknown> },
13
+ init: Omit<RequestInit, "body"> &
14
+ ({ url: string } | { path: `/${string}` }) & {
15
+ body?: RequestInit["body"] | Record<string, unknown>
16
+ },
32
17
  ): Promise<Response> {
33
- const url = "path" in init
34
- ? `http://localhost${init.path}`
35
- : init.url
18
+ const url = "path" in init ? `http://localhost${init.path}` : init.url
36
19
 
37
20
  const isPlain = Values.isPlainObject(init.body)
38
21
 
@@ -52,24 +35,15 @@ export function fetch(
52
35
  }
53
36
 
54
37
  export function createAbortableRequest(
55
- init:
56
- & Omit<RequestInit, "signal">
57
- & (
58
- | { url: string }
59
- | { path: `/${string}` }
60
- ),
38
+ init: Omit<RequestInit, "signal"> & ({ url: string } | { path: `/${string}` }),
61
39
  ): { request: Request; abort: () => void } {
62
- const url = "path" in init
63
- ? `http://localhost${init.path}`
64
- : init.url
40
+ const url = "path" in init ? `http://localhost${init.path}` : init.url
65
41
  const controller = new AbortController()
66
42
  const request = new Request(url, { ...init, signal: controller.signal })
67
43
  return { request, abort: () => controller.abort() }
68
44
  }
69
45
 
70
- export function mapHeaders(
71
- headers: Headers,
72
- ): Record<string, string | undefined> {
46
+ export function mapHeaders(headers: Headers): Record<string, string | undefined> {
73
47
  const result: Record<string, string | undefined> = {}
74
48
  headers.forEach((value, key) => {
75
49
  result[key.toLowerCase()] = value
@@ -77,9 +51,7 @@ export function mapHeaders(
77
51
  return result
78
52
  }
79
53
 
80
- export function parseCookies(
81
- cookieHeader: string | null,
82
- ): Record<string, string | undefined> {
54
+ export function parseCookies(cookieHeader: string | null): Record<string, string | undefined> {
83
55
  if (!cookieHeader) return {}
84
56
  const result: Record<string, string | undefined> = {}
85
57
  for (const part of cookieHeader.split(";")) {
@@ -130,23 +102,18 @@ export type MultipartPart = FilePart | FieldPart
130
102
 
131
103
  export async function parseFormData(
132
104
  request: Request,
133
- ): Promise<
134
- Record<string, ReadonlyArray<FilePart> | ReadonlyArray<string> | string>
135
- > {
105
+ ): Promise<Record<string, ReadonlyArray<FilePart> | ReadonlyArray<string> | string>> {
136
106
  const formData = await request.formData()
137
- const result: Record<
138
- string,
139
- ReadonlyArray<FilePart> | ReadonlyArray<string> | string
140
- > = {}
107
+ const result: Record<string, ReadonlyArray<FilePart> | ReadonlyArray<string> | string> = {}
141
108
 
142
109
  for (const key of new Set(formData.keys())) {
143
110
  const values = formData.getAll(key)
144
111
  const first = values[0]
145
112
 
146
113
  if (typeof first === "string") {
147
- result[key] = values.length === 1 ? first : (values as string[])
114
+ result[key] = values.length === 1 ? first : (values as Array<string>)
148
115
  } else {
149
- const files: FilePart[] = []
116
+ const files: Array<FilePart> = []
150
117
  for (const value of values) {
151
118
  if (typeof value !== "string") {
152
119
  const content = new Uint8Array(await value.arrayBuffer())
@@ -2,24 +2,29 @@ export type PathPattern = `/${string}`
2
2
 
3
3
  export type Segments<Path extends string> = Path extends `/${infer Rest}`
4
4
  ? Segments<Rest>
5
- : Path extends `${infer Head}/${infer Tail}` ? [Head, ...Segments<Tail>]
6
- : Path extends "" ? []
7
- : [Path]
8
-
9
- export type Params<T extends string> = string extends T ? Record<string, string>
5
+ : Path extends `${infer Head}/${infer Tail}`
6
+ ? [Head, ...Segments<Tail>]
7
+ : Path extends ""
8
+ ? []
9
+ : [Path]
10
+
11
+ export type Params<T extends string> = string extends T
12
+ ? Record<string, string>
10
13
  : T extends `${infer _Start}:${infer Param}?/${infer Rest}`
11
14
  ? { [K in Param]?: string } & Params<`/${Rest}`>
12
- : T extends `${infer _Start}:${infer Param}/${infer Rest}`
13
- ? { [K in Param]: string } & Params<`/${Rest}`>
14
- : T extends `${infer _Start}:${infer Param}+` ? { [K in Param]: string }
15
- : T extends `${infer _Start}:${infer Param}*` ? { [K in Param]?: string }
16
- : T extends `${infer _Start}:${infer Param}?` ? { [K in Param]?: string }
17
- : T extends `${infer _Start}:${infer Param}` ? { [K in Param]: string }
18
- : {}
19
-
20
- export type ValidateResult =
21
- | { ok: true; segments: string[] }
22
- | { ok: false; error: string }
15
+ : T extends `${infer _Start}:${infer Param}/${infer Rest}`
16
+ ? { [K in Param]: string } & Params<`/${Rest}`>
17
+ : T extends `${infer _Start}:${infer Param}+`
18
+ ? { [K in Param]: string }
19
+ : T extends `${infer _Start}:${infer Param}*`
20
+ ? { [K in Param]?: string }
21
+ : T extends `${infer _Start}:${infer Param}?`
22
+ ? { [K in Param]?: string }
23
+ : T extends `${infer _Start}:${infer Param}`
24
+ ? { [K in Param]: string }
25
+ : {}
26
+
27
+ export type ValidateResult = { ok: true; segments: Array<string> } | { ok: false; error: string }
23
28
 
24
29
  function isValidSegment(segment: string): boolean {
25
30
  if (segment.startsWith(":")) {
@@ -46,10 +51,7 @@ export function validate(path: string): ValidateResult {
46
51
  return { ok: true, segments }
47
52
  }
48
53
 
49
- export function match(
50
- pattern: string,
51
- path: string,
52
- ): Record<string, string> | null {
54
+ export function match(pattern: string, path: string): Record<string, string> | null {
53
55
  const patternSegments = pattern.split("/").filter(Boolean)
54
56
  const pathSegments = path.split("/").filter(Boolean)
55
57
  const params: Record<string, string> = {}
@@ -153,11 +155,9 @@ function getParamName(seg: string): string {
153
155
  * - `:param+` → `/*param`
154
156
  * - `:param*` → `/`, `/*param`
155
157
  */
156
- export function toExpress(path: string): string[] {
158
+ export function toExpress(path: string): Array<string> {
157
159
  const segments = path.split("/").filter(Boolean)
158
- const optionalWildcardIndex = segments.findIndex(
159
- (s) => s.startsWith(":") && s.endsWith("*"),
160
- )
160
+ const optionalWildcardIndex = segments.findIndex((s) => s.startsWith(":") && s.endsWith("*"))
161
161
 
162
162
  if (optionalWildcardIndex !== -1) {
163
163
  const before = segments.slice(0, optionalWildcardIndex)
@@ -207,7 +207,7 @@ export function toExpress(path: string): string[] {
207
207
  * - `:param+` → `:param+`
208
208
  * - `:param*` → `:param*`
209
209
  */
210
- export function toURLPattern(path: string): string[] {
210
+ export function toURLPattern(path: string): Array<string> {
211
211
  const segments = path.split("/").filter(Boolean)
212
212
  const joined = segments
213
213
  .map((seg) => {
@@ -230,11 +230,9 @@ export function toURLPattern(path: string): string[] {
230
230
  * - `:param+` → `*` (splat, required)
231
231
  * - `:param*` → `/`, `/*` (splat, optional - two routes)
232
232
  */
233
- export function toReactRouter(path: string): string[] {
233
+ export function toReactRouter(path: string): Array<string> {
234
234
  const segments = path.split("/").filter(Boolean)
235
- const optionalWildcardIndex = segments.findIndex(
236
- (s) => s.startsWith(":") && s.endsWith("*"),
237
- )
235
+ const optionalWildcardIndex = segments.findIndex((s) => s.startsWith(":") && s.endsWith("*"))
238
236
 
239
237
  if (optionalWildcardIndex !== -1) {
240
238
  const before = segments.slice(0, optionalWildcardIndex)
@@ -361,11 +359,9 @@ export function toTanStack(path: string): string {
361
359
  * - `:param+` → `*` (unnamed, required)
362
360
  * - `:param*` → `/`, `/*` (unnamed, optional - two routes)
363
361
  */
364
- export function toHono(path: string): string[] {
362
+ export function toHono(path: string): Array<string> {
365
363
  const segments = path.split("/").filter(Boolean)
366
- const optionalWildcardIndex = segments.findIndex(
367
- (s) => s.startsWith(":") && s.endsWith("*"),
368
- )
364
+ const optionalWildcardIndex = segments.findIndex((s) => s.startsWith(":") && s.endsWith("*"))
369
365
 
370
366
  if (optionalWildcardIndex !== -1) {
371
367
  const before = segments.slice(0, optionalWildcardIndex)
@@ -413,7 +409,7 @@ export function toHono(path: string): string[] {
413
409
  * - `:param+` → `*` (unnamed)
414
410
  * - `:param*` → `/`, `/*` (two routes)
415
411
  */
416
- export function toEffect(path: string): string[] {
412
+ export function toEffect(path: string): Array<string> {
417
413
  return toHono(path)
418
414
  }
419
415
 
@@ -430,7 +426,7 @@ export function toEffect(path: string): string[] {
430
426
  * - `:param+` → `/*`
431
427
  * - `:param*` → `/`, `/*` (two routes)
432
428
  */
433
- export function toBun(path: string): PathPattern[] {
429
+ export function toBun(path: string): Array<PathPattern> {
434
430
  const segments = path.split("/").filter(Boolean)
435
431
 
436
432
  const optionalIndex = segments.findIndex(
@@ -472,14 +468,10 @@ export function toBun(path: string): PathPattern[] {
472
468
 
473
469
  const optionalModifier = getModifier(optional)
474
470
  const optionalName = getParamName(optional)
475
- const requiredOptional = optionalModifier === "*"
476
- ? `:${optionalName}+`
477
- : `:${optionalName}`
471
+ const requiredOptional = optionalModifier === "*" ? `:${optionalName}+` : `:${optionalName}`
478
472
 
479
473
  const withOptionalSegments = [...before, requiredOptional, ...after]
480
- const withOptionalPath: PathPattern = `/${
481
- withOptionalSegments.map(formatSegment).join("/")
482
- }`
474
+ const withOptionalPath: PathPattern = `/${withOptionalSegments.map(formatSegment).join("/")}`
483
475
 
484
476
  return [...toBun(basePath), ...toBun(withOptionalPath)]
485
477
  }