effect-start 0.20.1 → 0.21.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-start",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "exports": {
@@ -34,11 +34,6 @@
34
34
  "types": "./dist/x/tailwind/plugin.d.ts",
35
35
  "default": "./dist/x/tailwind/plugin.js"
36
36
  },
37
- "./middlewares": {
38
- "bun": "./src/middlewares/index.ts",
39
- "types": "./dist/middlewares/index.d.ts",
40
- "default": "./dist/middlewares/index.js"
41
- },
42
37
  "./Unique": {
43
38
  "bun": "./src/Unique.ts",
44
39
  "types": "./dist/Unique.d.ts",
@@ -49,6 +44,11 @@
49
44
  "types": "./dist/FileRouter.d.ts",
50
45
  "default": "./dist/FileRouter.js"
51
46
  },
47
+ "./FileSystem": {
48
+ "bun": "./src/FileSystem.ts",
49
+ "types": "./dist/FileSystem.d.ts",
50
+ "default": "./dist/FileSystem.js"
51
+ },
52
52
  "./experimental": {
53
53
  "bun": "./src/experimental/index.ts",
54
54
  "types": "./dist/experimental/index.d.ts",
@@ -3,7 +3,6 @@
3
3
  * Based on {@link https://github.com/jshttp/negotiator}
4
4
  */
5
5
 
6
- import type * as Headers from "@effect/platform/Headers"
7
6
 
8
7
  interface ParsedSpec {
9
8
  value: string
@@ -506,37 +505,37 @@ export function charset(accept: string, available?: string[]): string[] {
506
505
  }
507
506
 
508
507
  export function headerMedia(
509
- headers: Headers.Headers,
508
+ headers: Headers,
510
509
  available?: string[],
511
510
  ): string[] {
512
- const accept = headers["accept"]
511
+ const accept = headers.get("accept")
513
512
  if (!accept) return []
514
513
  return media(accept, available)
515
514
  }
516
515
 
517
516
  export function headerLanguage(
518
- headers: Headers.Headers,
517
+ headers: Headers,
519
518
  available?: string[],
520
519
  ): string[] {
521
- const accept = headers["accept-language"]
520
+ const accept = headers.get("accept-language")
522
521
  if (!accept) return []
523
522
  return language(accept, available)
524
523
  }
525
524
 
526
525
  export function headerEncoding(
527
- headers: Headers.Headers,
526
+ headers: Headers,
528
527
  available?: string[],
529
528
  ): string[] {
530
- const accept = headers["accept-encoding"]
529
+ const accept = headers.get("accept-encoding")
531
530
  if (!accept) return []
532
531
  return encoding(accept, available)
533
532
  }
534
533
 
535
534
  export function headerCharset(
536
- headers: Headers.Headers,
535
+ headers: Headers,
537
536
  available?: string[],
538
537
  ): string[] {
539
- const accept = headers["accept-charset"]
538
+ const accept = headers.get("accept-charset")
540
539
  if (!accept) return []
541
540
  return charset(accept, available)
542
541
  }
package/src/Cookies.ts ADDED
@@ -0,0 +1,429 @@
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
+ import type * as Types from "effect/Types"
11
+
12
+ export const TypeId: unique symbol = Symbol.for(
13
+ "effect-start/Cookies",
14
+ )
15
+
16
+ export type TypeId = typeof TypeId
17
+
18
+ export const isCookies = (u: unknown): u is Cookies =>
19
+ Predicate.hasProperty(u, TypeId)
20
+
21
+ export interface Cookies extends Pipeable.Pipeable, Inspectable.Inspectable {
22
+ readonly [TypeId]: TypeId
23
+ readonly cookies: Record<string, Cookie>
24
+ }
25
+
26
+ export const CookieTypeId: unique symbol = Symbol.for(
27
+ "effect-start/Cookies/Cookie",
28
+ )
29
+
30
+ export type CookieTypeId = typeof CookieTypeId
31
+
32
+ export interface Cookie extends Inspectable.Inspectable {
33
+ readonly [CookieTypeId]: CookieTypeId
34
+ readonly name: string
35
+ readonly value: string
36
+ readonly valueEncoded: string
37
+ readonly options?: {
38
+ readonly domain?: string | undefined
39
+ readonly expires?: Date | undefined
40
+ readonly maxAge?: Duration.DurationInput | undefined
41
+ readonly path?: string | undefined
42
+ readonly priority?: "low" | "medium" | "high" | undefined
43
+ readonly httpOnly?: boolean | undefined
44
+ readonly secure?: boolean | undefined
45
+ readonly partitioned?: boolean | undefined
46
+ readonly sameSite?:
47
+ // send with top-level navigations and GET requests from third-party sites
48
+ | "lax"
49
+ // only send with same-site requests
50
+ | "strict"
51
+ // send with all requests (requires Secure)
52
+ | "none"
53
+ | undefined
54
+ } | undefined
55
+ }
56
+
57
+ const CookiesProto: Omit<Cookies, "cookies"> = {
58
+ [TypeId]: TypeId,
59
+ ...Inspectable.BaseProto,
60
+ toJSON(this: Cookies) {
61
+ return {
62
+ _id: "effect-start/Cookies",
63
+ cookies: Object.fromEntries(
64
+ Object.entries(this.cookies).map(([k, v]) => [k, v.toJSON()]),
65
+ ),
66
+ }
67
+ },
68
+ pipe() {
69
+ return Pipeable.pipeArguments(this, arguments)
70
+ },
71
+ }
72
+
73
+ const CookieProto = {
74
+ [CookieTypeId]: CookieTypeId,
75
+ ...Inspectable.BaseProto,
76
+ toJSON(this: Cookie) {
77
+ return {
78
+ _id: "effect-start/Cookies/Cookie",
79
+ name: this.name,
80
+ value: this.value,
81
+ options: this.options,
82
+ }
83
+ },
84
+ }
85
+
86
+ const makeCookiesFromRecord = (
87
+ cookies: Record<string, Cookie>,
88
+ ): Cookies => {
89
+ const self = Object.create(CookiesProto)
90
+ self.cookies = cookies
91
+ return self
92
+ }
93
+
94
+ const cookieFromParts = (
95
+ name: string,
96
+ value: string,
97
+ valueEncoded: string,
98
+ options?: Cookie["options"],
99
+ ): Cookie =>
100
+ Object.assign(Object.create(CookieProto), {
101
+ name,
102
+ value,
103
+ valueEncoded,
104
+ options,
105
+ })
106
+
107
+ export const empty: Cookies = makeCookiesFromRecord({})
108
+
109
+ export const fromIterable = (cookies: Iterable<Cookie>): Cookies => {
110
+ const record: Record<string, Cookie> = {}
111
+ for (const cookie of cookies) {
112
+ record[cookie.name] = cookie
113
+ }
114
+ return makeCookiesFromRecord(record)
115
+ }
116
+
117
+ export const fromSetCookie = (
118
+ headers: Iterable<string> | string,
119
+ ): Cookies => {
120
+ const arrayHeaders = typeof headers === "string" ? [headers] : headers
121
+ const cookies: Array<Cookie> = []
122
+ for (const header of arrayHeaders) {
123
+ const cookie = parseSetCookie(header.trim())
124
+ if (Option.isSome(cookie)) {
125
+ cookies.push(cookie.value)
126
+ }
127
+ }
128
+ return fromIterable(cookies)
129
+ }
130
+
131
+ export const unsafeMakeCookie = (
132
+ name: string,
133
+ value: string,
134
+ options?: Cookie["options"] | undefined,
135
+ ): Cookie => cookieFromParts(name, value, encodeURIComponent(value), options)
136
+
137
+ export const isEmpty = (self: Cookies): boolean => {
138
+ for (const _ in self.cookies) return false
139
+ return true
140
+ }
141
+
142
+ export const get = (
143
+ self: Cookies,
144
+ name: string,
145
+ ): Option.Option<Cookie> =>
146
+ name in self.cookies ? Option.some(self.cookies[name]) : Option.none()
147
+
148
+ export const getValue = (
149
+ self: Cookies,
150
+ name: string,
151
+ ): Option.Option<string> =>
152
+ Option.map(get(self, name), (cookie) => cookie.value)
153
+
154
+ export const setCookie = (self: Cookies, cookie: Cookie): Cookies =>
155
+ makeCookiesFromRecord({ ...self.cookies, [cookie.name]: cookie })
156
+
157
+ export const unsafeSet = (
158
+ self: Cookies,
159
+ name: string,
160
+ value: string,
161
+ options?: Cookie["options"],
162
+ ): Cookies => setCookie(self, unsafeMakeCookie(name, value, options))
163
+
164
+ export const unsafeSetAll = (
165
+ self: Cookies,
166
+ cookies: Iterable<
167
+ readonly [name: string, value: string, options?: Cookie["options"]]
168
+ >,
169
+ ): Cookies => {
170
+ const record: Record<string, Cookie> = { ...self.cookies }
171
+ for (const [name, value, options] of cookies) {
172
+ record[name] = unsafeMakeCookie(name, value, options)
173
+ }
174
+ return makeCookiesFromRecord(record)
175
+ }
176
+
177
+ export const remove = (self: Cookies, name: string): Cookies => {
178
+ const { [name]: _, ...rest } = self.cookies
179
+ return makeCookiesFromRecord(rest)
180
+ }
181
+
182
+ export const merge = (self: Cookies, that: Cookies): Cookies =>
183
+ makeCookiesFromRecord({ ...self.cookies, ...that.cookies })
184
+
185
+ export function serializeCookie(self: Cookie): string {
186
+ let str = self.name + "=" + self.valueEncoded
187
+
188
+ if (self.options === undefined) {
189
+ return str
190
+ }
191
+ const options = self.options
192
+
193
+ if (options.maxAge !== undefined) {
194
+ const maxAge = Duration.toSeconds(options.maxAge)
195
+ str += "; Max-Age=" + Math.trunc(maxAge)
196
+ }
197
+
198
+ if (options.domain !== undefined) {
199
+ str += "; Domain=" + options.domain
200
+ }
201
+
202
+ if (options.path !== undefined) {
203
+ str += "; Path=" + options.path
204
+ }
205
+
206
+ if (options.priority !== undefined) {
207
+ switch (options.priority) {
208
+ case "low":
209
+ str += "; Priority=Low"
210
+ break
211
+ case "medium":
212
+ str += "; Priority=Medium"
213
+ break
214
+ case "high":
215
+ str += "; Priority=High"
216
+ break
217
+ }
218
+ }
219
+
220
+ if (options.expires !== undefined) {
221
+ str += "; Expires=" + options.expires.toUTCString()
222
+ }
223
+
224
+ if (options.httpOnly) {
225
+ str += "; HttpOnly"
226
+ }
227
+
228
+ if (options.secure) {
229
+ str += "; Secure"
230
+ }
231
+
232
+ if (options.partitioned) {
233
+ str += "; Partitioned"
234
+ }
235
+
236
+ if (options.sameSite !== undefined) {
237
+ switch (options.sameSite) {
238
+ case "lax":
239
+ str += "; SameSite=Lax"
240
+ break
241
+ case "strict":
242
+ str += "; SameSite=Strict"
243
+ break
244
+ case "none":
245
+ str += "; SameSite=None"
246
+ break
247
+ }
248
+ }
249
+
250
+ return str
251
+ }
252
+
253
+ export const toCookieHeader = (self: Cookies): string =>
254
+ Object
255
+ .values(self.cookies)
256
+ .map((cookie) => `${cookie.name}=${cookie.valueEncoded}`)
257
+ .join("; ")
258
+
259
+ export const toRecord = (self: Cookies): Record<string, string> => {
260
+ const record: Record<string, string> = {}
261
+ for (const cookie of Object.values(self.cookies)) {
262
+ record[cookie.name] = cookie.value
263
+ }
264
+ return record
265
+ }
266
+
267
+ export const toSetCookieHeaders = (self: Cookies): Array<string> =>
268
+ Object.values(self.cookies).map(serializeCookie)
269
+
270
+ export function parseHeader(header: string): Record<string, string> {
271
+ const result: Record<string, string> = {}
272
+
273
+ const strLen = header.length
274
+ let pos = 0
275
+ let terminatorPos = 0
276
+
277
+ while (true) {
278
+ if (terminatorPos === strLen) break
279
+ terminatorPos = header.indexOf(";", pos)
280
+ if (terminatorPos === -1) terminatorPos = strLen
281
+
282
+ let eqIdx = header.indexOf("=", pos)
283
+ if (eqIdx === -1) break
284
+ if (eqIdx > terminatorPos) {
285
+ pos = terminatorPos + 1
286
+ continue
287
+ }
288
+
289
+ const key = header.substring(pos, eqIdx++).trim()
290
+ if (result[key] === undefined) {
291
+ const val = header.charCodeAt(eqIdx) === 0x22
292
+ ? header.substring(eqIdx + 1, terminatorPos - 1).trim()
293
+ : header.substring(eqIdx, terminatorPos).trim()
294
+
295
+ result[key] = !(val.indexOf("%") === -1)
296
+ ? tryDecodeURIComponent(val)
297
+ : val
298
+ }
299
+
300
+ pos = terminatorPos + 1
301
+ }
302
+
303
+ return result
304
+ }
305
+
306
+ // eslint-disable-next-line no-control-regex
307
+ const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/
308
+
309
+ function parseSetCookie(header: string): Option.Option<Cookie> {
310
+ const parts = header
311
+ .split(";")
312
+ .map((_) => _.trim())
313
+ .filter((_) => _ !== "")
314
+ if (parts.length === 0) {
315
+ return Option.none()
316
+ }
317
+
318
+ const firstEqual = parts[0].indexOf("=")
319
+ if (firstEqual === -1) {
320
+ return Option.none()
321
+ }
322
+ const name = parts[0].slice(0, firstEqual)
323
+ if (!fieldContentRegExp.test(name)) {
324
+ return Option.none()
325
+ }
326
+
327
+ const valueEncoded = parts[0].slice(firstEqual + 1)
328
+ const value = tryDecodeURIComponent(valueEncoded)
329
+
330
+ if (parts.length === 1) {
331
+ return Option.some(cookieFromParts(name, value, valueEncoded))
332
+ }
333
+
334
+ const options: Types.Mutable<Cookie["options"]> = {}
335
+
336
+ for (let i = 1; i < parts.length; i++) {
337
+ const part = parts[i]
338
+ const equalIndex = part.indexOf("=")
339
+ const key = equalIndex === -1 ? part : part.slice(0, equalIndex).trim()
340
+ const value = equalIndex === -1
341
+ ? undefined
342
+ : part.slice(equalIndex + 1).trim()
343
+
344
+ switch (key.toLowerCase()) {
345
+ case "domain": {
346
+ if (value === undefined) break
347
+ const domain = value.trim().replace(/^\./, "")
348
+ if (domain) options.domain = domain
349
+ break
350
+ }
351
+ case "expires": {
352
+ if (value === undefined) break
353
+ const date = new Date(value)
354
+ if (!isNaN(date.getTime())) options.expires = date
355
+ break
356
+ }
357
+ case "max-age": {
358
+ if (value === undefined) break
359
+ const maxAge = parseInt(value, 10)
360
+ if (!isNaN(maxAge)) options.maxAge = Duration.seconds(maxAge)
361
+ break
362
+ }
363
+ case "path": {
364
+ if (value === undefined) break
365
+ if (value[0] === "/") options.path = value
366
+ break
367
+ }
368
+ case "priority": {
369
+ if (value === undefined) break
370
+ switch (value.toLowerCase()) {
371
+ case "low":
372
+ options.priority = "low"
373
+ break
374
+ case "medium":
375
+ options.priority = "medium"
376
+ break
377
+ case "high":
378
+ options.priority = "high"
379
+ break
380
+ }
381
+ break
382
+ }
383
+ case "httponly": {
384
+ options.httpOnly = true
385
+ break
386
+ }
387
+ case "secure": {
388
+ options.secure = true
389
+ break
390
+ }
391
+ case "partitioned": {
392
+ options.partitioned = true
393
+ break
394
+ }
395
+ case "samesite": {
396
+ if (value === undefined) break
397
+ switch (value.toLowerCase()) {
398
+ case "lax":
399
+ options.sameSite = "lax"
400
+ break
401
+ case "strict":
402
+ options.sameSite = "strict"
403
+ break
404
+ case "none":
405
+ options.sameSite = "none"
406
+ break
407
+ }
408
+ break
409
+ }
410
+ }
411
+ }
412
+
413
+ return Option.some(
414
+ cookieFromParts(
415
+ name,
416
+ value,
417
+ valueEncoded,
418
+ Object.keys(options).length > 0 ? options : undefined,
419
+ ),
420
+ )
421
+ }
422
+
423
+ const tryDecodeURIComponent = (str: string): string => {
424
+ try {
425
+ return decodeURIComponent(str)
426
+ } catch {
427
+ return str
428
+ }
429
+ }
@@ -1,4 +1,4 @@
1
- import * as FileSystem from "@effect/platform/FileSystem"
1
+ import * as FileSystem from "./FileSystem.ts"
2
2
  import * as Context from "effect/Context"
3
3
  import * as Effect from "effect/Effect"
4
4
  import * as Function from "effect/Function"
package/src/FileRouter.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as FileSystem from "@effect/platform/FileSystem"
1
+ import * as FileSystem from "./FileSystem.ts"
2
2
  import * as Data from "effect/Data"
3
3
  import * as Effect from "effect/Effect"
4
4
  import * as Either from "effect/Either"
@@ -1,4 +1,4 @@
1
- import * as FileSystem from "@effect/platform/FileSystem"
1
+ import * as FileSystem from "./FileSystem.ts"
2
2
  import * as Effect from "effect/Effect"
3
3
  import * as Either from "effect/Either"
4
4
  import * as Schema from "effect/Schema"