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.
Files changed (105) hide show
  1. package/README.md +1 -4
  2. package/dist/Cookies.js +392 -0
  3. package/dist/FileSystem.js +131 -0
  4. package/dist/Socket.js +37 -0
  5. package/package.json +39 -40
  6. package/src/Commander.ts +73 -130
  7. package/src/ContentNegotiation.ts +68 -100
  8. package/src/Cookies.ts +408 -0
  9. package/src/Development.ts +48 -63
  10. package/src/Effectify.ts +222 -206
  11. package/src/Entity.ts +59 -86
  12. package/src/FilePathPattern.ts +5 -5
  13. package/src/FileRouter.ts +38 -63
  14. package/src/FileRouterCodegen.ts +64 -56
  15. package/src/FileSystem.ts +390 -0
  16. package/src/Http.ts +17 -50
  17. package/src/PathPattern.ts +33 -41
  18. package/src/PlatformError.ts +29 -50
  19. package/src/PlatformRuntime.ts +39 -47
  20. package/src/Route.ts +68 -187
  21. package/src/RouteBody.ts +45 -161
  22. package/src/RouteHook.ts +22 -45
  23. package/src/RouteHttp.ts +88 -142
  24. package/src/RouteHttpTracer.ts +25 -26
  25. package/src/RouteMount.ts +100 -238
  26. package/src/RouteSchema.ts +67 -201
  27. package/src/RouteSse.ts +28 -82
  28. package/src/RouteTree.ts +31 -79
  29. package/src/RouteTrie.ts +13 -32
  30. package/src/SchemaExtra.ts +3 -5
  31. package/src/Socket.ts +51 -0
  32. package/src/Start.ts +20 -21
  33. package/src/StreamExtra.ts +93 -96
  34. package/src/TuplePathPattern.ts +54 -43
  35. package/src/Unique.ts +9 -15
  36. package/src/Values.ts +26 -30
  37. package/src/bun/BunBundle.ts +27 -73
  38. package/src/bun/BunImportTrackerPlugin.ts +67 -65
  39. package/src/bun/BunRoute.ts +12 -31
  40. package/src/bun/BunRuntime.ts +3 -10
  41. package/src/bun/BunServer.ts +50 -60
  42. package/src/bun/BunVirtualFilesPlugin.ts +1 -4
  43. package/src/bun/_BunEnhancedResolve.ts +17 -42
  44. package/src/bun/_empty.html +0 -1
  45. package/src/bundler/Bundle.ts +20 -36
  46. package/src/bundler/BundleFiles.ts +36 -56
  47. package/src/client/Overlay.ts +1 -2
  48. package/src/client/ScrollState.ts +5 -9
  49. package/src/client/index.ts +10 -13
  50. package/src/datastar/actions/fetch.ts +29 -48
  51. package/src/datastar/actions/peek.ts +1 -5
  52. package/src/datastar/actions/setAll.ts +2 -2
  53. package/src/datastar/actions/toggleAll.ts +2 -2
  54. package/src/datastar/attributes/attr.ts +17 -18
  55. package/src/datastar/attributes/bind.ts +41 -61
  56. package/src/datastar/attributes/class.ts +2 -5
  57. package/src/datastar/attributes/computed.ts +2 -10
  58. package/src/datastar/attributes/effect.ts +1 -2
  59. package/src/datastar/attributes/indicator.ts +2 -8
  60. package/src/datastar/attributes/init.ts +2 -10
  61. package/src/datastar/attributes/jsonSignals.ts +1 -6
  62. package/src/datastar/attributes/on.ts +4 -13
  63. package/src/datastar/attributes/onIntersect.ts +10 -22
  64. package/src/datastar/attributes/onInterval.ts +2 -10
  65. package/src/datastar/attributes/onSignalPatch.ts +18 -28
  66. package/src/datastar/attributes/ref.ts +1 -2
  67. package/src/datastar/attributes/show.ts +1 -2
  68. package/src/datastar/attributes/signals.ts +1 -5
  69. package/src/datastar/attributes/style.ts +6 -12
  70. package/src/datastar/attributes/text.ts +1 -2
  71. package/src/datastar/engine.ts +102 -158
  72. package/src/datastar/index.ts +2 -2
  73. package/src/datastar/utils.ts +16 -51
  74. package/src/datastar/watchers/patchElements.ts +35 -93
  75. package/src/datastar/watchers/patchSignals.ts +1 -2
  76. package/src/experimental/EncryptedCookies.ts +81 -175
  77. package/src/experimental/index.ts +0 -1
  78. package/src/hyper/Hyper.ts +14 -33
  79. package/src/hyper/HyperHtml.ts +13 -10
  80. package/src/hyper/HyperNode.ts +2 -7
  81. package/src/hyper/HyperRoute.ts +2 -5
  82. package/src/hyper/jsx-runtime.ts +2 -10
  83. package/src/hyper/jsx.d.ts +171 -440
  84. package/src/lint/plugin.js +276 -0
  85. package/src/node/NodeFileSystem.ts +140 -202
  86. package/src/node/NodeUtils.ts +1 -3
  87. package/src/testing/TestLogger.ts +9 -22
  88. package/src/testing/index.ts +0 -1
  89. package/src/testing/utils.ts +30 -31
  90. package/src/x/cloudflare/CloudflareTunnel.ts +53 -65
  91. package/src/x/datastar/Datastar.ts +3 -10
  92. package/src/x/datastar/index.ts +1 -3
  93. package/src/x/datastar/jsx-datastar.d.ts +1 -4
  94. package/src/x/tailwind/TailwindPlugin.ts +119 -112
  95. package/src/x/tailwind/compile.ts +10 -33
  96. package/src/x/tailwind/plugin.ts +2 -2
  97. package/src/HttpAppExtra.ts +0 -478
  98. package/src/HttpUtils.ts +0 -17
  99. package/src/bun/BunPlatformHttpServer.ts +0 -88
  100. package/src/bun/BunServerRequest.ts +0 -396
  101. package/src/bundler/BundleHttp.ts +0 -259
  102. package/src/experimental/SseHttpResponse.ts +0 -55
  103. package/src/middlewares/BasicAuthMiddleware.ts +0 -36
  104. package/src/middlewares/index.ts +0 -1
  105. 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({
@@ -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