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.
- package/README.md +3 -3
- package/dist/Development.d.ts +3 -3
- package/dist/Development.js +3 -2
- package/dist/Effectify.d.ts +212 -0
- package/dist/Effectify.js +19 -0
- package/dist/FilePathPattern.d.ts +29 -0
- package/dist/FilePathPattern.js +86 -0
- package/dist/FileRouter.d.ts +39 -41
- package/dist/FileRouter.js +104 -158
- package/dist/FileRouterCodegen.d.ts +7 -8
- package/dist/FileRouterCodegen.js +97 -66
- package/dist/PlatformError.d.ts +46 -0
- package/dist/PlatformError.js +43 -0
- package/dist/PlatformRuntime.d.ts +23 -0
- package/dist/PlatformRuntime.js +42 -0
- package/dist/RouteBody.d.ts +1 -1
- package/dist/Start.d.ts +34 -3
- package/dist/Start.js +31 -6
- package/dist/bun/BunPlatformHttpServer.d.ts +10 -0
- package/dist/bun/BunPlatformHttpServer.js +53 -0
- package/dist/bun/BunRoute.d.ts +3 -5
- package/dist/bun/BunRoute.js +9 -17
- package/dist/bun/BunRuntime.d.ts +2 -1
- package/dist/bun/BunRuntime.js +10 -5
- package/dist/bun/BunServer.d.ts +33 -0
- package/dist/bun/BunServer.js +133 -0
- package/dist/bun/BunServerRequest.d.ts +60 -0
- package/dist/bun/BunServerRequest.js +252 -0
- package/dist/bun/index.d.ts +1 -1
- package/dist/bun/index.js +1 -1
- package/dist/datastar/actions/fetch.d.ts +30 -0
- package/dist/datastar/actions/fetch.js +411 -0
- package/dist/datastar/actions/peek.d.ts +1 -0
- package/dist/datastar/actions/peek.js +14 -0
- package/dist/datastar/actions/setAll.d.ts +1 -0
- package/dist/datastar/actions/setAll.js +13 -0
- package/dist/datastar/actions/toggleAll.d.ts +1 -0
- package/dist/datastar/actions/toggleAll.js +13 -0
- package/dist/datastar/attributes/attr.d.ts +1 -0
- package/dist/datastar/attributes/attr.js +49 -0
- package/dist/datastar/attributes/bind.d.ts +1 -0
- package/dist/datastar/attributes/bind.js +183 -0
- package/dist/datastar/attributes/class.d.ts +1 -0
- package/dist/datastar/attributes/class.js +50 -0
- package/dist/datastar/attributes/computed.d.ts +1 -0
- package/dist/datastar/attributes/computed.js +27 -0
- package/dist/datastar/attributes/effect.d.ts +1 -0
- package/dist/datastar/attributes/effect.js +10 -0
- package/dist/datastar/attributes/indicator.d.ts +1 -0
- package/dist/datastar/attributes/indicator.js +32 -0
- package/dist/datastar/attributes/init.d.ts +1 -0
- package/dist/datastar/attributes/init.js +27 -0
- package/dist/datastar/attributes/jsonSignals.d.ts +1 -0
- package/dist/datastar/attributes/jsonSignals.js +31 -0
- package/dist/datastar/attributes/on.d.ts +1 -0
- package/dist/datastar/attributes/on.js +59 -0
- package/dist/datastar/attributes/onIntersect.d.ts +1 -0
- package/dist/datastar/attributes/onIntersect.js +54 -0
- package/dist/datastar/attributes/onInterval.d.ts +1 -0
- package/dist/datastar/attributes/onInterval.js +31 -0
- package/dist/datastar/attributes/onSignalPatch.d.ts +1 -0
- package/dist/datastar/attributes/onSignalPatch.js +44 -0
- package/dist/datastar/attributes/ref.d.ts +1 -0
- package/dist/datastar/attributes/ref.js +11 -0
- package/dist/datastar/attributes/show.d.ts +1 -0
- package/dist/datastar/attributes/show.js +32 -0
- package/dist/datastar/attributes/signals.d.ts +1 -0
- package/dist/datastar/attributes/signals.js +18 -0
- package/dist/datastar/attributes/style.d.ts +1 -0
- package/dist/datastar/attributes/style.js +56 -0
- package/dist/datastar/attributes/text.d.ts +1 -0
- package/dist/datastar/attributes/text.js +27 -0
- package/dist/datastar/engine.d.ts +156 -0
- package/dist/datastar/engine.js +971 -0
- package/dist/datastar/index.d.ts +24 -0
- package/dist/datastar/index.js +24 -0
- package/dist/datastar/load.d.ts +24 -0
- package/dist/datastar/load.js +24 -0
- package/dist/datastar/utils.d.ts +51 -0
- package/dist/datastar/utils.js +205 -0
- package/dist/datastar/watchers/patchElements.d.ts +1 -0
- package/dist/datastar/watchers/patchElements.js +420 -0
- package/dist/datastar/watchers/patchSignals.d.ts +1 -0
- package/dist/datastar/watchers/patchSignals.js +15 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node/NodeFileSystem.d.ts +7 -0
- package/dist/node/NodeFileSystem.js +420 -0
- package/dist/node/NodeUtils.d.ts +2 -0
- package/dist/node/NodeUtils.js +20 -0
- package/dist/x/tailwind/plugin.js +1 -1
- package/package.json +11 -7
- package/src/Development.ts +26 -25
- package/src/{node/Effectify.ts → Effectify.ts} +10 -3
- package/src/FilePathPattern.ts +115 -0
- package/src/FileRouter.ts +178 -255
- package/src/FileRouterCodegen.ts +135 -92
- package/src/{node/PlatformError.ts → PlatformError.ts} +34 -19
- package/src/PlatformRuntime.ts +97 -0
- package/src/RouteBody.ts +1 -1
- package/src/RouteHttp.ts +3 -1
- package/src/Start.ts +61 -14
- package/src/bun/BunPlatformHttpServer.ts +88 -0
- package/src/bun/BunRoute.ts +12 -22
- package/src/bun/BunRuntime.ts +21 -5
- package/src/bun/BunServer.ts +228 -0
- package/src/bun/index.ts +1 -1
- package/src/datastar/README.md +18 -0
- package/src/datastar/actions/fetch.ts +609 -0
- package/src/datastar/actions/peek.ts +17 -0
- package/src/datastar/actions/setAll.ts +20 -0
- package/src/datastar/actions/toggleAll.ts +20 -0
- package/src/datastar/attributes/attr.ts +50 -0
- package/src/datastar/attributes/bind.ts +220 -0
- package/src/datastar/attributes/class.ts +57 -0
- package/src/datastar/attributes/computed.ts +33 -0
- package/src/datastar/attributes/effect.ts +11 -0
- package/src/datastar/attributes/indicator.ts +39 -0
- package/src/datastar/attributes/init.ts +35 -0
- package/src/datastar/attributes/jsonSignals.ts +38 -0
- package/src/datastar/attributes/on.ts +71 -0
- package/src/datastar/attributes/onIntersect.ts +65 -0
- package/src/datastar/attributes/onInterval.ts +39 -0
- package/src/datastar/attributes/onSignalPatch.ts +63 -0
- package/src/datastar/attributes/ref.ts +12 -0
- package/src/datastar/attributes/show.ts +33 -0
- package/src/datastar/attributes/signals.ts +22 -0
- package/src/datastar/attributes/style.ts +63 -0
- package/src/datastar/attributes/text.ts +30 -0
- package/src/datastar/engine.ts +1341 -0
- package/src/datastar/index.ts +25 -0
- package/src/datastar/utils.ts +286 -0
- package/src/datastar/watchers/patchElements.ts +554 -0
- package/src/datastar/watchers/patchSignals.ts +15 -0
- package/src/index.ts +1 -1
- package/src/node/{FileSystem.ts → NodeFileSystem.ts} +2 -2
- package/src/node/{Utils.ts → NodeUtils.ts} +2 -0
- package/src/x/tailwind/plugin.ts +1 -1
- package/src/FileRouterCodegen.todo.ts +0 -1133
- package/src/FileRouterPattern.ts +0 -59
- package/src/RouterPattern.ts +0 -416
- package/src/StartApp.ts +0 -47
- package/src/bun/BunHttpServer.ts +0 -303
- /package/src/bun/{BunHttpServer_web.ts → BunServerRequest.ts} +0 -0
package/src/FileRouterCodegen.ts
CHANGED
|
@@ -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
|
|
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<
|
|
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.
|
|
37
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
): Effect.Effect<void,
|
|
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 =
|
|
58
|
+
const routeHandles = routes.filter(h => h.handle === "route")
|
|
63
59
|
|
|
64
60
|
for (const handle of routeHandles) {
|
|
65
|
-
const routeModulePath = NPath.resolve(
|
|
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.
|
|
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
|
-
|
|
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.
|
|
110
|
-
layers: FileRouter.
|
|
113
|
+
route?: FileRouter.FileRoute
|
|
114
|
+
layers: FileRouter.FileRoute[]
|
|
111
115
|
}>()
|
|
112
116
|
|
|
113
|
-
for (const
|
|
114
|
-
const existing = routesByPath.get(
|
|
115
|
-
if (
|
|
116
|
-
existing.route =
|
|
117
|
-
} else if (
|
|
118
|
-
existing.layers.push(
|
|
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(
|
|
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.
|
|
129
|
-
route: FileRouter.
|
|
129
|
+
layer: FileRouter.FileRoute,
|
|
130
|
+
route: FileRouter.FileRoute,
|
|
130
131
|
): boolean => {
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
//
|
|
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
|
|
141
|
+
if (!route) continue
|
|
144
142
|
|
|
145
143
|
// Collect all parent layers that match the route's groups
|
|
146
|
-
const allLayers: FileRouter.
|
|
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
|
-
//
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
177
|
+
entries.push({ path: pathPattern, loaders })
|
|
180
178
|
}
|
|
181
179
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
180
|
+
// No routes found - don't create file
|
|
181
|
+
if (entries.length === 0) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
185
184
|
|
|
186
|
-
const
|
|
187
|
-
|
|
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
|
|
192
|
+
return `/**
|
|
193
|
+
* Auto-generated by effect-start.
|
|
194
|
+
*/
|
|
191
195
|
|
|
192
|
-
export
|
|
196
|
+
export default {
|
|
197
|
+
${routeEntries},
|
|
198
|
+
} satisfies import("effect-start/FileRouter").FileRoutes
|
|
193
199
|
`
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
/**
|
|
197
|
-
* Updates the
|
|
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
|
-
|
|
203
|
-
): Effect.Effect<void,
|
|
208
|
+
treePath = "server.gen.ts",
|
|
209
|
+
): Effect.Effect<void, FileRouter.FileRouterError, FileSystem.FileSystem> {
|
|
204
210
|
return Effect.gen(function*() {
|
|
205
|
-
|
|
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
|
-
|
|
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,
|
|
222
|
+
yield* validateRouteModules(routesPath, fileRoutes)
|
|
213
223
|
|
|
214
|
-
const newCode = generateCode(
|
|
224
|
+
const newCode = generateCode(fileRoutes)
|
|
215
225
|
|
|
216
|
-
// Check if file exists
|
|
226
|
+
// Check if file exists (ok to fail - means file doesn't exist)
|
|
217
227
|
const existingCode = yield* fs
|
|
218
|
-
.readFileString(
|
|
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
|
|
223
|
-
yield* fs.writeFileString(
|
|
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
|
|
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
|
-
|
|
233
|
-
): Effect.Effect<void,
|
|
264
|
+
treePath = "server.gen.ts",
|
|
265
|
+
): Effect.Effect<void, FileRouter.FileRouterError, FileSystem.FileSystem> {
|
|
234
266
|
return Effect.gen(function*() {
|
|
235
|
-
|
|
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
|
-
|
|
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,
|
|
278
|
+
yield* validateRouteModules(routesPath, fileRoutes)
|
|
243
279
|
|
|
244
|
-
const code = generateCode(
|
|
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
|
|
288
|
+
yield* Effect.logDebug(`Generating file routes tree: ${treePath}`)
|
|
247
289
|
|
|
248
|
-
yield* fs.writeFileString(
|
|
249
|
-
|
|
250
|
-
|
|
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 =>
|
|
16
|
+
export const isPlatformError = (u: unknown): u is PlatformError =>
|
|
17
|
+
Predicate.hasProperty(u, TypeId)
|
|
17
18
|
|
|
18
|
-
export const TypeIdError = <
|
|
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")(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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}${
|
|
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")(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
package/src/PlatformRuntime.ts
CHANGED
|
@@ -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>,
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
82
|
+
const composed = Function.pipe(
|
|
83
|
+
BunServer.layer(),
|
|
84
|
+
BunServer.withLogAddress,
|
|
39
85
|
Layer.provide(appLayer),
|
|
40
|
-
Layer.provide(
|
|
41
|
-
|
|
42
|
-
|
|
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
|
)
|