effect-start 0.19.0 → 0.20.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 (144) hide show
  1. package/README.md +3 -3
  2. package/dist/Development.d.ts +3 -3
  3. package/dist/Development.js +3 -2
  4. package/dist/Effectify.d.ts +212 -0
  5. package/dist/Effectify.js +19 -0
  6. package/dist/FilePathPattern.d.ts +29 -0
  7. package/dist/FilePathPattern.js +86 -0
  8. package/dist/FileRouter.d.ts +39 -41
  9. package/dist/FileRouter.js +104 -158
  10. package/dist/FileRouterCodegen.d.ts +7 -8
  11. package/dist/FileRouterCodegen.js +97 -66
  12. package/dist/PlatformError.d.ts +46 -0
  13. package/dist/PlatformError.js +43 -0
  14. package/dist/PlatformRuntime.d.ts +23 -0
  15. package/dist/PlatformRuntime.js +42 -0
  16. package/dist/RouteBody.d.ts +1 -1
  17. package/dist/Start.d.ts +34 -3
  18. package/dist/Start.js +31 -6
  19. package/dist/bun/BunPlatformHttpServer.d.ts +10 -0
  20. package/dist/bun/BunPlatformHttpServer.js +53 -0
  21. package/dist/bun/BunRoute.d.ts +3 -5
  22. package/dist/bun/BunRoute.js +9 -17
  23. package/dist/bun/BunRuntime.d.ts +2 -1
  24. package/dist/bun/BunRuntime.js +10 -5
  25. package/dist/bun/BunServer.d.ts +33 -0
  26. package/dist/bun/BunServer.js +133 -0
  27. package/dist/bun/BunServerRequest.d.ts +60 -0
  28. package/dist/bun/BunServerRequest.js +252 -0
  29. package/dist/bun/index.d.ts +1 -1
  30. package/dist/bun/index.js +1 -1
  31. package/dist/datastar/actions/fetch.d.ts +30 -0
  32. package/dist/datastar/actions/fetch.js +411 -0
  33. package/dist/datastar/actions/peek.d.ts +1 -0
  34. package/dist/datastar/actions/peek.js +14 -0
  35. package/dist/datastar/actions/setAll.d.ts +1 -0
  36. package/dist/datastar/actions/setAll.js +13 -0
  37. package/dist/datastar/actions/toggleAll.d.ts +1 -0
  38. package/dist/datastar/actions/toggleAll.js +13 -0
  39. package/dist/datastar/attributes/attr.d.ts +1 -0
  40. package/dist/datastar/attributes/attr.js +49 -0
  41. package/dist/datastar/attributes/bind.d.ts +1 -0
  42. package/dist/datastar/attributes/bind.js +183 -0
  43. package/dist/datastar/attributes/class.d.ts +1 -0
  44. package/dist/datastar/attributes/class.js +50 -0
  45. package/dist/datastar/attributes/computed.d.ts +1 -0
  46. package/dist/datastar/attributes/computed.js +27 -0
  47. package/dist/datastar/attributes/effect.d.ts +1 -0
  48. package/dist/datastar/attributes/effect.js +10 -0
  49. package/dist/datastar/attributes/indicator.d.ts +1 -0
  50. package/dist/datastar/attributes/indicator.js +32 -0
  51. package/dist/datastar/attributes/init.d.ts +1 -0
  52. package/dist/datastar/attributes/init.js +27 -0
  53. package/dist/datastar/attributes/jsonSignals.d.ts +1 -0
  54. package/dist/datastar/attributes/jsonSignals.js +31 -0
  55. package/dist/datastar/attributes/on.d.ts +1 -0
  56. package/dist/datastar/attributes/on.js +59 -0
  57. package/dist/datastar/attributes/onIntersect.d.ts +1 -0
  58. package/dist/datastar/attributes/onIntersect.js +54 -0
  59. package/dist/datastar/attributes/onInterval.d.ts +1 -0
  60. package/dist/datastar/attributes/onInterval.js +31 -0
  61. package/dist/datastar/attributes/onSignalPatch.d.ts +1 -0
  62. package/dist/datastar/attributes/onSignalPatch.js +44 -0
  63. package/dist/datastar/attributes/ref.d.ts +1 -0
  64. package/dist/datastar/attributes/ref.js +11 -0
  65. package/dist/datastar/attributes/show.d.ts +1 -0
  66. package/dist/datastar/attributes/show.js +32 -0
  67. package/dist/datastar/attributes/signals.d.ts +1 -0
  68. package/dist/datastar/attributes/signals.js +18 -0
  69. package/dist/datastar/attributes/style.d.ts +1 -0
  70. package/dist/datastar/attributes/style.js +56 -0
  71. package/dist/datastar/attributes/text.d.ts +1 -0
  72. package/dist/datastar/attributes/text.js +27 -0
  73. package/dist/datastar/engine.d.ts +156 -0
  74. package/dist/datastar/engine.js +971 -0
  75. package/dist/datastar/index.d.ts +24 -0
  76. package/dist/datastar/index.js +24 -0
  77. package/dist/datastar/load.d.ts +24 -0
  78. package/dist/datastar/load.js +24 -0
  79. package/dist/datastar/utils.d.ts +51 -0
  80. package/dist/datastar/utils.js +205 -0
  81. package/dist/datastar/watchers/patchElements.d.ts +1 -0
  82. package/dist/datastar/watchers/patchElements.js +420 -0
  83. package/dist/datastar/watchers/patchSignals.d.ts +1 -0
  84. package/dist/datastar/watchers/patchSignals.js +15 -0
  85. package/dist/index.d.ts +1 -1
  86. package/dist/index.js +1 -1
  87. package/dist/node/NodeFileSystem.d.ts +7 -0
  88. package/dist/node/NodeFileSystem.js +420 -0
  89. package/dist/node/NodeUtils.d.ts +2 -0
  90. package/dist/node/NodeUtils.js +20 -0
  91. package/dist/x/tailwind/plugin.js +1 -1
  92. package/package.json +11 -7
  93. package/src/Development.ts +26 -25
  94. package/src/{node/Effectify.ts → Effectify.ts} +10 -3
  95. package/src/FilePathPattern.ts +115 -0
  96. package/src/FileRouter.ts +178 -255
  97. package/src/FileRouterCodegen.ts +135 -92
  98. package/src/{node/PlatformError.ts → PlatformError.ts} +34 -19
  99. package/src/PlatformRuntime.ts +97 -0
  100. package/src/RouteBody.ts +1 -1
  101. package/src/RouteHttp.ts +3 -1
  102. package/src/Start.ts +61 -14
  103. package/src/bun/BunPlatformHttpServer.ts +88 -0
  104. package/src/bun/BunRoute.ts +12 -22
  105. package/src/bun/BunRuntime.ts +21 -5
  106. package/src/bun/BunServer.ts +228 -0
  107. package/src/bun/index.ts +1 -1
  108. package/src/datastar/README.md +18 -0
  109. package/src/datastar/actions/fetch.ts +609 -0
  110. package/src/datastar/actions/peek.ts +17 -0
  111. package/src/datastar/actions/setAll.ts +20 -0
  112. package/src/datastar/actions/toggleAll.ts +20 -0
  113. package/src/datastar/attributes/attr.ts +50 -0
  114. package/src/datastar/attributes/bind.ts +220 -0
  115. package/src/datastar/attributes/class.ts +57 -0
  116. package/src/datastar/attributes/computed.ts +33 -0
  117. package/src/datastar/attributes/effect.ts +11 -0
  118. package/src/datastar/attributes/indicator.ts +39 -0
  119. package/src/datastar/attributes/init.ts +35 -0
  120. package/src/datastar/attributes/jsonSignals.ts +38 -0
  121. package/src/datastar/attributes/on.ts +71 -0
  122. package/src/datastar/attributes/onIntersect.ts +65 -0
  123. package/src/datastar/attributes/onInterval.ts +39 -0
  124. package/src/datastar/attributes/onSignalPatch.ts +63 -0
  125. package/src/datastar/attributes/ref.ts +12 -0
  126. package/src/datastar/attributes/show.ts +33 -0
  127. package/src/datastar/attributes/signals.ts +22 -0
  128. package/src/datastar/attributes/style.ts +63 -0
  129. package/src/datastar/attributes/text.ts +30 -0
  130. package/src/datastar/engine.ts +1341 -0
  131. package/src/datastar/index.ts +25 -0
  132. package/src/datastar/utils.ts +286 -0
  133. package/src/datastar/watchers/patchElements.ts +554 -0
  134. package/src/datastar/watchers/patchSignals.ts +15 -0
  135. package/src/index.ts +1 -1
  136. package/src/node/{FileSystem.ts → NodeFileSystem.ts} +2 -2
  137. package/src/node/{Utils.ts → NodeUtils.ts} +2 -0
  138. package/src/x/tailwind/plugin.ts +1 -1
  139. package/src/FileRouterCodegen.todo.ts +0 -1133
  140. package/src/FileRouterPattern.ts +0 -59
  141. package/src/RouterPattern.ts +0 -416
  142. package/src/StartApp.ts +0 -47
  143. package/src/bun/BunHttpServer.ts +0 -303
  144. /package/src/bun/{BunHttpServer_web.ts → BunServerRequest.ts} +0 -0
