@effect/platform 0.48.15 → 0.48.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/Http/Cookies/package.json +6 -0
  2. package/README.md +235 -46
  3. package/dist/cjs/Http/Client.js +6 -1
  4. package/dist/cjs/Http/Client.js.map +1 -1
  5. package/dist/cjs/Http/ClientRequest.js.map +1 -1
  6. package/dist/cjs/Http/ClientResponse.js.map +1 -1
  7. package/dist/cjs/Http/Cookies.js +566 -0
  8. package/dist/cjs/Http/Cookies.js.map +1 -0
  9. package/dist/cjs/Http/Headers.js +2 -2
  10. package/dist/cjs/Http/Headers.js.map +1 -1
  11. package/dist/cjs/Http/IncomingMessage.js +12 -16
  12. package/dist/cjs/Http/IncomingMessage.js.map +1 -1
  13. package/dist/cjs/Http/Multipart.js.map +1 -1
  14. package/dist/cjs/Http/Router.js +11 -1
  15. package/dist/cjs/Http/Router.js.map +1 -1
  16. package/dist/cjs/Http/ServerRequest.js +6 -1
  17. package/dist/cjs/Http/ServerRequest.js.map +1 -1
  18. package/dist/cjs/Http/ServerResponse.js +36 -1
  19. package/dist/cjs/Http/ServerResponse.js.map +1 -1
  20. package/dist/cjs/Http/UrlParams.js +2 -2
  21. package/dist/cjs/Http/UrlParams.js.map +1 -1
  22. package/dist/cjs/HttpClient.js +3 -1
  23. package/dist/cjs/HttpClient.js.map +1 -1
  24. package/dist/cjs/HttpServer.js +3 -1
  25. package/dist/cjs/HttpServer.js.map +1 -1
  26. package/dist/cjs/internal/http/body.js +2 -2
  27. package/dist/cjs/internal/http/body.js.map +1 -1
  28. package/dist/cjs/internal/http/client.js +9 -3
  29. package/dist/cjs/internal/http/client.js.map +1 -1
  30. package/dist/cjs/internal/http/clientRequest.js +2 -2
  31. package/dist/cjs/internal/http/clientRequest.js.map +1 -1
  32. package/dist/cjs/internal/http/clientResponse.js +16 -8
  33. package/dist/cjs/internal/http/clientResponse.js.map +1 -1
  34. package/dist/cjs/internal/http/multipart.js +4 -4
  35. package/dist/cjs/internal/http/multipart.js.map +1 -1
  36. package/dist/cjs/internal/http/router.js +34 -7
  37. package/dist/cjs/internal/http/router.js.map +1 -1
  38. package/dist/cjs/internal/http/serverRequest.js +30 -15
  39. package/dist/cjs/internal/http/serverRequest.js.map +1 -1
  40. package/dist/cjs/internal/http/serverResponse.js +48 -23
  41. package/dist/cjs/internal/http/serverResponse.js.map +1 -1
  42. package/dist/dts/Error.d.ts.map +1 -1
  43. package/dist/dts/Http/App.d.ts +1 -1
  44. package/dist/dts/Http/App.d.ts.map +1 -1
  45. package/dist/dts/Http/Client.d.ts +13 -2
  46. package/dist/dts/Http/Client.d.ts.map +1 -1
  47. package/dist/dts/Http/ClientRequest.d.ts +2 -1
  48. package/dist/dts/Http/ClientRequest.d.ts.map +1 -1
  49. package/dist/dts/Http/ClientResponse.d.ts +7 -4
  50. package/dist/dts/Http/ClientResponse.d.ts.map +1 -1
  51. package/dist/dts/Http/Cookies.d.ts +253 -0
  52. package/dist/dts/Http/Cookies.d.ts.map +1 -0
  53. package/dist/dts/Http/Headers.d.ts +2 -2
  54. package/dist/dts/Http/Headers.d.ts.map +1 -1
  55. package/dist/dts/Http/IncomingMessage.d.ts +7 -6
  56. package/dist/dts/Http/IncomingMessage.d.ts.map +1 -1
  57. package/dist/dts/Http/Multipart.d.ts +3 -2
  58. package/dist/dts/Http/Multipart.d.ts.map +1 -1
  59. package/dist/dts/Http/Router.d.ts +29 -3
  60. package/dist/dts/Http/Router.d.ts.map +1 -1
  61. package/dist/dts/Http/ServerRequest.d.ts +14 -6
  62. package/dist/dts/Http/ServerRequest.d.ts.map +1 -1
  63. package/dist/dts/Http/ServerResponse.d.ts +89 -13
  64. package/dist/dts/Http/ServerResponse.d.ts.map +1 -1
  65. package/dist/dts/Http/UrlParams.d.ts +3 -2
  66. package/dist/dts/Http/UrlParams.d.ts.map +1 -1
  67. package/dist/dts/HttpClient.d.ts +8 -0
  68. package/dist/dts/HttpClient.d.ts.map +1 -1
  69. package/dist/dts/HttpServer.d.ts +8 -0
  70. package/dist/dts/HttpServer.d.ts.map +1 -1
  71. package/dist/dts/Socket.d.ts +3 -3
  72. package/dist/dts/Socket.d.ts.map +1 -1
  73. package/dist/dts/internal/http/router.d.ts.map +1 -1
  74. package/dist/esm/Http/Client.js +5 -0
  75. package/dist/esm/Http/Client.js.map +1 -1
  76. package/dist/esm/Http/ClientRequest.js.map +1 -1
  77. package/dist/esm/Http/ClientResponse.js.map +1 -1
  78. package/dist/esm/Http/Cookies.js +518 -0
  79. package/dist/esm/Http/Cookies.js.map +1 -0
  80. package/dist/esm/Http/Headers.js +2 -2
  81. package/dist/esm/Http/Headers.js.map +1 -1
  82. package/dist/esm/Http/IncomingMessage.js +12 -15
  83. package/dist/esm/Http/IncomingMessage.js.map +1 -1
  84. package/dist/esm/Http/Multipart.js.map +1 -1
  85. package/dist/esm/Http/Router.js +10 -0
  86. package/dist/esm/Http/Router.js.map +1 -1
  87. package/dist/esm/Http/ServerRequest.js +5 -0
  88. package/dist/esm/Http/ServerRequest.js.map +1 -1
  89. package/dist/esm/Http/ServerResponse.js +35 -0
  90. package/dist/esm/Http/ServerResponse.js.map +1 -1
  91. package/dist/esm/Http/UrlParams.js +2 -2
  92. package/dist/esm/Http/UrlParams.js.map +1 -1
  93. package/dist/esm/HttpClient.js +8 -0
  94. package/dist/esm/HttpClient.js.map +1 -1
  95. package/dist/esm/HttpServer.js +8 -0
  96. package/dist/esm/HttpServer.js.map +1 -1
  97. package/dist/esm/internal/http/body.js +2 -2
  98. package/dist/esm/internal/http/body.js.map +1 -1
  99. package/dist/esm/internal/http/client.js +8 -2
  100. package/dist/esm/internal/http/client.js.map +1 -1
  101. package/dist/esm/internal/http/clientRequest.js +2 -2
  102. package/dist/esm/internal/http/clientRequest.js.map +1 -1
  103. package/dist/esm/internal/http/clientResponse.js +16 -8
  104. package/dist/esm/internal/http/clientResponse.js.map +1 -1
  105. package/dist/esm/internal/http/multipart.js +4 -4
  106. package/dist/esm/internal/http/multipart.js.map +1 -1
  107. package/dist/esm/internal/http/router.js +31 -6
  108. package/dist/esm/internal/http/router.js.map +1 -1
  109. package/dist/esm/internal/http/serverRequest.js +28 -14
  110. package/dist/esm/internal/http/serverRequest.js.map +1 -1
  111. package/dist/esm/internal/http/serverResponse.js +47 -22
  112. package/dist/esm/internal/http/serverResponse.js.map +1 -1
  113. package/package.json +12 -4
  114. package/src/Http/Client.ts +16 -2
  115. package/src/Http/ClientRequest.ts +3 -1
  116. package/src/Http/ClientResponse.ts +18 -7
  117. package/src/Http/Cookies.ts +718 -0
  118. package/src/Http/Headers.ts +16 -8
  119. package/src/Http/IncomingMessage.ts +17 -12
  120. package/src/Http/Multipart.ts +5 -2
  121. package/src/Http/Router.ts +50 -3
  122. package/src/Http/ServerRequest.ts +25 -7
  123. package/src/Http/ServerResponse.ts +145 -13
  124. package/src/Http/UrlParams.ts +3 -2
  125. package/src/HttpClient.ts +8 -0
  126. package/src/HttpServer.ts +8 -0
  127. package/src/internal/http/body.ts +3 -2
  128. package/src/internal/http/client.ts +45 -5
  129. package/src/internal/http/clientRequest.ts +3 -2
  130. package/src/internal/http/clientResponse.ts +18 -8
  131. package/src/internal/http/multipart.ts +7 -4
  132. package/src/internal/http/router.ts +80 -6
  133. package/src/internal/http/serverRequest.ts +41 -15
  134. package/src/internal/http/serverResponse.ts +190 -18
