effect-start 0.20.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +39 -40
- package/src/Commander.ts +73 -130
- package/src/ContentNegotiation.ts +68 -100
- package/src/Cookies.ts +408 -0
- package/src/Development.ts +48 -63
- package/src/Effectify.ts +222 -206
- package/src/Entity.ts +59 -86
- package/src/FilePathPattern.ts +5 -5
- package/src/FileRouter.ts +38 -63
- package/src/FileRouterCodegen.ts +64 -56
- package/src/FileSystem.ts +390 -0
- 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 +51 -0
- 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 +50 -60
- 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 +36 -56
- 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 +81 -175
- package/src/experimental/index.ts +0 -1
- package/src/hyper/Hyper.ts +14 -33
- package/src/hyper/HyperHtml.ts +13 -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 +140 -202
- package/src/node/NodeUtils.ts +1 -3
- package/src/testing/TestLogger.ts +9 -22
- package/src/testing/index.ts +0 -1
- package/src/testing/utils.ts +30 -31
- package/src/x/cloudflare/CloudflareTunnel.ts +53 -65
- 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/HttpAppExtra.ts +0 -478
- package/src/HttpUtils.ts +0 -17
- package/src/bun/BunPlatformHttpServer.ts +0 -88
- package/src/bun/BunServerRequest.ts +0 -396
- package/src/bundler/BundleHttp.ts +0 -259
- package/src/experimental/SseHttpResponse.ts +0 -55
- package/src/middlewares/BasicAuthMiddleware.ts +0 -36
- package/src/middlewares/index.ts +0 -1
- package/src/testing/TestHttpClient.ts +0 -148
package/README.md
CHANGED
|
@@ -14,10 +14,7 @@ by checking out `examples/` directory.
|
|
|
14
14
|
It exports a layer that applies configuration and changes the behavior of the server:
|
|
15
15
|
|
|
16
16
|
```typescript
|
|
17
|
-
import {
|
|
18
|
-
FileRouter,
|
|
19
|
-
Start,
|
|
20
|
-
} from "effect-start"
|
|
17
|
+
import { FileRouter, Start } from "effect-start"
|
|
21
18
|
|
|
22
19
|
export default Start.layer(
|
|
23
20
|
FileRouter.layer({
|
package/dist/Cookies.js
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Minimal Cookies management adapted from @effect/platform
|
|
3
|
+
* We'll aim for full compatbility when it stabilizes.
|
|
4
|
+
*/
|
|
5
|
+
import * as Duration from "effect/Duration"
|
|
6
|
+
import * as Inspectable from "effect/Inspectable"
|
|
7
|
+
import * as Option from "effect/Option"
|
|
8
|
+
import * as Pipeable from "effect/Pipeable"
|
|
9
|
+
import * as Predicate from "effect/Predicate"
|
|
10
|
+
|
|
11
|
+
export const TypeId = Symbol.for(
|
|
12
|
+
"effect-start/Cookies",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
export const isCookies = (u) =>
|
|
16
|
+
Predicate.hasProperty(u, TypeId)
|
|
17
|
+
|
|
18
|
+
export const CookieTypeId = Symbol.for(
|
|
19
|
+
"effect-start/Cookies/Cookie",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const CookiesProto = {
|
|
23
|
+
[TypeId]: TypeId,
|
|
24
|
+
...Inspectable.BaseProto,
|
|
25
|
+
toJSON() {
|
|
26
|
+
return {
|
|
27
|
+
_id: "effect-start/Cookies",
|
|
28
|
+
cookies: Object.fromEntries(
|
|
29
|
+
Object.entries(this.cookies).map(([k, v]) => [k, v.toJSON()]),
|
|
30
|
+
),
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
pipe() {
|
|
34
|
+
return Pipeable.pipeArguments(this, arguments)
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const CookieProto = {
|
|
39
|
+
[CookieTypeId]: CookieTypeId,
|
|
40
|
+
...Inspectable.BaseProto,
|
|
41
|
+
toJSON() {
|
|
42
|
+
return {
|
|
43
|
+
_id: "effect-start/Cookies/Cookie",
|
|
44
|
+
name: this.name,
|
|
45
|
+
value: this.value,
|
|
46
|
+
options: this.options,
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const makeCookiesFromRecord = (
|
|
52
|
+
cookies,
|
|
53
|
+
) => {
|
|
54
|
+
const self = Object.create(CookiesProto)
|
|
55
|
+
self.cookies = cookies
|
|
56
|
+
return self
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cookieFromParts = (
|
|
60
|
+
name,
|
|
61
|
+
value,
|
|
62
|
+
valueEncoded,
|
|
63
|
+
options,
|
|
64
|
+
) =>
|
|
65
|
+
Object.assign(Object.create(CookieProto), {
|
|
66
|
+
name,
|
|
67
|
+
value,
|
|
68
|
+
valueEncoded,
|
|
69
|
+
options,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
export const empty = makeCookiesFromRecord({})
|
|
73
|
+
|
|
74
|
+
export const fromIterable = (cookies) => {
|
|
75
|
+
const record = {}
|
|
76
|
+
for (const cookie of cookies) {
|
|
77
|
+
record[cookie.name] = cookie
|
|
78
|
+
}
|
|
79
|
+
return makeCookiesFromRecord(record)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const fromSetCookie = (
|
|
83
|
+
headers,
|
|
84
|
+
) => {
|
|
85
|
+
const arrayHeaders = typeof headers === "string" ? [headers] : headers
|
|
86
|
+
const cookies = []
|
|
87
|
+
for (const header of arrayHeaders) {
|
|
88
|
+
const cookie = parseSetCookie(header.trim())
|
|
89
|
+
if (Option.isSome(cookie)) {
|
|
90
|
+
cookies.push(cookie.value)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return fromIterable(cookies)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const unsafeMakeCookie = (
|
|
97
|
+
name,
|
|
98
|
+
value,
|
|
99
|
+
options,
|
|
100
|
+
) => cookieFromParts(name, value, encodeURIComponent(value), options)
|
|
101
|
+
|
|
102
|
+
export const isEmpty = (self) => {
|
|
103
|
+
for (const _ in self.cookies) return false
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const get = (
|
|
108
|
+
self,
|
|
109
|
+
name,
|
|
110
|
+
) =>
|
|
111
|
+
name in self.cookies ? Option.some(self.cookies[name]) : Option.none()
|
|
112
|
+
|
|
113
|
+
export const getValue = (
|
|
114
|
+
self,
|
|
115
|
+
name,
|
|
116
|
+
) =>
|
|
117
|
+
Option.map(get(self, name), (cookie) => cookie.value)
|
|
118
|
+
|
|
119
|
+
export const setCookie = (self, cookie) =>
|
|
120
|
+
makeCookiesFromRecord({ ...self.cookies, [cookie.name]: cookie })
|
|
121
|
+
|
|
122
|
+
export const unsafeSet = (
|
|
123
|
+
self,
|
|
124
|
+
name,
|
|
125
|
+
value,
|
|
126
|
+
options,
|
|
127
|
+
) => setCookie(self, unsafeMakeCookie(name, value, options))
|
|
128
|
+
|
|
129
|
+
export const unsafeSetAll = (
|
|
130
|
+
self,
|
|
131
|
+
cookies,
|
|
132
|
+
) => {
|
|
133
|
+
const record = { ...self.cookies }
|
|
134
|
+
for (const [name, value, options] of cookies) {
|
|
135
|
+
record[name] = unsafeMakeCookie(name, value, options)
|
|
136
|
+
}
|
|
137
|
+
return makeCookiesFromRecord(record)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const remove = (self, name) => {
|
|
141
|
+
const { [name]: _, ...rest } = self.cookies
|
|
142
|
+
return makeCookiesFromRecord(rest)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const merge = (self, that) =>
|
|
146
|
+
makeCookiesFromRecord({ ...self.cookies, ...that.cookies })
|
|
147
|
+
|
|
148
|
+
export function serializeCookie(self) {
|
|
149
|
+
let str = self.name + "=" + self.valueEncoded
|
|
150
|
+
|
|
151
|
+
if (self.options === undefined) {
|
|
152
|
+
return str
|
|
153
|
+
}
|
|
154
|
+
const options = self.options
|
|
155
|
+
|
|
156
|
+
if (options.maxAge !== undefined) {
|
|
157
|
+
const maxAge = Duration.toSeconds(options.maxAge)
|
|
158
|
+
str += "; Max-Age=" + Math.trunc(maxAge)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (options.domain !== undefined) {
|
|
162
|
+
str += "; Domain=" + options.domain
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (options.path !== undefined) {
|
|
166
|
+
str += "; Path=" + options.path
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (options.priority !== undefined) {
|
|
170
|
+
switch (options.priority) {
|
|
171
|
+
case "low":
|
|
172
|
+
str += "; Priority=Low"
|
|
173
|
+
break
|
|
174
|
+
case "medium":
|
|
175
|
+
str += "; Priority=Medium"
|
|
176
|
+
break
|
|
177
|
+
case "high":
|
|
178
|
+
str += "; Priority=High"
|
|
179
|
+
break
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (options.expires !== undefined) {
|
|
184
|
+
str += "; Expires=" + options.expires.toUTCString()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (options.httpOnly) {
|
|
188
|
+
str += "; HttpOnly"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (options.secure) {
|
|
192
|
+
str += "; Secure"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (options.partitioned) {
|
|
196
|
+
str += "; Partitioned"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (options.sameSite !== undefined) {
|
|
200
|
+
switch (options.sameSite) {
|
|
201
|
+
case "lax":
|
|
202
|
+
str += "; SameSite=Lax"
|
|
203
|
+
break
|
|
204
|
+
case "strict":
|
|
205
|
+
str += "; SameSite=Strict"
|
|
206
|
+
break
|
|
207
|
+
case "none":
|
|
208
|
+
str += "; SameSite=None"
|
|
209
|
+
break
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return str
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export const toCookieHeader = (self) =>
|
|
217
|
+
Object
|
|
218
|
+
.values(self.cookies)
|
|
219
|
+
.map((cookie) => `${cookie.name}=${cookie.valueEncoded}`)
|
|
220
|
+
.join("; ")
|
|
221
|
+
|
|
222
|
+
export const toRecord = (self) => {
|
|
223
|
+
const record = {}
|
|
224
|
+
for (const cookie of Object.values(self.cookies)) {
|
|
225
|
+
record[cookie.name] = cookie.value
|
|
226
|
+
}
|
|
227
|
+
return record
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export const toSetCookieHeaders = (self) =>
|
|
231
|
+
Object.values(self.cookies).map(serializeCookie)
|
|
232
|
+
|
|
233
|
+
export function parseHeader(header) {
|
|
234
|
+
const result = {}
|
|
235
|
+
|
|
236
|
+
const strLen = header.length
|
|
237
|
+
let pos = 0
|
|
238
|
+
let terminatorPos = 0
|
|
239
|
+
|
|
240
|
+
while (true) {
|
|
241
|
+
if (terminatorPos === strLen) break
|
|
242
|
+
terminatorPos = header.indexOf(";", pos)
|
|
243
|
+
if (terminatorPos === -1) terminatorPos = strLen
|
|
244
|
+
|
|
245
|
+
let eqIdx = header.indexOf("=", pos)
|
|
246
|
+
if (eqIdx === -1) break
|
|
247
|
+
if (eqIdx > terminatorPos) {
|
|
248
|
+
pos = terminatorPos + 1
|
|
249
|
+
continue
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const key = header.substring(pos, eqIdx++).trim()
|
|
253
|
+
if (result[key] === undefined) {
|
|
254
|
+
const val = header.charCodeAt(eqIdx) === 0x22
|
|
255
|
+
? header.substring(eqIdx + 1, terminatorPos - 1).trim()
|
|
256
|
+
: header.substring(eqIdx, terminatorPos).trim()
|
|
257
|
+
|
|
258
|
+
result[key] = !(val.indexOf("%") === -1)
|
|
259
|
+
? tryDecodeURIComponent(val)
|
|
260
|
+
: val
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
pos = terminatorPos + 1
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return result
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// eslint-disable-next-line no-control-regex
|
|
270
|
+
const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/
|
|
271
|
+
|
|
272
|
+
function parseSetCookie(header) {
|
|
273
|
+
const parts = header
|
|
274
|
+
.split(";")
|
|
275
|
+
.map((_) => _.trim())
|
|
276
|
+
.filter((_) => _ !== "")
|
|
277
|
+
if (parts.length === 0) {
|
|
278
|
+
return Option.none()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const firstEqual = parts[0].indexOf("=")
|
|
282
|
+
if (firstEqual === -1) {
|
|
283
|
+
return Option.none()
|
|
284
|
+
}
|
|
285
|
+
const name = parts[0].slice(0, firstEqual)
|
|
286
|
+
if (!fieldContentRegExp.test(name)) {
|
|
287
|
+
return Option.none()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const valueEncoded = parts[0].slice(firstEqual + 1)
|
|
291
|
+
const value = tryDecodeURIComponent(valueEncoded)
|
|
292
|
+
|
|
293
|
+
if (parts.length === 1) {
|
|
294
|
+
return Option.some(cookieFromParts(name, value, valueEncoded))
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const options = {}
|
|
298
|
+
|
|
299
|
+
for (let i = 1; i < parts.length; i++) {
|
|
300
|
+
const part = parts[i]
|
|
301
|
+
const equalIndex = part.indexOf("=")
|
|
302
|
+
const key = equalIndex === -1 ? part : part.slice(0, equalIndex).trim()
|
|
303
|
+
const value = equalIndex === -1
|
|
304
|
+
? undefined
|
|
305
|
+
: part.slice(equalIndex + 1).trim()
|
|
306
|
+
|
|
307
|
+
switch (key.toLowerCase()) {
|
|
308
|
+
case "domain": {
|
|
309
|
+
if (value === undefined) break
|
|
310
|
+
const domain = value.trim().replace(/^\./, "")
|
|
311
|
+
if (domain) options.domain = domain
|
|
312
|
+
break
|
|
313
|
+
}
|
|
314
|
+
case "expires": {
|
|
315
|
+
if (value === undefined) break
|
|
316
|
+
const date = new Date(value)
|
|
317
|
+
if (!isNaN(date.getTime())) options.expires = date
|
|
318
|
+
break
|
|
319
|
+
}
|
|
320
|
+
case "max-age": {
|
|
321
|
+
if (value === undefined) break
|
|
322
|
+
const maxAge = parseInt(value, 10)
|
|
323
|
+
if (!isNaN(maxAge)) options.maxAge = Duration.seconds(maxAge)
|
|
324
|
+
break
|
|
325
|
+
}
|
|
326
|
+
case "path": {
|
|
327
|
+
if (value === undefined) break
|
|
328
|
+
if (value[0] === "/") options.path = value
|
|
329
|
+
break
|
|
330
|
+
}
|
|
331
|
+
case "priority": {
|
|
332
|
+
if (value === undefined) break
|
|
333
|
+
switch (value.toLowerCase()) {
|
|
334
|
+
case "low":
|
|
335
|
+
options.priority = "low"
|
|
336
|
+
break
|
|
337
|
+
case "medium":
|
|
338
|
+
options.priority = "medium"
|
|
339
|
+
break
|
|
340
|
+
case "high":
|
|
341
|
+
options.priority = "high"
|
|
342
|
+
break
|
|
343
|
+
}
|
|
344
|
+
break
|
|
345
|
+
}
|
|
346
|
+
case "httponly": {
|
|
347
|
+
options.httpOnly = true
|
|
348
|
+
break
|
|
349
|
+
}
|
|
350
|
+
case "secure": {
|
|
351
|
+
options.secure = true
|
|
352
|
+
break
|
|
353
|
+
}
|
|
354
|
+
case "partitioned": {
|
|
355
|
+
options.partitioned = true
|
|
356
|
+
break
|
|
357
|
+
}
|
|
358
|
+
case "samesite": {
|
|
359
|
+
if (value === undefined) break
|
|
360
|
+
switch (value.toLowerCase()) {
|
|
361
|
+
case "lax":
|
|
362
|
+
options.sameSite = "lax"
|
|
363
|
+
break
|
|
364
|
+
case "strict":
|
|
365
|
+
options.sameSite = "strict"
|
|
366
|
+
break
|
|
367
|
+
case "none":
|
|
368
|
+
options.sameSite = "none"
|
|
369
|
+
break
|
|
370
|
+
}
|
|
371
|
+
break
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return Option.some(
|
|
377
|
+
cookieFromParts(
|
|
378
|
+
name,
|
|
379
|
+
value,
|
|
380
|
+
valueEncoded,
|
|
381
|
+
Object.keys(options).length > 0 ? options : undefined,
|
|
382
|
+
),
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const tryDecodeURIComponent = (str) => {
|
|
387
|
+
try {
|
|
388
|
+
return decodeURIComponent(str)
|
|
389
|
+
} catch {
|
|
390
|
+
return str
|
|
391
|
+
}
|
|
392
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Adapted from @effect/platform
|
|
3
|
+
*/
|
|
4
|
+
import * as Brand from "effect/Brand"
|
|
5
|
+
import * as Channel from "effect/Channel"
|
|
6
|
+
import * as Chunk from "effect/Chunk"
|
|
7
|
+
import * as Context from "effect/Context"
|
|
8
|
+
import * as Data from "effect/Data"
|
|
9
|
+
import * as Effect from "effect/Effect"
|
|
10
|
+
import * as Function from "effect/Function"
|
|
11
|
+
import * as Option from "effect/Option"
|
|
12
|
+
import * as Sink from "effect/Sink"
|
|
13
|
+
import * as Stream from "effect/Stream"
|
|
14
|
+
import * as PlatformError from "./PlatformError.js"
|
|
15
|
+
|
|
16
|
+
export const FileSystem = Context.GenericTag(
|
|
17
|
+
"@effect/platform/FileSystem",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
export const Size = (bytes) =>
|
|
21
|
+
typeof bytes === "bigint" ? bytes : BigInt(bytes)
|
|
22
|
+
|
|
23
|
+
export const FileTypeId = Symbol.for("@effect/platform/FileSystem/File")
|
|
24
|
+
|
|
25
|
+
export const FileDescriptor = Brand.nominal()
|
|
26
|
+
|
|
27
|
+
export const WatchEventCreate = Data.tagged(
|
|
28
|
+
"Create",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export const WatchEventUpdate = Data.tagged(
|
|
32
|
+
"Update",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
export const WatchEventRemove = Data.tagged(
|
|
36
|
+
"Remove",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
export class WatchBackend extends Context.Tag("@effect/platform/FileSystem/WatchBackend")() {}
|
|
40
|
+
|
|
41
|
+
export const make = (
|
|
42
|
+
impl,
|
|
43
|
+
) => {
|
|
44
|
+
return FileSystem.of({
|
|
45
|
+
...impl,
|
|
46
|
+
exists: (path) =>
|
|
47
|
+
Function.pipe(
|
|
48
|
+
impl.access(path),
|
|
49
|
+
Effect.as(true),
|
|
50
|
+
Effect.catchTag("SystemError", (e) => e.reason === "NotFound" ? Effect.succeed(false) : Effect.fail(e)),
|
|
51
|
+
),
|
|
52
|
+
readFileString: (path, encoding) =>
|
|
53
|
+
Effect.tryMap(impl.readFile(path), {
|
|
54
|
+
try: (_) => new TextDecoder(encoding).decode(_),
|
|
55
|
+
catch: (cause) =>
|
|
56
|
+
new PlatformError.BadArgument({
|
|
57
|
+
module: "FileSystem",
|
|
58
|
+
method: "readFileString",
|
|
59
|
+
description: "invalid encoding",
|
|
60
|
+
cause,
|
|
61
|
+
}),
|
|
62
|
+
}),
|
|
63
|
+
stream: (path, options) =>
|
|
64
|
+
Function.pipe(
|
|
65
|
+
impl.open(path, { flag: "r" }),
|
|
66
|
+
options?.offset
|
|
67
|
+
? Effect.tap((file) => file.seek(options.offset, "start"))
|
|
68
|
+
: Function.identity,
|
|
69
|
+
Effect.map((file) => fileStream(file, options)),
|
|
70
|
+
Stream.unwrapScoped,
|
|
71
|
+
),
|
|
72
|
+
sink: (path, options) =>
|
|
73
|
+
Function.pipe(
|
|
74
|
+
impl.open(path, { flag: "w", ...options }),
|
|
75
|
+
Effect.map((file) => Sink.forEach((_) => file.writeAll(_))),
|
|
76
|
+
Sink.unwrapScoped,
|
|
77
|
+
),
|
|
78
|
+
writeFileString: (path, data, options) =>
|
|
79
|
+
Effect.flatMap(
|
|
80
|
+
Effect.try({
|
|
81
|
+
try: () => new TextEncoder().encode(data),
|
|
82
|
+
catch: (cause) =>
|
|
83
|
+
new PlatformError.BadArgument({
|
|
84
|
+
module: "FileSystem",
|
|
85
|
+
method: "writeFileString",
|
|
86
|
+
description: "could not encode string",
|
|
87
|
+
cause,
|
|
88
|
+
}),
|
|
89
|
+
}),
|
|
90
|
+
(_) => impl.writeFile(path, _, options),
|
|
91
|
+
),
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const fileStream = (file, {
|
|
96
|
+
bufferSize = 16,
|
|
97
|
+
bytesToRead: bytesToRead_,
|
|
98
|
+
chunkSize: chunkSize_ = Size(64 * 1024),
|
|
99
|
+
} = {}) => {
|
|
100
|
+
const bytesToRead = bytesToRead_ !== undefined ? Size(bytesToRead_) : undefined
|
|
101
|
+
const chunkSize = Size(chunkSize_)
|
|
102
|
+
|
|
103
|
+
function loop(
|
|
104
|
+
totalBytesRead,
|
|
105
|
+
) {
|
|
106
|
+
if (bytesToRead !== undefined && bytesToRead <= totalBytesRead) {
|
|
107
|
+
return Channel.void
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const toRead = bytesToRead !== undefined && (bytesToRead - totalBytesRead) < chunkSize
|
|
111
|
+
? bytesToRead - totalBytesRead
|
|
112
|
+
: chunkSize
|
|
113
|
+
|
|
114
|
+
return Channel.flatMap(
|
|
115
|
+
file.readAlloc(toRead),
|
|
116
|
+
Option.match({
|
|
117
|
+
onNone: () => Channel.void,
|
|
118
|
+
onSome: (buf) =>
|
|
119
|
+
Channel.flatMap(
|
|
120
|
+
Channel.write(Chunk.of(buf)),
|
|
121
|
+
() => loop(totalBytesRead + BigInt(buf.length)),
|
|
122
|
+
),
|
|
123
|
+
}),
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return Stream.bufferChunks(
|
|
128
|
+
Stream.fromChannel(loop(BigInt(0))),
|
|
129
|
+
{ capacity: bufferSize },
|
|
130
|
+
)
|
|
131
|
+
}
|
package/dist/Socket.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Adapted from @effect/platform
|
|
3
|
+
*/
|
|
4
|
+
import * as Predicate from "effect/Predicate"
|
|
5
|
+
import * as PlatformError from "./PlatformError.js"
|
|
6
|
+
|
|
7
|
+
export const SocketErrorTypeId = Symbol.for("@effect/platform/Socket/SocketError")
|
|
8
|
+
|
|
9
|
+
export const isSocketError = (u) =>
|
|
10
|
+
Predicate.hasProperty(u, SocketErrorTypeId)
|
|
11
|
+
|
|
12
|
+
export class SocketGenericError extends PlatformError.TypeIdError(SocketErrorTypeId, "SocketError") {
|
|
13
|
+
get message() {
|
|
14
|
+
return `An error occurred during ${this.reason}`
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class SocketCloseError extends PlatformError.TypeIdError(SocketErrorTypeId, "SocketError") {
|
|
19
|
+
static is(u) {
|
|
20
|
+
return isSocketError(u) && u.reason === "Close"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static isClean(isClean) {
|
|
24
|
+
return function(u) {
|
|
25
|
+
return SocketCloseError.is(u) && isClean(u.code)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get message() {
|
|
30
|
+
if (this.closeReason) {
|
|
31
|
+
return `${this.reason}: ${this.code}: ${this.closeReason}`
|
|
32
|
+
}
|
|
33
|
+
return `${this.reason}: ${this.code}`
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const defaultCloseCodeIsError = (code) => code !== 1000 && code !== 1006
|