@@ -1,11 +1,10 @@
1
- import type { PlatformError } from "@effect/platform/Error"
2
1
  import * as FileSystem from "@effect/platform/FileSystem"
3
2
  import * as Effect from "effect/Effect"
4
- import * as Function from "effect/Function"
3
+ import * as Either from "effect/Either"
5
4
  import * as Schema from "effect/Schema"
6
5
  import * as NPath from "node:path"
6
+ import * as FilePathPattern from "./FilePathPattern.ts"
7
7
  import * as FileRouter from "./FileRouter.ts"
8
- import * as FileRouterPattern from "./FileRouterPattern.ts"
9
8
  import * as SchemaExtra from "./SchemaExtra.ts"
10
9
 
11
10
  export function validateRouteModule(
@@ -24,7 +23,7 @@ export function validateRouteModule(
24
23
  }
25
24
 
26
25
  export function generatePathParamsSchema(
27
- segments: ReadonlyArray<FileRouterPattern.Segment>,
26
+ segments: ReadonlyArray<FilePathPattern.Segment>,
28
27
  ): Schema.Struct<any> | null {
29
28
  const fields: Record<
30
29
  PropertyKey,
@@ -32,13 +31,10 @@ export function generatePathParamsSchema(
32
31
  > = {}
33
32
 
34
33
  for (const segment of segments) {
35
- if (
36
- segment._tag === "ParamSegment"
37
- || segment._tag === "RestSegment"
38
- ) {
39
- fields[segment.name] = segment.optional
40
- ? Function.pipe(Schema.String, Schema.optional)
41
- : Schema.String
34
+ if (segment._tag === "ParamSegment") {
35
+ fields[segment.name] = Schema.String
36
+ } else if (segment._tag === "RestSegment") {
37
+ fields[segment.name] = Schema.optional(Schema.String)
42
38
  }
43
39
  }
44
40
 
@@ -54,23 +50,33 @@ export function generatePathParamsSchema(
54
50
  */
55
51
 
56
52
  export function validateRouteModules(
57
- routesPath: string,
58
- handles: FileRouter.OrderedRouteHandles,
59
- ): Effect.Effect<void, PlatformError, FileSystem.FileSystem> {
53
+ path: string,
54
+ routes: FileRouter.OrderedFileRoutes,
55
+ ): Effect.Effect<void, FileRouter.FileRouterError, FileSystem.FileSystem> {
60
56
  return Effect.gen(function*() {
61
57
  const fs = yield* FileSystem.FileSystem
62
- const routeHandles = handles.filter(h => h.handle === "route")
58
+ const routeHandles = routes.filter(h => h.handle === "route")
63
59
 
64
60
  for (const handle of routeHandles) {
65
- const routeModulePath = NPath.resolve(routesPath, handle.modulePath)
61
+ const routeModulePath = NPath.resolve(path, handle.modulePath)
66
62
  const expectedSchema = generatePathParamsSchema(handle.segments)
67
63
 
68
- const fileExists = yield* fs.exists(routeModulePath)
64
+ const fileExists = yield* fs.exists(routeModulePath).pipe(
65
+ Effect.catchAll(() => Effect.succeed(false)),
66
+ )
69
67
  if (!fileExists) {
70
68
  continue
71
69
  }
72
70
 
73
- const module = yield* Effect.promise(() => import(routeModulePath))
71
+ const module = yield* Effect.tryPromise({
72
+ try: () => import(routeModulePath),
73
+ catch: (cause) =>
74
+ new FileRouter.FileRouterError({
75
+ reason: "Import",
76
+ cause,
77
+ path: routeModulePath,
78
+ }),
79
+ })
74
80
 
75
81
  if (!validateRouteModule(module)) {
76
82
  yield* Effect.logWarning(
@@ -100,50 +106,42 @@ export function validateRouteModules(
100
106
  }
101
107
 
102
108
  export function generateCode(
103
- handles: FileRouter.OrderedRouteHandles,
104
- ): string {
105
- const routerModuleId = "effect-start"
106
-
109
+ fileRoutes: FileRouter.OrderedFileRoutes,
110
+ ): string | null {
107
111
  // Group routes by path to find layers
108
112
  const routesByPath = new Map<string, {
109
- route?: FileRouter.RouteHandle
110
- layers: FileRouter.RouteHandle[]
113
+ route?: FileRouter.FileRoute
114
+ layers: FileRouter.FileRoute[]
111
115
  }>()
112
116
 
113
- for (const handle of handles) {
114
- const existing = routesByPath.get(handle.routePath) || { layers: [] }
115
- if (handle.handle === "route") {
116
- existing.route = handle
117
- } else if (handle.handle === "layer") {
118
- existing.layers.push(handle)
117
+ for (const fileRoute of fileRoutes) {
118
+ const existing = routesByPath.get(fileRoute.routePath) || { layers: [] }
119
+ if (fileRoute.handle === "route") {
120
+ existing.route = fileRoute
121
+ } else if (fileRoute.handle === "layer") {
122
+ existing.layers.push(fileRoute)
119
123
  }
120
- routesByPath.set(handle.routePath, existing)
124
+ routesByPath.set(fileRoute.routePath, existing)
121
125
  }
122
126
 
123
- // Generate route definitions
124
- const routes: string[] = []
125
-
126
127
  // Helper to check if layer's path is an ancestor of route's path
127
128
  const layerMatchesRoute = (
128
- layer: FileRouter.RouteHandle,
129
- route: FileRouter.RouteHandle,
129
+ layer: FileRouter.FileRoute,
130
+ route: FileRouter.FileRoute,
130
131
  ): boolean => {
131
- // Get the directory of the layer (strip the filename like layer.tsx)
132
- const layerDir = layer.modulePath.replace(/\/?(layer)\.(tsx?|jsx?)$/, "")
133
-
134
- // Layer at root (empty layerDir) applies to all routes
135
- if (layerDir === "") return true
136
-
137
- // Route's modulePath must start with the layer's directory
132
+ const layerDir = layer.modulePath.replace(/\/(layer)\.(tsx?|jsx?)$/, "")
133
+ if (layerDir === "/") return true
138
134
  return route.modulePath.startsWith(layerDir + "/")
139
135
  }
140
136
 
141
- // Find layers for each route by walking up the path hierarchy
137
+ // Build entries for each route path
138
+ const entries: Array<{ path: string; loaders: string[] }> = []
139
+
142
140
  for (const [path, { route }] of routesByPath) {
143
- if (!route) continue // Skip paths that only have layers
141
+ if (!route) continue
144
142
 
145
143
  // Collect all parent layers that match the route's groups
146
- const allLayers: FileRouter.RouteHandle[] = []
144
+ const allLayers: FileRouter.FileRoute[] = []
147
145
  let currentPath = path
148
146
 
149
147
  while (true) {
@@ -157,97 +155,142 @@ export function generateCode(
157
155
 
158
156
  if (currentPath === "/") break
159
157
 
160
- // Move to parent path
161
158
  const parentPath = currentPath.substring(0, currentPath.lastIndexOf("/"))
162
159
  currentPath = parentPath || "/"
163
160
  }
164
161
 
165
- // Generate layers array
166
- const layersCode = allLayers.length > 0
167
- ? `\n layers: [\n ${
168
- allLayers.map(layer => `() => import("./${layer.modulePath}")`).join(
169
- ",\n ",
170
- )
171
- },\n ],`
172
- : ""
162
+ // Convert file-style path to colon-style PathPattern
163
+ const pathPatternResult = FilePathPattern.toPathPattern(path)
164
+ if (Either.isLeft(pathPatternResult)) {
165
+ continue
166
+ }
167
+ const pathPattern = pathPatternResult.right
173
168
 
174
- const routeCode = ` {
175
- path: "${path}",
176
- load: () => import("./${route.modulePath}"),${layersCode}
177
- },`
169
+ // Order: route first, then layers from innermost to outermost
170
+ const loaders: string[] = [
171
+ `() => import(".${route.modulePath}")`,
172
+ ...allLayers.reverse().map(layer =>
173
+ `() => import(".${layer.modulePath}")`
174
+ ),
175
+ ]
178
176
 
179
- routes.push(routeCode)
177
+ entries.push({ path: pathPattern, loaders })
180
178
  }
181
179
 
182
- const header = `/**
183
- * Auto-generated by effect-start.
184
- */`
180
+ // No routes found - don't create file
181
+ if (entries.length === 0) {
182
+ return null
183
+ }
185
184
 
186
- const routesArray = routes.length > 0
187
- ? `[\n${routes.join("\n")}\n]`
188
- : "[]"
185
+ const routeEntries = entries
186
+ .map(({ path, loaders }) => {
187
+ const loadersCode = loaders.join(",\n ")
188
+ return ` "${path}": [\n ${loadersCode},\n ]`
189
+ })
190
+ .join(",\n")
189
191
 
190
- return `${header}
192
+ return `/**
193
+ * Auto-generated by effect-start.
194
+ */
191
195
 
192
- export const routes = ${routesArray} as const
196
+ export default {
197
+ ${routeEntries},
198
+ } satisfies import("effect-start/FileRouter").FileRoutes
193
199
  `
194
200
  }
195
201
 
196
202
  /**
197
- * Updates the manifest file only if the generated content differs from the existing file.
203
+ * Updates the tree file only if the generated content differs from the existing file.
198
204
  * This prevents infinite loops when watching for file changes.
199
205
  */
200
206
  export function update(
201
207
  routesPath: string,
202
- manifestPath = "manifest.ts",
203
- ): Effect.Effect<void, PlatformError, FileSystem.FileSystem> {
208
+ treePath = "server.gen.ts",
209
+ ): Effect.Effect<void, FileRouter.FileRouterError, FileSystem.FileSystem> {
204
210
  return Effect.gen(function*() {
205
- manifestPath = NPath.resolve(routesPath, manifestPath)
211
+ treePath = NPath.resolve(routesPath, treePath)
206
212
 
207
213
  const fs = yield* FileSystem.FileSystem
208
- const files = yield* fs.readDirectory(routesPath, { recursive: true })
209
- const handles = FileRouter.getRouteHandlesFromPaths(files)
214
+ const files = yield* fs.readDirectory(routesPath, { recursive: true }).pipe(
215
+ Effect.mapError((cause) =>
216
+ new FileRouter.FileRouterError({ reason: "FileSystem", cause, path: routesPath })
217
+ ),
218
+ )
219
+ const fileRoutes = yield* FileRouter.getFileRoutes(files)
210
220
 
211
221
  // Validate route modules
212
- yield* validateRouteModules(routesPath, handles)
222
+ yield* validateRouteModules(routesPath, fileRoutes)
213
223
 
214
- const newCode = generateCode(handles)
224
+ const newCode = generateCode(fileRoutes)
215
225
 
216
- // Check if file exists and content differs
226
+ // Check if file exists (ok to fail - means file doesn't exist)
217
227
  const existingCode = yield* fs
218
- .readFileString(manifestPath)
228
+ .readFileString(treePath)
219
229
  .pipe(Effect.catchAll(() => Effect.succeed(null)))
220
230
 
231
+ // No routes found
232
+ if (newCode === null) {
233
+ // If gen file exists, write empty export
234
+ if (existingCode !== null) {
235
+ const emptyCode = "export default {}\n"
236
+ if (existingCode !== emptyCode) {
237
+ yield* Effect.logDebug(`Clearing file routes tree: ${treePath}`)
238
+ yield* fs.writeFileString(treePath, emptyCode).pipe(
239
+ Effect.mapError((cause) =>
240
+ new FileRouter.FileRouterError({ reason: "FileSystem", cause, path: treePath })
241
+ ),
242
+ )
243
+ }
244
+ }
245
+ return
246
+ }
247
+
248
+ // Write if content differs
221
249
  if (existingCode !== newCode) {
222
- yield* Effect.logDebug(`Updating file routes manifest: ${manifestPath}`)
223
- yield* fs.writeFileString(manifestPath, newCode)
250
+ yield* Effect.logDebug(`Updating file routes tree: ${treePath}`)
251
+ yield* fs.writeFileString(treePath, newCode).pipe(
252
+ Effect.mapError((cause) =>
253
+ new FileRouter.FileRouterError({ reason: "FileSystem", cause, path: treePath })
254
+ ),
255
+ )
224
256
  } else {
225
- yield* Effect.logDebug(`File routes manifest unchanged: ${manifestPath}`)
257
+ yield* Effect.logDebug(`File routes tree unchanged: ${treePath}`)
226
258
  }
227
259
  })
228
260
  }
229
261
 
230
262
  export function dump(
231
263
  routesPath: string,
232
- manifestPath = "manifest.ts",
233
- ): Effect.Effect<void, PlatformError, FileSystem.FileSystem> {
264
+ treePath = "server.gen.ts",
265
+ ): Effect.Effect<void, FileRouter.FileRouterError, FileSystem.FileSystem> {
234
266
  return Effect.gen(function*() {
235
- manifestPath = NPath.resolve(routesPath, manifestPath)
267
+ treePath = NPath.resolve(routesPath, treePath)
236
268
 
237
269
  const fs = yield* FileSystem.FileSystem
238
- const files = yield* fs.readDirectory(routesPath, { recursive: true })
239
- const handles = FileRouter.getRouteHandlesFromPaths(files)
270
+ const files = yield* fs.readDirectory(routesPath, { recursive: true }).pipe(
271
+ Effect.mapError((cause) =>
272
+ new FileRouter.FileRouterError({ reason: "FileSystem", cause, path: routesPath })
273
+ ),
274
+ )
275
+ const fileRoutes = yield* FileRouter.getFileRoutes(files)
240
276
 
241
277
  // Validate route modules
242
- yield* validateRouteModules(routesPath, handles)
278
+ yield* validateRouteModules(routesPath, fileRoutes)
243
279
 
244
- const code = generateCode(handles)
280
+ const code = generateCode(fileRoutes)
281
+
282
+ // No routes found - don't create file
283
+ if (code === null) {
284
+ yield* Effect.logDebug(`No routes found, skipping: ${treePath}`)
285
+ return
286
+ }
245
287
 
246
- yield* Effect.logDebug(`Generating file routes manifest: ${manifestPath}`)
288
+ yield* Effect.logDebug(`Generating file routes tree: ${treePath}`)
247
289
 
248
- yield* fs.writeFileString(
249
- manifestPath,
250
- code,
290
+ yield* fs.writeFileString(treePath, code).pipe(
291
+ Effect.mapError((cause) =>
292
+ new FileRouter.FileRouterError({ reason: "FileSystem", cause, path: treePath })
293
+ ),
251
294
  )
252
295
  })
253
296
  }
@@ -13,9 +13,13 @@ export const TypeId: typeof TypeId_ = TypeId_
13
13
 
14
14
  export type TypeId = typeof TypeId
15
15
 
16
- export const isPlatformError = (u: unknown): u is PlatformError => Predicate.hasProperty(u, TypeId)
16
+ export const isPlatformError = (u: unknown): u is PlatformError =>
17
+ Predicate.hasProperty(u, TypeId)
17
18
 
18
- export const TypeIdError = <const TypeId extends symbol, const Tag extends string>(
19
+ export const TypeIdError = <
20
+ const TypeId extends symbol,
21
+ const Tag extends string,
22
+ >(
19
23
  typeId: TypeId,
20
24
  tag: Tag,
21
25
  ): new<A extends Record<string, any>>(
@@ -24,7 +28,8 @@ export const TypeIdError = <const TypeId extends symbol, const Tag extends strin
24
28
  & Cause.YieldableError
25
29
  & Record<TypeId, TypeId>
26
30
  & { readonly _tag: Tag }
27
- & Readonly<A> => {
31
+ & Readonly<A> =>
32
+ {
28
33
  class Base extends Data.Error<{}> {
29
34
  readonly _tag = tag
30
35
  }
@@ -44,17 +49,22 @@ export const Module = Schema.Literal(
44
49
  )
45
50
 
46
51
  export class BadArgument
47
- extends Schema.TaggedError<BadArgument>("@effect/platform/Error/BadArgument")("BadArgument", {
48
- module: Module,
49
- method: Schema.String,
50
- description: Schema.optional(Schema.String),
51
- cause: Schema.optional(Schema.Defect),
52
- })
52
+ extends Schema.TaggedError<BadArgument>("@effect/platform/Error/BadArgument")(
53
+ "BadArgument",
54
+ {
55
+ module: Module,
56
+ method: Schema.String,
57
+ description: Schema.optional(Schema.String),
58
+ cause: Schema.optional(Schema.Defect),
59
+ },
60
+ )
53
61
  {
54
62
  readonly [TypeId]: typeof TypeId = TypeId
55
63
 
56
64
  get message(): string {
57
- return `${this.module}.${this.method}${this.description ? `: ${this.description}` : ""}`
65
+ return `${this.module}.${this.method}${
66
+ this.description ? `: ${this.description}` : ""
67
+ }`
58
68
  }
59
69
  }
60
70
 
@@ -75,15 +85,20 @@ export const SystemErrorReason = Schema.Literal(
75
85
  export type SystemErrorReason = typeof SystemErrorReason.Type
76
86
 
77
87
  export class SystemError
78
- extends Schema.TaggedError<SystemError>("@effect/platform/Error/SystemError")("SystemError", {
79
- reason: SystemErrorReason,
80
- module: Module,
81
- method: Schema.String,
82
- description: Schema.optional(Schema.String),
83
- syscall: Schema.optional(Schema.String),
84
- pathOrDescriptor: Schema.optional(Schema.Union(Schema.String, Schema.Number)),
85
- cause: Schema.optional(Schema.Defect),
86
- })
88
+ extends Schema.TaggedError<SystemError>("@effect/platform/Error/SystemError")(
89
+ "SystemError",
90
+ {
91
+ reason: SystemErrorReason,
92
+ module: Module,
93
+ method: Schema.String,
94
+ description: Schema.optional(Schema.String),
95
+ syscall: Schema.optional(Schema.String),
96
+ pathOrDescriptor: Schema.optional(
97
+ Schema.Union(Schema.String, Schema.Number),
98
+ ),
99
+ cause: Schema.optional(Schema.Defect),
100
+ },
101
+ )
87
102
  {
88
103
  readonly [TypeId]: typeof TypeId = TypeId
89
104
 
@@ -1,3 +1,100 @@
1
+ import * as Cause from "effect/Cause"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Exit from "effect/Exit"
4
+ import type * as Fiber from "effect/Fiber"
5
+ import type * as FiberId from "effect/FiberId"
6
+ import * as FiberRef from "effect/FiberRef"
7
+ import * as FiberRefs from "effect/FiberRefs"
8
+ import * as Function from "effect/Function"
9
+ import * as HashSet from "effect/HashSet"
10
+ import * as Logger from "effect/Logger"
11
+
12
+ export interface Teardown {
13
+ <E, A>(exit: Exit.Exit<E, A>, onExit: (code: number) => void): void
14
+ }
15
+
16
+ export const defaultTeardown: Teardown = <E, A>(
17
+ exit: Exit.Exit<E, A>,
18
+ onExit: (code: number) => void,
19
+ ) => {
20
+ onExit(Exit.isFailure(exit) && !Cause.isInterruptedOnly(exit.cause) ? 1 : 0)
21
+ }
22
+
23
+ export interface RunMain {
24
+ (
25
+ options?: {
26
+ readonly disableErrorReporting?: boolean | undefined
27
+ readonly disablePrettyLogger?: boolean | undefined
28
+ readonly teardown?: Teardown | undefined
29
+ },
30
+ ): <E, A>(effect: Effect.Effect<A, E>) => void
31
+ <E, A>(
32
+ effect: Effect.Effect<A, E>,
33
+ options?: {
34
+ readonly disableErrorReporting?: boolean | undefined
35
+ readonly disablePrettyLogger?: boolean | undefined
36
+ readonly teardown?: Teardown | undefined
37
+ },
38
+ ): void
39
+ }
40
+
41
+ const addPrettyLogger = (
42
+ refs: FiberRefs.FiberRefs,
43
+ fiberId: FiberId.Runtime,
44
+ ) => {
45
+ const loggers = FiberRefs.getOrDefault(refs, FiberRef.currentLoggers)
46
+ if (!HashSet.has(loggers, Logger.defaultLogger)) {
47
+ return refs
48
+ }
49
+ return FiberRefs.updateAs(refs, {
50
+ fiberId,
51
+ fiberRef: FiberRef.currentLoggers,
52
+ value: loggers.pipe(
53
+ HashSet.remove(Logger.defaultLogger),
54
+ HashSet.add(Logger.prettyLoggerDefault),
55
+ ),
56
+ })
57
+ }
58
+
59
+ export const makeRunMain = (
60
+ f: <E, A>(
61
+ options: {
62
+ readonly fiber: Fiber.RuntimeFiber<A, E>
63
+ readonly teardown: Teardown
64
+ },
65
+ ) => void,
66
+ ): RunMain =>
67
+ Function.dual(
68
+ (args) => Effect.isEffect(args[0]),
69
+ (effect: Effect.Effect<any, any>, options?: {
70
+ readonly disableErrorReporting?: boolean | undefined
71
+ readonly disablePrettyLogger?: boolean | undefined
72
+ readonly teardown?: Teardown | undefined
73
+ }) => {
74
+ const fiber = options?.disableErrorReporting === true
75
+ ? Effect.runFork(effect, {
76
+ updateRefs: options?.disablePrettyLogger === true
77
+ ? undefined
78
+ : addPrettyLogger,
79
+ })
80
+ : Effect.runFork(
81
+ Effect.tapErrorCause(effect, (cause) => {
82
+ if (Cause.isInterruptedOnly(cause)) {
83
+ return Effect.void
84
+ }
85
+ return Effect.logError(cause)
86
+ }),
87
+ {
88
+ updateRefs: options?.disablePrettyLogger === true
89
+ ? undefined
90
+ : addPrettyLogger,
91
+ },
92
+ )
93
+ const teardown = options?.teardown ?? defaultTeardown
94
+ return f({ fiber, teardown })
95
+ },
96
+ )
97
+
1
98
  /**
2
99
  * Are we running within an agent harness, like Claude Code?
3
100
  */
package/src/RouteBody.ts CHANGED
@@ -35,7 +35,7 @@ export type GeneratorHandler<B, A, Y> = (
35
35
  next: (
36
36
  context?: Partial<B> & Record<string, unknown>,
37
37
  ) => Entity.Entity<UnwrapStream<A>>,
38
- ) => Generator<Y, A | Entity.Entity<A>, unknown>
38
+ ) => Generator<Y, A | Entity.Entity<A>, never>
39
39
 
40
40
  export type HandlerInput<B, A, E, R> =
41
41
  | A
package/src/RouteHttp.ts CHANGED
@@ -391,7 +391,9 @@ export const toWebHandlerRuntime = <R>(
391
391
  if (exit._tag === "Success") {
392
392
  resolve(exit.value)
393
393
  } else if (isClientAbort(exit.cause)) {
394
- resolve(respondError({ status: 499, message: "client closed request" }))
394
+ resolve(
395
+ respondError({ status: 499, message: "client closed request" }),
396
+ )
395
397
  } else {
396
398
  const status = getStatusFromCause(exit.cause)
397
399
  const message = Cause.pretty(exit.cause, { renderErrorCause: true })
package/src/Start.ts CHANGED
@@ -1,9 +1,11 @@
1
+ import * as FileSystem from "@effect/platform/FileSystem"
2
+ import * as Context from "effect/Context"
1
3
  import * as Effect from "effect/Effect"
2
4
  import * as Function from "effect/Function"
3
5
  import * as Layer from "effect/Layer"
4
- import * as BunHttpServer from "./bun/BunHttpServer.ts"
5
6
  import * as BunRuntime from "./bun/BunRuntime.ts"
6
- import * as StartApp from "./StartApp.ts"
7
+ import * as BunServer from "./bun/BunServer.ts"
8
+ import * as NodeFileSystem from "./node/NodeFileSystem.ts"
7
9
 
8
10
  export function layer<
9
11
  Layers extends [
@@ -18,13 +20,56 @@ export function layer<
18
20
  return Layer.mergeAll(...layers)
19
21
  }
20
22
 
21
- export function serve<ROut, E>(
23
+ /**
24
+ * Bundles layers together, wiring their dependencies automatically.
25
+ *
26
+ * Equivalent to chaining `Layer.provide` calls, but more concise.
27
+ *
28
+ * **Ordering: dependents first, dependencies last.**
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // UserRepo needs Database, Database needs Logger
33
+ * const AppLayer = Start.pack(
34
+ * UserRepoLive, // needs Database, Logger
35
+ * DatabaseLive, // needs Logger
36
+ * LoggerLive, // no deps
37
+ * )
38
+ * // Result: Layer<UserRepo | Database | Logger, never, never>
39
+ * ```
40
+ *
41
+ * @since 1.0.0
42
+ * @category constructors
43
+ */
44
+ export function pack<
45
+ const Layers extends readonly [Layer.Layer.Any, ...Array<Layer.Layer.Any>],
46
+ >(
47
+ ...layers: Layers
48
+ ): Layer.Layer<
49
+ { [K in keyof Layers]: Layer.Layer.Success<Layers[K]> }[number],
50
+ { [K in keyof Layers]: Layer.Layer.Error<Layers[K]> }[number],
51
+ Exclude<
52
+ { [K in keyof Layers]: Layer.Layer.Context<Layers[K]> }[number],
53
+ { [K in keyof Layers]: Layer.Layer.Success<Layers[K]> }[number]
54
+ >
55
+ > {
56
+ type AnyLayer = Layer.Layer<any, any, any>
57
+ const layerArray = layers as unknown as ReadonlyArray<AnyLayer>
58
+ const result: AnyLayer = layerArray.reduce(
59
+ (acc: AnyLayer, layer: AnyLayer) => Layer.provideMerge(acc, layer),
60
+ Layer.succeedContext(Context.empty()) as unknown as AnyLayer,
61
+ )
62
+
63
+ return result as AnyLayer
64
+ }
65
+
66
+ export function serve<
67
+ ROut,
68
+ E,
69
+ RIn extends BunServer.BunServer | FileSystem.FileSystem,
70
+ >(
22
71
  load: () => Promise<{
23
- default: Layer.Layer<
24
- ROut,
25
- E,
26
- BunHttpServer.BunHttpServer
27
- >
72
+ default: Layer.Layer<ROut, E, RIn>
28
73
  }>,
29
74
  ) {
30
75
  const appLayer = Function.pipe(
@@ -34,13 +79,15 @@ export function serve<ROut, E>(
34
79
  Layer.unwrapEffect,
35
80
  )
36
81
 
37
- return Function.pipe(
38
- BunHttpServer.layerAuto(),
82
+ const composed = Function.pipe(
83
+ BunServer.layer(),
84
+ BunServer.withLogAddress,
39
85
  Layer.provide(appLayer),
40
- Layer.provide([
41
- BunHttpServer.layer(),
42
- StartApp.layer(),
43
- ]),
86
+ Layer.provide(NodeFileSystem.layer),
87
+ ) as Layer.Layer<BunServer.BunServer, never, never>
88
+
89
+ return Function.pipe(
90
+ composed,
44
91
  Layer.launch,
45
92
  BunRuntime.runMain,
46
93
  )