@@ -0,0 +1,718 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ import * as Duration from "effect/Duration"
5
+ import * as Either from "effect/Either"
6
+ import { dual } from "effect/Function"
7
+ import * as Inspectable from "effect/Inspectable"
8
+ import * as Option from "effect/Option"
9
+ import { type Pipeable, pipeArguments } from "effect/Pipeable"
10
+ import * as Predicate from "effect/Predicate"
11
+ import * as ReadonlyRecord from "effect/ReadonlyRecord"
12
+ import type * as Types from "effect/Types"
13
+ import { TypeIdError } from "../Error.js"
14
+
15
+ /**
16
+ * @since 1.0.0
17
+ * @category type ids
18
+ */
19
+ export const TypeId = Symbol.for("@effect/platform/Http/Cookies")
20
+
21
+ /**
22
+ * @since 1.0.0
23
+ * @category type ids
24
+ */
25
+ export type TypeId = typeof TypeId
26
+
27
+ /**
28
+ * @since 1.0.0
29
+ * @category refinements
30
+ */
31
+ export const isCookies = (u: unknown): u is Cookies => Predicate.hasProperty(u, TypeId)
32
+
33
+ /**
34
+ * @since 1.0.0
35
+ * @category models
36
+ */
37
+ export interface Cookies extends Pipeable, Inspectable.Inspectable {
38
+ readonly [TypeId]: TypeId
39
+ readonly cookies: ReadonlyRecord.ReadonlyRecord<string, Cookie>
40
+ }
41
+
42
+ /**
43
+ * @since 1.0.0
44
+ * @category type ids
45
+ */
46
+ export const CookieTypeId = Symbol.for("@effect/platform/Http/Cookies/Cookie")
47
+
48
+ /**
49
+ * @since 1.0.0
50
+ * @category type ids
51
+ */
52
+ export type CookieTypeId = typeof CookieTypeId
53
+
54
+ /**
55
+ * @since 1.0.0
56
+ * @category cookie
57
+ */
58
+ export interface Cookie extends Inspectable.Inspectable {
59
+ readonly [CookieTypeId]: CookieTypeId
60
+ readonly name: string
61
+ readonly value: string
62
+ readonly valueEncoded: string
63
+ readonly options?: {
64
+ readonly domain?: string | undefined
65
+ readonly expires?: Date | undefined
66
+ readonly maxAge?: Duration.DurationInput | undefined
67
+ readonly path?: string | undefined
68
+ readonly priority?: "low" | "medium" | "high" | undefined
69
+ readonly httpOnly?: boolean | undefined
70
+ readonly secure?: boolean | undefined
71
+ readonly partitioned?: boolean | undefined
72
+ readonly sameSite?: "lax" | "strict" | "none" | undefined
73
+ } | undefined
74
+ }
75
+
76
+ /**
77
+ * @since 1.0.0
78
+ * @category type ids
79
+ */
80
+ export const ErrorTypeId = Symbol.for("@effect/platform/Http/Cookies/CookieError")
81
+
82
+ /**
83
+ * @since 1.0.0
84
+ * @category type ids
85
+ */
86
+ export type ErrorTypeId = typeof ErrorTypeId
87
+
88
+ /**
89
+ * @since 1.0.0
90
+ * @category errors
91
+ */
92
+ export class CookiesError extends TypeIdError(ErrorTypeId, "CookieError")<{
93
+ readonly reason: "InvalidName" | "InvalidValue" | "InvalidDomain" | "InvalidPath" | "InfinityMaxAge"
94
+ }> {
95
+ get message() {
96
+ return this.reason
97
+ }
98
+ }
99
+
100
+ const Proto: Omit<Cookies, "cookies"> = {
101
+ [TypeId]: TypeId,
102
+ ...Inspectable.BaseProto,
103
+ toJSON(this: Cookies) {
104
+ return {
105
+ _id: "@effect/platform/Http/Cookies",
106
+ cookies: ReadonlyRecord.map(this.cookies, (cookie) => cookie.toJSON())
107
+ }
108
+ },
109
+ pipe() {
110
+ return pipeArguments(this, arguments)
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Create a Cookies object from an Iterable
116
+ *
117
+ * @since 1.0.0
118
+ * @category constructors
119
+ */
120
+ export const fromReadonlyRecord = (cookies: ReadonlyRecord.ReadonlyRecord<string, Cookie>): Cookies => {
121
+ const self = Object.create(Proto)
122
+ self.cookies = cookies
123
+ return self
124
+ }
125
+
126
+ /**
127
+ * Create a Cookies object from an Iterable
128
+ *
129
+ * @since 1.0.0
130
+ * @category constructors
131
+ */
132
+ export const fromIterable = (cookies: Iterable<Cookie>): Cookies => {
133
+ const record: Record<string, Cookie> = {}
134
+ for (const cookie of cookies) {
135
+ record[cookie.name] = cookie
136
+ }
137
+ return fromReadonlyRecord(record)
138
+ }
139
+
140
+ /**
141
+ * Create a Cookies object from a set of Set-Cookie headers
142
+ *
143
+ * @since 1.0.0
144
+ * @category constructors
145
+ */
146
+ export const fromSetCookie = (headers: Iterable<string> | string): Cookies => {
147
+ const arrayHeaders = typeof headers === "string" ? [headers] : headers
148
+ const cookies: Array<Cookie> = []
149
+ for (const header of arrayHeaders) {
150
+ const cookie = parseSetCookie(header.trim())
151
+ if (Option.isSome(cookie)) {
152
+ cookies.push(cookie.value)
153
+ }
154
+ }
155
+
156
+ return fromIterable(cookies)
157
+ }
158
+
159
+ function parseSetCookie(header: string): Option.Option<Cookie> {
160
+ const parts = header.split(";").map((_) => _.trim()).filter((_) => _ !== "")
161
+ if (parts.length === 0) {
162
+ return Option.none()
163
+ }
164
+
165
+ const firstEqual = parts[0].indexOf("=")
166
+ if (firstEqual === -1) {
167
+ return Option.none()
168
+ }
169
+ const name = parts[0].slice(0, firstEqual)
170
+ if (!fieldContentRegExp.test(name)) {
171
+ return Option.none()
172
+ }
173
+
174
+ const valueEncoded = parts[0].slice(firstEqual + 1)
175
+ const value = tryDecodeURIComponent(valueEncoded)
176
+
177
+ if (parts.length === 1) {
178
+ return Option.some(Object.assign(Object.create(CookieProto), {
179
+ name,
180
+ value,
181
+ valueEncoded
182
+ }))
183
+ }
184
+
185
+ const options: Types.Mutable<Cookie["options"]> = {}
186
+
187
+ for (let i = 1; i < parts.length; i++) {
188
+ const part = parts[i]
189
+ const equalIndex = part.indexOf("=")
190
+ const key = equalIndex === -1 ? part : part.slice(0, equalIndex).trim()
191
+ const value = equalIndex === -1 ? undefined : part.slice(equalIndex + 1).trim()
192
+
193
+ switch (key.toLowerCase()) {
194
+ case "domain": {
195
+ if (value === undefined) {
196
+ break
197
+ }
198
+ const domain = value.trim().replace(/^\./, "")
199
+ if (domain) {
200
+ options.domain = domain
201
+ }
202
+ break
203
+ }
204
+ case "expires": {
205
+ if (value === undefined) {
206
+ break
207
+ }
208
+ const date = new Date(value)
209
+ if (!isNaN(date.getTime())) {
210
+ options.expires = date
211
+ }
212
+ break
213
+ }
214
+ case "max-age": {
215
+ if (value === undefined) {
216
+ break
217
+ }
218
+ const maxAge = parseInt(value, 10)
219
+ if (!isNaN(maxAge)) {
220
+ options.maxAge = Duration.seconds(maxAge)
221
+ }
222
+ break
223
+ }
224
+ case "path": {
225
+ if (value === undefined) {
226
+ break
227
+ }
228
+ if (value[0] === "/") {
229
+ options.path = value
230
+ }
231
+ break
232
+ }
233
+ case "priority": {
234
+ if (value === undefined) {
235
+ break
236
+ }
237
+ switch (value.toLowerCase()) {
238
+ case "low":
239
+ options.priority = "low"
240
+ break
241
+ case "medium":
242
+ options.priority = "medium"
243
+ break
244
+ case "high":
245
+ options.priority = "high"
246
+ break
247
+ }
248
+ break
249
+ }
250
+ case "httponly": {
251
+ options.httpOnly = true
252
+ break
253
+ }
254
+ case "secure": {
255
+ options.secure = true
256
+ break
257
+ }
258
+ case "partitioned": {
259
+ options.partitioned = true
260
+ break
261
+ }
262
+ case "samesite": {
263
+ if (value === undefined) {
264
+ break
265
+ }
266
+ switch (value.toLowerCase()) {
267
+ case "lax":
268
+ options.sameSite = "lax"
269
+ break
270
+ case "strict":
271
+ options.sameSite = "strict"
272
+ break
273
+ case "none":
274
+ options.sameSite = "none"
275
+ break
276
+ }
277
+ break
278
+ }
279
+ }
280
+ }
281
+
282
+ return Option.some(Object.assign(Object.create(CookieProto), {
283
+ name,
284
+ value,
285
+ valueEncoded,
286
+ options: Object.keys(options).length > 0 ? options : undefined
287
+ }))
288
+ }
289
+
290
+ /**
291
+ * An empty Cookies object
292
+ *
293
+ * @since 1.0.0
294
+ * @category constructors
295
+ */
296
+ export const empty: Cookies = fromIterable([])
297
+
298
+ /**
299
+ * @since 1.0.0
300
+ * @category refinements
301
+ */
302
+ export const isEmpty = (self: Cookies): boolean => ReadonlyRecord.isEmptyRecord(self.cookies)
303
+
304
+ // eslint-disable-next-line no-control-regex
305
+ const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/
306
+
307
+ const CookieProto = {
308
+ [CookieTypeId]: CookieTypeId,
309
+ ...Inspectable.BaseProto,
310
+ toJSON(this: Cookie) {
311
+ return {
312
+ _id: "@effect/platform/Http/Cookies/Cookie",
313
+ name: this.name,
314
+ value: this.value,
315
+ options: this.options
316
+ }
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Create a new cookie
322
+ *
323
+ * @since 1.0.0
324
+ * @category constructors
325
+ */
326
+ export function makeCookie(
327
+ name: string,
328
+ value: string,
329
+ options?: Cookie["options"] | undefined
330
+ ): Either.Either<Cookie, CookiesError> {
331
+ if (!fieldContentRegExp.test(name)) {
332
+ return Either.left(new CookiesError({ reason: "InvalidName" }))
333
+ }
334
+ const encodedValue = encodeURIComponent(value)
335
+ if (encodedValue && !fieldContentRegExp.test(encodedValue)) {
336
+ return Either.left(new CookiesError({ reason: "InvalidValue" }))
337
+ }
338
+
339
+ if (options !== undefined) {
340
+ if (options.domain !== undefined && !fieldContentRegExp.test(options.domain)) {
341
+ return Either.left(new CookiesError({ reason: "InvalidDomain" }))
342
+ }
343
+
344
+ if (options.path !== undefined && !fieldContentRegExp.test(options.path)) {
345
+ return Either.left(new CookiesError({ reason: "InvalidPath" }))
346
+ }
347
+
348
+ if (options.maxAge !== undefined && !Duration.isFinite(Duration.decode(options.maxAge))) {
349
+ return Either.left(new CookiesError({ reason: "InfinityMaxAge" }))
350
+ }
351
+ }
352
+
353
+ return Either.right(Object.assign(Object.create(CookieProto), {
354
+ name,
355
+ value,
356
+ valueEncoded: encodedValue,
357
+ options
358
+ }))
359
+ }
360
+
361
+ /**
362
+ * Create a new cookie, throwing an error if invalid
363
+ *
364
+ * @since 1.0.0
365
+ * @category constructors
366
+ */
367
+ export const unsafeMakeCookie = (
368
+ name: string,
369
+ value: string,
370
+ options?: Cookie["options"] | undefined
371
+ ): Cookie => Either.getOrThrow(makeCookie(name, value, options))
372
+
373
+ /**
374
+ * Add a cookie to a Cookies object
375
+ *
376
+ * @since 1.0.0
377
+ * @category combinators
378
+ */
379
+ export const setCookie: {
380
+ (cookie: Cookie): (self: Cookies) => Cookies
381
+ (
382
+ self: Cookies,
383
+ cookie: Cookie
384
+ ): Cookies
385
+ } = dual(
386
+ 2,
387
+ (self: Cookies, cookie: Cookie) =>
388
+ fromReadonlyRecord(ReadonlyRecord.set(
389
+ self.cookies,
390
+ cookie.name,
391
+ cookie
392
+ ))
393
+ )
394
+
395
+ /**
396
+ * Add multiple cookies to a Cookies object
397
+ *
398
+ * @since 1.0.0
399
+ * @category combinators
400
+ */
401
+ export const setAllCookie: {
402
+ (cookies: Iterable<Cookie>): (self: Cookies) => Cookies
403
+ (
404
+ self: Cookies,
405
+ cookies: Iterable<Cookie>
406
+ ): Cookies
407
+ } = dual(2, (self: Cookies, cookies: Iterable<Cookie>) => {
408
+ const record = { ...self.cookies }
409
+ for (const cookie of cookies) {
410
+ record[cookie.name] = cookie
411
+ }
412
+ return fromReadonlyRecord(record)
413
+ })
414
+
415
+ /**
416
+ * Combine two Cookies objects, removing duplicates from the first
417
+ *
418
+ * @since 1.0.0
419
+ * @category combinators
420
+ */
421
+ export const merge: {
422
+ (that: Cookies): (self: Cookies) => Cookies
423
+ (
424
+ self: Cookies,
425
+ that: Cookies
426
+ ): Cookies
427
+ } = dual(2, (self: Cookies, that: Cookies) =>
428
+ fromReadonlyRecord({
429
+ ...self.cookies,
430
+ ...that.cookies
431
+ }))
432
+
433
+ /**
434
+ * Remove a cookie by name
435
+ *
436
+ * @since 1.0.0
437
+ * @category combinators
438
+ */
439
+ export const remove: {
440
+ (name: string): (self: Cookies) => Cookies
441
+ (
442
+ self: Cookies,
443
+ name: string
444
+ ): Cookies
445
+ } = dual(2, (self: Cookies, name: string) => fromReadonlyRecord(ReadonlyRecord.remove(self.cookies, name)))
446
+
447
+ /**
448
+ * Add a cookie to a Cookies object
449
+ *
450
+ * @since 1.0.0
451
+ * @category combinators
452
+ */
453
+ export const set: {
454
+ (
455
+ name: string,
456
+ value: string,
457
+ options?: Cookie["options"]
458
+ ): (self: Cookies) => Either.Either<Cookies, CookiesError>
459
+ (
460
+ self: Cookies,
461
+ name: string,
462
+ value: string,
463
+ options?: Cookie["options"]
464
+ ): Either.Either<Cookies, CookiesError>
465
+ } = dual(
466
+ (args) => isCookies(args[0]),
467
+ (self: Cookies, name: string, value: string, options?: Cookie["options"]) =>
468
+ Either.map(
469
+ makeCookie(name, value, options),
470
+ (cookie) => fromReadonlyRecord(ReadonlyRecord.set(self.cookies, name, cookie))
471
+ )
472
+ )
473
+
474
+ /**
475
+ * Add a cookie to a Cookies object
476
+ *
477
+ * @since 1.0.0
478
+ * @category combinators
479
+ */
480
+ export const unsafeSet: {
481
+ (
482
+ name: string,
483
+ value: string,
484
+ options?: Cookie["options"]
485
+ ): (self: Cookies) => Cookies
486
+ (
487
+ self: Cookies,
488
+ name: string,
489
+ value: string,
490
+ options?: Cookie["options"]
491
+ ): Cookies
492
+ } = dual(
493
+ (args) => isCookies(args[0]),
494
+ (self: Cookies, name: string, value: string, options?: Cookie["options"]) =>
495
+ fromReadonlyRecord(ReadonlyRecord.set(
496
+ self.cookies,
497
+ name,
498
+ unsafeMakeCookie(name, value, options)
499
+ ))
500
+ )
501
+
502
+ /**
503
+ * Add multiple cookies to a Cookies object
504
+ *
505
+ * @since 1.0.0
506
+ * @category combinators
507
+ */
508
+ export const setAll: {
509
+ (
510
+ cookies: Iterable<readonly [name: string, value: string, options?: Cookie["options"]]>
511
+ ): (self: Cookies) => Either.Either<Cookies, CookiesError>
512
+ (
513
+ self: Cookies,
514
+ cookies: Iterable<readonly [name: string, value: string, options?: Cookie["options"]]>
515
+ ): Either.Either<Cookies, CookiesError>
516
+ } = dual(
517
+ 2,
518
+ (
519
+ self: Cookies,
520
+ cookies: Iterable<readonly [name: string, value: string, options?: Cookie["options"]]>
521
+ ): Either.Either<Cookies, CookiesError> => {
522
+ const record: Record<string, Cookie> = { ...self.cookies }
523
+ for (const [name, value, options] of cookies) {
524
+ const either = makeCookie(name, value, options)
525
+ if (Either.isLeft(either)) {
526
+ return either as Either.Left<CookiesError, never>
527
+ }
528
+ record[name] = either.right
529
+ }
530
+ return Either.right(fromReadonlyRecord(record))
531
+ }
532
+ )
533
+
534
+ /**
535
+ * Add multiple cookies to a Cookies object, throwing an error if invalid
536
+ *
537
+ * @since 1.0.0
538
+ * @category combinators
539
+ */
540
+ export const unsafeSetAll: {
541
+ (
542
+ cookies: Iterable<readonly [name: string, value: string, options?: Cookie["options"]]>
543
+ ): (self: Cookies) => Cookies
544
+ (
545
+ self: Cookies,
546
+ cookies: Iterable<readonly [name: string, value: string, options?: Cookie["options"]]>
547
+ ): Cookies
548
+ } = dual(
549
+ 2,
550
+ (
551
+ self: Cookies,
552
+ cookies: Iterable<readonly [name: string, value: string, options?: Cookie["options"]]>
553
+ ): Cookies => Either.getOrThrow(setAll(self, cookies))
554
+ )
555
+
556
+ /**
557
+ * Serialize a cookie into a string
558
+ *
559
+ * Adapted from https://github.com/fastify/fastify-cookie under MIT License
560
+ *
561
+ * @since 1.0.0
562
+ * @category encoding
563
+ */
564
+ export function serializeCookie(self: Cookie): string {
565
+ let str = self.name + "=" + self.valueEncoded
566
+
567
+ if (self.options === undefined) {
568
+ return str
569
+ }
570
+ const options = self.options
571
+
572
+ if (options.maxAge !== undefined) {
573
+ const maxAge = Duration.toSeconds(options.maxAge)
574
+ str += "; Max-Age=" + Math.trunc(maxAge)
575
+ }
576
+
577
+ if (options.domain !== undefined) {
578
+ str += "; Domain=" + options.domain
579
+ }
580
+
581
+ if (options.path !== undefined) {
582
+ str += "; Path=" + options.path
583
+ }
584
+
585
+ if (options.priority !== undefined) {
586
+ switch (options.priority) {
587
+ case "low":
588
+ str += "; Priority=Low"
589
+ break
590
+ case "medium":
591
+ str += "; Priority=Medium"
592
+ break
593
+ case "high":
594
+ str += "; Priority=High"
595
+ break
596
+ }
597
+ }
598
+
599
+ if (options.expires !== undefined) {
600
+ str += "; Expires=" + options.expires.toUTCString()
601
+ }
602
+
603
+ if (options.httpOnly) {
604
+ str += "; HttpOnly"
605
+ }
606
+
607
+ if (options.secure) {
608
+ str += "; Secure"
609
+ }
610
+
611
+ // Draft implementation to support Chrome from 2024-Q1 forward.
612
+ // See https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#section-2.1
613
+ if (options.partitioned) {
614
+ str += "; Partitioned"
615
+ }
616
+
617
+ if (options.sameSite !== undefined) {
618
+ switch (options.sameSite) {
619
+ case "lax":
620
+ str += "; SameSite=Lax"
621
+ break
622
+ case "strict":
623
+ str += "; SameSite=Strict"
624
+ break
625
+ case "none":
626
+ str += "; SameSite=None"
627
+ break
628
+ }
629
+ }
630
+
631
+ return str
632
+ }
633
+
634
+ /**
635
+ * Serialize a Cookies object into a Cookie header
636
+ *
637
+ * @since 1.0.0
638
+ * @category encoding
639
+ */
640
+ export const toCookieHeader = (self: Cookies): string =>
641
+ Object.values(self.cookies).map((cookie) => `${cookie.name}=${cookie.valueEncoded}`).join("; ")
642
+
643
+ /**
644
+ * To record
645
+ *
646
+ * @since 1.0.0
647
+ * @category encoding
648
+ */
649
+ export const toRecord = (self: Cookies): Record<string, string> => {
650
+ const record: Record<string, string> = {}
651
+ const cookies = Object.values(self.cookies)
652
+ for (let index = 0; index < cookies.length; index++) {
653
+ const cookie = cookies[index]
654
+ record[cookie.name] = cookie.value
655
+ }
656
+ return record
657
+ }
658
+
659
+ /**
660
+ * Serialize a Cookies object into Headers object containing one or more Set-Cookie headers
661
+ *
662
+ * @since 1.0.0
663
+ * @category encoding
664
+ */
665
+ export const toSetCookieHeaders = (self: Cookies): Array<string> => Object.values(self.cookies).map(serializeCookie)
666
+
667
+ /**
668
+ * Parse a cookie header into a record of key-value pairs
669
+ *
670
+ * Adapted from https://github.com/fastify/fastify-cookie under MIT License
671
+ *
672
+ * @since 1.0.0
673
+ * @category decoding
674
+ */
675
+ export function parseHeader(header: string): Record<string, string> {
676
+ const result: Record<string, string> = {}
677
+
678
+ const strLen = header.length
679
+ let pos = 0
680
+ let terminatorPos = 0
681
+ // eslint-disable-next-line no-constant-condition
682
+ while (true) {
683
+ if (terminatorPos === strLen) break
684
+ terminatorPos = header.indexOf(";", pos)
685
+ if (terminatorPos === -1) terminatorPos = strLen // This is the last pair
686
+
687
+ let eqIdx = header.indexOf("=", pos)
688
+ if (eqIdx === -1) break // No key-value pairs left
689
+ if (eqIdx > terminatorPos) {
690
+ // Malformed key-value pair
691
+ pos = terminatorPos + 1
692
+ continue
693
+ }
694
+
695
+ const key = header.substring(pos, eqIdx++).trim()
696
+ if (result[key] === undefined) {
697
+ const val = header.charCodeAt(eqIdx) === 0x22
698
+ ? header.substring(eqIdx + 1, terminatorPos - 1).trim()
699
+ : header.substring(eqIdx, terminatorPos).trim()
700
+
701
+ result[key] = !(val.indexOf("%") === -1)
702
+ ? tryDecodeURIComponent(val)
703
+ : val
704
+ }
705
+
706
+ pos = terminatorPos + 1
707
+ }
708
+
709
+ return result
710
+ }
711
+
712
+ const tryDecodeURIComponent = (str: string): string => {
713
+ try {
714
+ return decodeURIComponent(str)
715
+ } catch (_) {
716
+ return str
717
+ }
718
+ }