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.
- package/README.md +1 -4
- package/dist/Cookies.js +392 -0
- package/dist/FileSystem.js +131 -0
- package/dist/Socket.js +37 -0
- package/package.json +34 -36
- package/src/Commander.ts +73 -130
- package/src/ContentNegotiation.ts +64 -95
- package/src/Cookies.ts +36 -57
- package/src/Development.ts +47 -62
- package/src/Effectify.ts +222 -206
- package/src/Entity.ts +59 -86
- package/src/FilePathPattern.ts +5 -5
- package/src/FileRouter.ts +37 -62
- package/src/FileRouterCodegen.ts +63 -55
- package/src/FileSystem.ts +46 -59
- package/src/Http.ts +17 -50
- package/src/PathPattern.ts +33 -41
- package/src/PlatformError.ts +29 -50
- package/src/PlatformRuntime.ts +39 -47
- package/src/Route.ts +68 -187
- package/src/RouteBody.ts +45 -161
- package/src/RouteHook.ts +22 -45
- package/src/RouteHttp.ts +88 -142
- package/src/RouteHttpTracer.ts +25 -26
- package/src/RouteMount.ts +100 -238
- package/src/RouteSchema.ts +67 -201
- package/src/RouteSse.ts +28 -82
- package/src/RouteTree.ts +31 -79
- package/src/RouteTrie.ts +13 -32
- package/src/SchemaExtra.ts +3 -5
- package/src/Socket.ts +5 -2
- package/src/Start.ts +20 -21
- package/src/StreamExtra.ts +93 -96
- package/src/TuplePathPattern.ts +54 -43
- package/src/Unique.ts +9 -15
- package/src/Values.ts +26 -30
- package/src/bun/BunBundle.ts +27 -73
- package/src/bun/BunImportTrackerPlugin.ts +67 -65
- package/src/bun/BunRoute.ts +12 -31
- package/src/bun/BunRuntime.ts +3 -10
- package/src/bun/BunServer.ts +55 -91
- package/src/bun/BunVirtualFilesPlugin.ts +1 -4
- package/src/bun/_BunEnhancedResolve.ts +17 -42
- package/src/bun/_empty.html +0 -1
- package/src/bundler/Bundle.ts +20 -36
- package/src/bundler/BundleFiles.ts +35 -55
- package/src/client/Overlay.ts +1 -2
- package/src/client/ScrollState.ts +5 -9
- package/src/client/index.ts +10 -13
- package/src/datastar/actions/fetch.ts +29 -48
- package/src/datastar/actions/peek.ts +1 -5
- package/src/datastar/actions/setAll.ts +2 -2
- package/src/datastar/actions/toggleAll.ts +2 -2
- package/src/datastar/attributes/attr.ts +17 -18
- package/src/datastar/attributes/bind.ts +41 -61
- package/src/datastar/attributes/class.ts +2 -5
- package/src/datastar/attributes/computed.ts +2 -10
- package/src/datastar/attributes/effect.ts +1 -2
- package/src/datastar/attributes/indicator.ts +2 -8
- package/src/datastar/attributes/init.ts +2 -10
- package/src/datastar/attributes/jsonSignals.ts +1 -6
- package/src/datastar/attributes/on.ts +4 -13
- package/src/datastar/attributes/onIntersect.ts +10 -22
- package/src/datastar/attributes/onInterval.ts +2 -10
- package/src/datastar/attributes/onSignalPatch.ts +18 -28
- package/src/datastar/attributes/ref.ts +1 -2
- package/src/datastar/attributes/show.ts +1 -2
- package/src/datastar/attributes/signals.ts +1 -5
- package/src/datastar/attributes/style.ts +6 -12
- package/src/datastar/attributes/text.ts +1 -2
- package/src/datastar/engine.ts +102 -158
- package/src/datastar/index.ts +2 -2
- package/src/datastar/utils.ts +16 -51
- package/src/datastar/watchers/patchElements.ts +35 -93
- package/src/datastar/watchers/patchSignals.ts +1 -2
- package/src/experimental/EncryptedCookies.ts +79 -142
- package/src/hyper/Hyper.ts +14 -33
- package/src/hyper/HyperHtml.ts +9 -10
- package/src/hyper/HyperNode.ts +2 -7
- package/src/hyper/HyperRoute.ts +2 -5
- package/src/hyper/jsx-runtime.ts +2 -10
- package/src/hyper/jsx.d.ts +171 -440
- package/src/lint/plugin.js +276 -0
- package/src/node/NodeFileSystem.ts +138 -186
- package/src/node/NodeUtils.ts +1 -3
- package/src/testing/TestLogger.ts +9 -22
- package/src/testing/utils.ts +30 -31
- package/src/x/cloudflare/CloudflareTunnel.ts +37 -54
- package/src/x/datastar/Datastar.ts +3 -10
- package/src/x/datastar/index.ts +1 -3
- package/src/x/datastar/jsx-datastar.d.ts +1 -4
- package/src/x/tailwind/TailwindPlugin.ts +119 -112
- package/src/x/tailwind/compile.ts +10 -33
- package/src/x/tailwind/plugin.ts +2 -2
package/src/FileRouterCodegen.ts
CHANGED
|
@@ -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
|
|
65
|
-
|
|
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
|
-
|
|
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<
|
|
113
|
-
|
|
114
|
-
|
|
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(
|
|
216
|
-
|
|
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(
|
|
240
|
-
|
|
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(
|
|
253
|
-
|
|
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(
|
|
272
|
-
|
|
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(
|
|
292
|
-
|
|
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
|
-
|
|
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: (
|
|
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"> =
|
|
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"> =
|
|
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"> =
|
|
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) =>
|
|
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 = (
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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<
|
|
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 =
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
401
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
&
|
|
27
|
-
|
|
28
|
-
|
|
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())
|
package/src/PathPattern.ts
CHANGED
|
@@ -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}`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
}
|