@evolu/common 8.14.0 → 8.15.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/dist/src/Crypto.d.ts +2 -1
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Redacted.d.ts +19 -39
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +18 -33
- package/dist/src/Type.d.ts +161 -0
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +152 -2
- package/dist/src/WebSocket.d.ts +16 -1
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +14 -2
- package/dist/src/intl/_en.d.ts +5 -1
- package/dist/src/intl/_en.d.ts.map +1 -1
- package/dist/src/intl/_en.js +4 -0
- package/dist/src/intl/ar.d.ts +5 -1
- package/dist/src/intl/ar.d.ts.map +1 -1
- package/dist/src/intl/ar.js +4 -0
- package/dist/src/intl/bn.d.ts +5 -1
- package/dist/src/intl/bn.d.ts.map +1 -1
- package/dist/src/intl/bn.js +4 -0
- package/dist/src/intl/ca.d.ts +5 -1
- package/dist/src/intl/ca.d.ts.map +1 -1
- package/dist/src/intl/ca.js +4 -0
- package/dist/src/intl/cs.d.ts +5 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +4 -0
- package/dist/src/intl/da.d.ts +5 -1
- package/dist/src/intl/da.d.ts.map +1 -1
- package/dist/src/intl/da.js +4 -0
- package/dist/src/intl/de.d.ts +5 -1
- package/dist/src/intl/de.d.ts.map +1 -1
- package/dist/src/intl/de.js +4 -0
- package/dist/src/intl/el.d.ts +5 -1
- package/dist/src/intl/el.d.ts.map +1 -1
- package/dist/src/intl/el.js +4 -0
- package/dist/src/intl/es.d.ts +5 -1
- package/dist/src/intl/es.d.ts.map +1 -1
- package/dist/src/intl/es.js +4 -0
- package/dist/src/intl/fa.d.ts +5 -1
- package/dist/src/intl/fa.d.ts.map +1 -1
- package/dist/src/intl/fa.js +4 -0
- package/dist/src/intl/fi.d.ts +5 -1
- package/dist/src/intl/fi.d.ts.map +1 -1
- package/dist/src/intl/fi.js +4 -0
- package/dist/src/intl/fil.d.ts +5 -1
- package/dist/src/intl/fil.d.ts.map +1 -1
- package/dist/src/intl/fil.js +4 -0
- package/dist/src/intl/fr.d.ts +5 -1
- package/dist/src/intl/fr.d.ts.map +1 -1
- package/dist/src/intl/fr.js +4 -0
- package/dist/src/intl/he.d.ts +5 -1
- package/dist/src/intl/he.d.ts.map +1 -1
- package/dist/src/intl/he.js +4 -0
- package/dist/src/intl/hi.d.ts +5 -1
- package/dist/src/intl/hi.d.ts.map +1 -1
- package/dist/src/intl/hi.js +4 -0
- package/dist/src/intl/hr.d.ts +5 -1
- package/dist/src/intl/hr.d.ts.map +1 -1
- package/dist/src/intl/hr.js +4 -0
- package/dist/src/intl/hu.d.ts +3 -1
- package/dist/src/intl/hu.d.ts.map +1 -1
- package/dist/src/intl/hu.js +2 -0
- package/dist/src/intl/id.d.ts +5 -1
- package/dist/src/intl/id.d.ts.map +1 -1
- package/dist/src/intl/id.js +4 -0
- package/dist/src/intl/it.d.ts +5 -1
- package/dist/src/intl/it.d.ts.map +1 -1
- package/dist/src/intl/it.js +4 -0
- package/dist/src/intl/ja.d.ts +5 -1
- package/dist/src/intl/ja.d.ts.map +1 -1
- package/dist/src/intl/ja.js +4 -0
- package/dist/src/intl/ko.d.ts +5 -1
- package/dist/src/intl/ko.d.ts.map +1 -1
- package/dist/src/intl/ko.js +4 -0
- package/dist/src/intl/ml.d.ts +5 -1
- package/dist/src/intl/ml.d.ts.map +1 -1
- package/dist/src/intl/ml.js +4 -0
- package/dist/src/intl/mr.d.ts +5 -1
- package/dist/src/intl/mr.d.ts.map +1 -1
- package/dist/src/intl/mr.js +4 -0
- package/dist/src/intl/ms.d.ts +5 -1
- package/dist/src/intl/ms.d.ts.map +1 -1
- package/dist/src/intl/ms.js +4 -0
- package/dist/src/intl/nb.d.ts +3 -1
- package/dist/src/intl/nb.d.ts.map +1 -1
- package/dist/src/intl/nb.js +2 -0
- package/dist/src/intl/nl.d.ts +5 -1
- package/dist/src/intl/nl.d.ts.map +1 -1
- package/dist/src/intl/nl.js +4 -0
- package/dist/src/intl/pa.d.ts +5 -1
- package/dist/src/intl/pa.d.ts.map +1 -1
- package/dist/src/intl/pa.js +4 -0
- package/dist/src/intl/pl.d.ts +4 -0
- package/dist/src/intl/pl.d.ts.map +1 -1
- package/dist/src/intl/pl.js +4 -0
- package/dist/src/intl/pt-BR.d.ts +5 -1
- package/dist/src/intl/pt-BR.d.ts.map +1 -1
- package/dist/src/intl/pt-BR.js +4 -0
- package/dist/src/intl/pt.d.ts +5 -1
- package/dist/src/intl/pt.d.ts.map +1 -1
- package/dist/src/intl/pt.js +4 -0
- package/dist/src/intl/ro.d.ts +5 -1
- package/dist/src/intl/ro.d.ts.map +1 -1
- package/dist/src/intl/ro.js +4 -0
- package/dist/src/intl/sk.d.ts +5 -1
- package/dist/src/intl/sk.d.ts.map +1 -1
- package/dist/src/intl/sk.js +4 -0
- package/dist/src/intl/sl.d.ts +5 -1
- package/dist/src/intl/sl.d.ts.map +1 -1
- package/dist/src/intl/sl.js +4 -0
- package/dist/src/intl/sv.d.ts +5 -1
- package/dist/src/intl/sv.d.ts.map +1 -1
- package/dist/src/intl/sv.js +4 -0
- package/dist/src/intl/sw.d.ts +2 -0
- package/dist/src/intl/sw.d.ts.map +1 -1
- package/dist/src/intl/sw.js +2 -0
- package/dist/src/intl/ta.d.ts +5 -1
- package/dist/src/intl/ta.d.ts.map +1 -1
- package/dist/src/intl/ta.js +4 -0
- package/dist/src/intl/te.d.ts +5 -1
- package/dist/src/intl/te.d.ts.map +1 -1
- package/dist/src/intl/te.js +4 -0
- package/dist/src/intl/th.d.ts +5 -1
- package/dist/src/intl/th.d.ts.map +1 -1
- package/dist/src/intl/th.js +4 -0
- package/dist/src/intl/tr.d.ts +5 -1
- package/dist/src/intl/tr.d.ts.map +1 -1
- package/dist/src/intl/tr.js +4 -0
- package/dist/src/intl/uk.d.ts +5 -1
- package/dist/src/intl/uk.d.ts.map +1 -1
- package/dist/src/intl/uk.js +4 -0
- package/dist/src/intl/ur.d.ts +5 -1
- package/dist/src/intl/ur.d.ts.map +1 -1
- package/dist/src/intl/ur.js +4 -0
- package/dist/src/intl/vi.d.ts +5 -1
- package/dist/src/intl/vi.d.ts.map +1 -1
- package/dist/src/intl/vi.js +4 -0
- package/dist/src/intl/zh-CN.d.ts +5 -1
- package/dist/src/intl/zh-CN.d.ts.map +1 -1
- package/dist/src/intl/zh-CN.js +4 -0
- package/dist/src/intl/zh-TW.d.ts +5 -1
- package/dist/src/intl/zh-TW.d.ts.map +1 -1
- package/dist/src/intl/zh-TW.js +4 -0
- package/dist/src/local-first/Evolu.d.ts +4 -0
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +6 -1
- package/dist/src/local-first/Schema.d.ts +3 -0
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +9 -5
- package/dist/src/local-first/Shared.js +1 -3
- package/package.json +1 -1
- package/src/Crypto.ts +2 -1
- package/src/Redacted.test.ts +231 -0
- package/src/Redacted.ts +36 -47
- package/src/Type.test.ts +166 -0
- package/src/Type.ts +198 -2
- package/src/WebSocket.ts +33 -4
- package/src/intl/_en.ts +10 -0
- package/src/intl/ar.ts +8 -0
- package/src/intl/bn.ts +10 -0
- package/src/intl/ca.ts +10 -0
- package/src/intl/cs.ts +10 -0
- package/src/intl/da.ts +10 -0
- package/src/intl/de.ts +10 -0
- package/src/intl/el.ts +10 -0
- package/src/intl/es.ts +10 -0
- package/src/intl/fa.ts +10 -0
- package/src/intl/fi.ts +10 -0
- package/src/intl/fil.ts +10 -0
- package/src/intl/fr.ts +10 -0
- package/src/intl/he.ts +10 -0
- package/src/intl/hi.ts +10 -0
- package/src/intl/hr.ts +10 -0
- package/src/intl/hu.ts +6 -0
- package/src/intl/id.ts +10 -0
- package/src/intl/intl.test.ts +88 -0
- package/src/intl/it.ts +10 -0
- package/src/intl/ja.ts +10 -0
- package/src/intl/ko.ts +10 -0
- package/src/intl/ml.ts +10 -0
- package/src/intl/mr.ts +10 -0
- package/src/intl/ms.ts +8 -0
- package/src/intl/nb.ts +6 -0
- package/src/intl/nl.ts +10 -0
- package/src/intl/pa.ts +10 -0
- package/src/intl/pl.ts +12 -0
- package/src/intl/pt-BR.ts +10 -0
- package/src/intl/pt.ts +8 -0
- package/src/intl/ro.ts +10 -0
- package/src/intl/sk.ts +8 -0
- package/src/intl/sl.ts +10 -0
- package/src/intl/sv.ts +10 -0
- package/src/intl/sw.ts +4 -0
- package/src/intl/ta.ts +10 -0
- package/src/intl/te.ts +10 -0
- package/src/intl/th.ts +10 -0
- package/src/intl/tr.ts +10 -0
- package/src/intl/uk.ts +10 -0
- package/src/intl/ur.ts +8 -0
- package/src/intl/vi.ts +8 -0
- package/src/intl/zh-CN.ts +10 -0
- package/src/intl/zh-TW.ts +10 -0
- package/src/local-first/Evolu.test.ts +62 -8
- package/src/local-first/Evolu.ts +11 -1
- package/src/local-first/Schema.test.ts +80 -1
- package/src/local-first/Schema.ts +15 -5
- package/src/local-first/Shared.ts +1 -3
package/src/Redacted.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
import { assert } from "./Assert.ts";
|
|
8
8
|
import type { Brand } from "./Brand.ts";
|
|
9
|
-
import type { Eq } from "./Eq.ts";
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* A wrapper type that prevents sensitive values from being accidentally exposed
|
|
@@ -18,13 +17,17 @@ import type { Eq } from "./Eq.ts";
|
|
|
18
17
|
*
|
|
19
18
|
* For type-level distinction between different secrets, use branded types.
|
|
20
19
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* visible in devtools. Symbols can't be used because they don't support custom
|
|
25
|
-
* `toString`.
|
|
20
|
+
* Redacted guards against accidental exposure, such as logs, error reports, and
|
|
21
|
+
* serialized payloads. It does not hide the value from a debugger: DevTools and
|
|
22
|
+
* heap snapshots can still reach it.
|
|
26
23
|
*
|
|
27
|
-
*
|
|
24
|
+
* A structured clone, such as a `postMessage` to a worker, does not copy the
|
|
25
|
+
* hidden value and arrives as an empty object. Reveal the value before posting
|
|
26
|
+
* it, and wrap it again on arrival if the receiver passes it to app code.
|
|
27
|
+
*
|
|
28
|
+
* Implements `Disposable`, so the `using` syntax detaches the value when the
|
|
29
|
+
* scope ends. Disposal does not overwrite the value or release other references
|
|
30
|
+
* to it.
|
|
28
31
|
*
|
|
29
32
|
* ### Example
|
|
30
33
|
*
|
|
@@ -48,7 +51,6 @@ import type { Eq } from "./Eq.ts";
|
|
|
48
51
|
* using redactedKey: RedactedApiKey = createRedacted(apiKey);
|
|
49
52
|
* const fetchUser = (key: RedactedApiKey): ApiKey => revealRedacted(key);
|
|
50
53
|
*
|
|
51
|
-
* // oxlint-disable-next-line typescript/no-base-to-string -- Redacted intentionally implements a safe custom toString.
|
|
52
54
|
* assertEqual(redactedKey.toString(), "<redacted>");
|
|
53
55
|
* assertEqual(
|
|
54
56
|
* JSON.stringify({ apiKey: redactedKey }),
|
|
@@ -64,18 +66,32 @@ import type { Eq } from "./Eq.ts";
|
|
|
64
66
|
* using key = createRedacted(apiKey);
|
|
65
67
|
* return key;
|
|
66
68
|
* })();
|
|
67
|
-
* // Leaving the `using` scope
|
|
69
|
+
* // Leaving the `using` scope detaches the value, so revealing it throws.
|
|
68
70
|
* assertErr(trySync(() => revealRedacted(disposedKey)));
|
|
69
71
|
* ```
|
|
70
72
|
*/
|
|
71
73
|
export interface Redacted<A> extends Brand<"Redacted">, Disposable {
|
|
72
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The inner type. This is a type-only phantom property. Use it through
|
|
76
|
+
* `typeof redacted.Type`; it does not exist at runtime.
|
|
77
|
+
*/
|
|
73
78
|
readonly Type: A;
|
|
79
|
+
readonly toString: () => "<redacted>";
|
|
80
|
+
readonly toJSON: () => "<redacted>";
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
/** Creates a {@link Redacted} wrapper for a sensitive value. */
|
|
77
84
|
export const createRedacted = <A>(value: A): Redacted<A> => {
|
|
78
|
-
|
|
85
|
+
// Symbol.dispose is read here rather than in proto because installPolyfills
|
|
86
|
+
// runs after imported modules are evaluated. An arrow function also keeps a
|
|
87
|
+
// detached dispose method working.
|
|
88
|
+
const redacted = Object.create(proto, {
|
|
89
|
+
[Symbol.dispose]: {
|
|
90
|
+
value: () => {
|
|
91
|
+
registry.delete(redacted);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
}) as Redacted<A>;
|
|
79
95
|
registry.set(redacted, value);
|
|
80
96
|
return redacted;
|
|
81
97
|
};
|
|
@@ -84,11 +100,15 @@ const proto = {
|
|
|
84
100
|
toString: () => redactedString,
|
|
85
101
|
toJSON: () => redactedString,
|
|
86
102
|
[Symbol.for("nodejs.util.inspect.custom")]: () => redactedString,
|
|
87
|
-
[Symbol.dispose](this: Redacted<unknown>) {
|
|
88
|
-
registry.delete(this);
|
|
89
|
-
},
|
|
90
103
|
};
|
|
91
104
|
const redactedString = "<redacted>";
|
|
105
|
+
|
|
106
|
+
// The value lives in a WeakMap, so it is never a property: previews,
|
|
107
|
+
// enumeration, and serialization cannot show it, and it is garbage collected
|
|
108
|
+
// with the wrapper. A private field would show when DevTools expands the
|
|
109
|
+
// object. The wrapper is an object, not a symbol, because a symbol cannot
|
|
110
|
+
// customize toString or toJSON. DevTools can still reach the registry through
|
|
111
|
+
// the scopes of the wrapper's functions.
|
|
92
112
|
const registry = new WeakMap<Redacted<unknown>, unknown>();
|
|
93
113
|
|
|
94
114
|
/**
|
|
@@ -97,6 +117,8 @@ const registry = new WeakMap<Redacted<unknown>, unknown>();
|
|
|
97
117
|
* This is a separate function rather than a method on {@link Redacted} to make
|
|
98
118
|
* access visually explicit and easy to grep in code reviews. Accessing
|
|
99
119
|
* sensitive values should feel intentional, not convenient.
|
|
120
|
+
*
|
|
121
|
+
* Throws when the wrapper was disposed or is a structured clone.
|
|
100
122
|
*/
|
|
101
123
|
export const revealRedacted = <A>(redacted: Redacted<A>): A => {
|
|
102
124
|
assert(registry.has(redacted), "Redacted value was not in registry");
|
|
@@ -108,36 +130,3 @@ export const isRedacted = (value: unknown): value is Redacted<unknown> =>
|
|
|
108
130
|
typeof value === "object" &&
|
|
109
131
|
value !== null &&
|
|
110
132
|
Object.getPrototypeOf(value) === proto;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Creates an {@link Eq} for {@link Redacted} values based on an equality function
|
|
114
|
-
* for the underlying type.
|
|
115
|
-
*
|
|
116
|
-
* ### Example
|
|
117
|
-
*
|
|
118
|
-
* ```ts
|
|
119
|
-
* import {
|
|
120
|
-
* assertFalse,
|
|
121
|
-
* assertTrue,
|
|
122
|
-
* createEqRedacted,
|
|
123
|
-
* createRedacted,
|
|
124
|
-
* eqString,
|
|
125
|
-
* type Brand,
|
|
126
|
-
* } from "@evolu/common";
|
|
127
|
-
*
|
|
128
|
-
* type ApiKey = string & Brand<"ApiKey">;
|
|
129
|
-
* const eqRedactedApiKey = createEqRedacted<ApiKey>(eqString);
|
|
130
|
-
*
|
|
131
|
-
* // Apply brands only after validation or at another trusted boundary.
|
|
132
|
-
* using a = createRedacted("x" as ApiKey);
|
|
133
|
-
* using b = createRedacted("x" as ApiKey);
|
|
134
|
-
* using c = createRedacted("y" as ApiKey);
|
|
135
|
-
*
|
|
136
|
-
* assertTrue(eqRedactedApiKey(a, b));
|
|
137
|
-
* assertFalse(eqRedactedApiKey(a, c));
|
|
138
|
-
* ```
|
|
139
|
-
*/
|
|
140
|
-
export const createEqRedacted =
|
|
141
|
-
<A>(eq: Eq<A>): Eq<Redacted<A>> =>
|
|
142
|
-
(x, y) =>
|
|
143
|
-
eq(revealRedacted(x), revealRedacted(y));
|
package/src/Type.test.ts
CHANGED
|
@@ -103,6 +103,7 @@ import {
|
|
|
103
103
|
Digit1To59,
|
|
104
104
|
Digit1To99,
|
|
105
105
|
discriminatedUnion,
|
|
106
|
+
Email,
|
|
106
107
|
EvoluType,
|
|
107
108
|
finite,
|
|
108
109
|
FiniteNumber,
|
|
@@ -121,6 +122,7 @@ import {
|
|
|
121
122
|
idBytesTypeValueLength,
|
|
122
123
|
id,
|
|
123
124
|
idToIdBytes,
|
|
125
|
+
idToUuid,
|
|
124
126
|
json,
|
|
125
127
|
Json,
|
|
126
128
|
JsonArray,
|
|
@@ -216,6 +218,8 @@ import {
|
|
|
216
218
|
UnknownResult,
|
|
217
219
|
uint8ArrayToBase64Url,
|
|
218
220
|
UrlSafeString,
|
|
221
|
+
Uuid,
|
|
222
|
+
uuidToId,
|
|
219
223
|
zeroNonNegativeInt,
|
|
220
224
|
type AnyType,
|
|
221
225
|
type ArrayElementIssue,
|
|
@@ -239,6 +243,7 @@ import {
|
|
|
239
243
|
type DiscriminatedUnionMemberError,
|
|
240
244
|
type DiscriminatedUnionMemberIssue,
|
|
241
245
|
type DiscriminatedUnionType,
|
|
246
|
+
type EmailError,
|
|
242
247
|
type EvoluTypeError,
|
|
243
248
|
type ExtractTyped,
|
|
244
249
|
type FiniteError,
|
|
@@ -354,6 +359,7 @@ import {
|
|
|
354
359
|
type UnionInputType,
|
|
355
360
|
type UnionMemberError,
|
|
356
361
|
type UnionType,
|
|
362
|
+
type UuidError,
|
|
357
363
|
type ValidationOptions,
|
|
358
364
|
type ValidateLiteral,
|
|
359
365
|
type ValidateOutput,
|
|
@@ -545,6 +551,7 @@ describe("Type", () => {
|
|
|
545
551
|
Digit1To51,
|
|
546
552
|
Digit1To59,
|
|
547
553
|
Digit1To99,
|
|
554
|
+
Email,
|
|
548
555
|
EvoluType,
|
|
549
556
|
FiniteNumber,
|
|
550
557
|
Function,
|
|
@@ -601,6 +608,7 @@ describe("Type", () => {
|
|
|
601
608
|
UnknownNextResult,
|
|
602
609
|
UnknownResult,
|
|
603
610
|
UrlSafeString,
|
|
611
|
+
Uuid,
|
|
604
612
|
} as const satisfies Readonly<Record<ExportedTypeKey, TypeNode>>;
|
|
605
613
|
|
|
606
614
|
const IntrospectionRoot = createType(
|
|
@@ -679,6 +687,7 @@ describe("Type", () => {
|
|
|
679
687
|
"DateIsoFromDate",
|
|
680
688
|
"DecimalString",
|
|
681
689
|
"DiscriminatedUnion",
|
|
690
|
+
"Email",
|
|
682
691
|
"EvoluType",
|
|
683
692
|
"Finite",
|
|
684
693
|
"Function",
|
|
@@ -753,6 +762,7 @@ describe("Type", () => {
|
|
|
753
762
|
"Unknown",
|
|
754
763
|
"Uppercased",
|
|
755
764
|
"UrlSafeString",
|
|
765
|
+
"Uuid",
|
|
756
766
|
] as const;
|
|
757
767
|
|
|
758
768
|
assertType<
|
|
@@ -10013,6 +10023,122 @@ describe("BrandFactory", () => {
|
|
|
10013
10023
|
});
|
|
10014
10024
|
});
|
|
10015
10025
|
|
|
10026
|
+
describe("Email", () => {
|
|
10027
|
+
// Single-address cases from the WebKit and Chromium
|
|
10028
|
+
// ValidityState-typeMismatch-email.js tests, without the cases that
|
|
10029
|
+
// test the browser's whitespace sanitization.
|
|
10030
|
+
it("accepts valid email addresses as defined by the HTML Standard", () => {
|
|
10031
|
+
for (const value of [
|
|
10032
|
+
"something@something.com",
|
|
10033
|
+
"someone@localhost.localdomain",
|
|
10034
|
+
"someone@127.0.0.1",
|
|
10035
|
+
"a@b.b",
|
|
10036
|
+
"a/b@domain.com",
|
|
10037
|
+
"{}@domain.com",
|
|
10038
|
+
"m*'!%@something.sa",
|
|
10039
|
+
"tu!!7n7.ad##0!!!@company.ca",
|
|
10040
|
+
"%@com.com",
|
|
10041
|
+
"!#$%&'*+/=?^_`{|}~.-@com.com",
|
|
10042
|
+
".wooly@example.com",
|
|
10043
|
+
"wo..oly@example.com",
|
|
10044
|
+
"someone@do-ma-in.com",
|
|
10045
|
+
"somebody@example",
|
|
10046
|
+
"Ada@Example.COM",
|
|
10047
|
+
"a@xn--maana-pta.com",
|
|
10048
|
+
`${"a".repeat(64)}@p.com`,
|
|
10049
|
+
`a@${"p".repeat(63)}.${"c".repeat(63)}`,
|
|
10050
|
+
]) {
|
|
10051
|
+
assertEqual(Email.fromUnknown(value), ok(value));
|
|
10052
|
+
}
|
|
10053
|
+
assertType<typeof Email.Output, string & Brand<"Email">>();
|
|
10054
|
+
});
|
|
10055
|
+
|
|
10056
|
+
it("rejects invalid email addresses", () => {
|
|
10057
|
+
for (const value of [
|
|
10058
|
+
"invalid:email@example.com",
|
|
10059
|
+
"@somewhere.com",
|
|
10060
|
+
"example.com",
|
|
10061
|
+
"@@example.com",
|
|
10062
|
+
"a space@example.com",
|
|
10063
|
+
"something@ex..ample.com",
|
|
10064
|
+
"a\b@c",
|
|
10065
|
+
"someone@somewhere.com.",
|
|
10066
|
+
'""test\blah""@example.com',
|
|
10067
|
+
'"testblah"@example.com',
|
|
10068
|
+
"someone@somewhere.com@",
|
|
10069
|
+
"someone@somewhere_com",
|
|
10070
|
+
"someone@some:where.com",
|
|
10071
|
+
".",
|
|
10072
|
+
"F/s/f/a@feo+re.com",
|
|
10073
|
+
"some+long+email+address@some+host-weird-/looking.com",
|
|
10074
|
+
"a @p.com",
|
|
10075
|
+
"a\t@p.com",
|
|
10076
|
+
"a\u000B@p.com",
|
|
10077
|
+
"a\u000C@p.com",
|
|
10078
|
+
"a @p.com",
|
|
10079
|
+
"a @p.com",
|
|
10080
|
+
"ddjk-s-jk@asl-.com",
|
|
10081
|
+
"someone@do-.com",
|
|
10082
|
+
"somebody@-.com",
|
|
10083
|
+
`a@${"p".repeat(64)}.com`,
|
|
10084
|
+
`a@p.${"c".repeat(64)}`,
|
|
10085
|
+
" a@p.com",
|
|
10086
|
+
"a@p.com\n",
|
|
10087
|
+
"a@[127.0.0.1]",
|
|
10088
|
+
"mañana@example.com",
|
|
10089
|
+
"a@mañana.com",
|
|
10090
|
+
"",
|
|
10091
|
+
]) {
|
|
10092
|
+
assertEqual(
|
|
10093
|
+
Email.fromUnknown(value),
|
|
10094
|
+
err({ type: "Email", value }),
|
|
10095
|
+
);
|
|
10096
|
+
}
|
|
10097
|
+
assertEqual(
|
|
10098
|
+
Email.formatError({ type: "Email", value: "example.com" }),
|
|
10099
|
+
'The value "example.com" is not a valid email address.',
|
|
10100
|
+
);
|
|
10101
|
+
assertType<typeof Email.Error, EmailError>();
|
|
10102
|
+
});
|
|
10103
|
+
});
|
|
10104
|
+
|
|
10105
|
+
describe("Uuid", () => {
|
|
10106
|
+
it("accepts canonical lowercase UUIDs of every version and variant", () => {
|
|
10107
|
+
for (const value of [
|
|
10108
|
+
"20354d7a-e4fe-47af-8ff6-187bca92f3f9",
|
|
10109
|
+
"0190a6f4-8c3e-7b2a-9d41-5e6f7a8b9c0d",
|
|
10110
|
+
"00000000-0000-0000-0000-000000000000",
|
|
10111
|
+
"ffffffff-ffff-ffff-ffff-ffffffffffff",
|
|
10112
|
+
"01234567-89ab-0def-0123-456789abcdef",
|
|
10113
|
+
]) {
|
|
10114
|
+
assertEqual(Uuid.fromUnknown(value), ok(value));
|
|
10115
|
+
}
|
|
10116
|
+
assertType<typeof Uuid.Output, string & Brand<"Uuid">>();
|
|
10117
|
+
});
|
|
10118
|
+
|
|
10119
|
+
it("rejects non-canonical text", () => {
|
|
10120
|
+
for (const value of [
|
|
10121
|
+
"20354D7A-E4FE-47AF-8FF6-187BCA92F3F9",
|
|
10122
|
+
"20354d7a-e4fe-47af-8ff6-187bca92f3F9",
|
|
10123
|
+
"{20354d7a-e4fe-47af-8ff6-187bca92f3f9}",
|
|
10124
|
+
"urn:uuid:20354d7a-e4fe-47af-8ff6-187bca92f3f9",
|
|
10125
|
+
"20354d7ae4fe47af8ff6187bca92f3f9",
|
|
10126
|
+
"20354d7a-e4fe-47af-8ff6-187bca92f3f",
|
|
10127
|
+
"20354d7a-e4fe-47af-8ff6-187bca92f3f9 ",
|
|
10128
|
+
"g0354d7a-e4fe-47af-8ff6-187bca92f3f9",
|
|
10129
|
+
"invalid",
|
|
10130
|
+
"",
|
|
10131
|
+
]) {
|
|
10132
|
+
assertEqual(Uuid.fromUnknown(value), err({ type: "Uuid", value }));
|
|
10133
|
+
}
|
|
10134
|
+
assertEqual(
|
|
10135
|
+
Uuid.formatError({ type: "Uuid", value: "invalid" }),
|
|
10136
|
+
'The value "invalid" is not a canonical lowercase UUID.',
|
|
10137
|
+
);
|
|
10138
|
+
assertType<typeof Uuid.Error, UuidError>();
|
|
10139
|
+
});
|
|
10140
|
+
});
|
|
10141
|
+
|
|
10016
10142
|
describe("SimplePassword", () => {
|
|
10017
10143
|
it("requires trimmed text containing between 8 and 64 characters", () => {
|
|
10018
10144
|
assertEqual(
|
|
@@ -10195,6 +10321,46 @@ describe("BrandFactory", () => {
|
|
|
10195
10321
|
assertEqual(idBytesTypeValueLength, 16);
|
|
10196
10322
|
assertSame(idBytesToId(bytes), value);
|
|
10197
10323
|
});
|
|
10324
|
+
|
|
10325
|
+
it("converts to and from a Uuid with the same bytes", () => {
|
|
10326
|
+
const deps = testCreateDeps();
|
|
10327
|
+
const uuid = Uuid.orThrow("0190a6f4-8c3e-7b2a-9d41-5e6f7a8b9c0d");
|
|
10328
|
+
const value = uuidToId(uuid);
|
|
10329
|
+
|
|
10330
|
+
assertEqual(value, "AZCm9Iw-eyqdQV5veoucDQ");
|
|
10331
|
+
assertEqualBytes(
|
|
10332
|
+
idToIdBytes(value),
|
|
10333
|
+
new globalThis.Uint8Array([
|
|
10334
|
+
0x01, 0x90, 0xa6, 0xf4, 0x8c, 0x3e, 0x7b, 0x2a, 0x9d, 0x41, 0x5e,
|
|
10335
|
+
0x6f, 0x7a, 0x8b, 0x9c, 0x0d,
|
|
10336
|
+
]),
|
|
10337
|
+
);
|
|
10338
|
+
assertSame(idToUuid(value), uuid);
|
|
10339
|
+
|
|
10340
|
+
for (const id of [
|
|
10341
|
+
createId(deps),
|
|
10342
|
+
createIdAsUuidv7(deps),
|
|
10343
|
+
createIdFromString("uuid"),
|
|
10344
|
+
uuidToId(Uuid.orThrow("00000000-0000-0000-0000-000000000000")),
|
|
10345
|
+
uuidToId(Uuid.orThrow("ffffffff-ffff-ffff-ffff-ffffffffffff")),
|
|
10346
|
+
]) {
|
|
10347
|
+
const converted = idToUuid(id);
|
|
10348
|
+
assertTrue(Uuid.is(converted));
|
|
10349
|
+
assertSame(uuidToId(converted), id);
|
|
10350
|
+
}
|
|
10351
|
+
assertEqual(idToUuid(createIdAsUuidv7(deps))[14], "7");
|
|
10352
|
+
|
|
10353
|
+
const todoId = uuidToId<"Todo">(uuid);
|
|
10354
|
+
assertType<typeof value, Id>();
|
|
10355
|
+
assertType<typeof todoId, Id & Brand<"Todo">>();
|
|
10356
|
+
assertType<ReturnType<typeof idToUuid>, Uuid>();
|
|
10357
|
+
void (() => {
|
|
10358
|
+
// @ts-expect-error A union would assign multiple brands to one Id.
|
|
10359
|
+
uuidToId<"Todo" | "User">(uuid);
|
|
10360
|
+
// @ts-expect-error uuidToId accepts only a validated Uuid.
|
|
10361
|
+
uuidToId("0190a6f4-8c3e-7b2a-9d41-5e6f7a8b9c0d");
|
|
10362
|
+
});
|
|
10363
|
+
});
|
|
10198
10364
|
});
|
|
10199
10365
|
|
|
10200
10366
|
describe("Int64String", () => {
|
package/src/Type.ts
CHANGED
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
* ```
|
|
195
195
|
*
|
|
196
196
|
* Evolu includes dozens of predefined Types and Type factories. Use Types such
|
|
197
|
-
* as {@link Age}, {@link PositiveInt}, {@link DateIso},
|
|
197
|
+
* as {@link Age}, {@link PositiveInt}, {@link DateIso}, {@link Email}, {@link Uuid},
|
|
198
198
|
* {@link NonEmptyTrimmedString100}, {@link Base64Url}, and {@link Json} directly.
|
|
199
199
|
* Build domain Types with factories such as {@link brand}, {@link typed},
|
|
200
200
|
* {@link minLength}, {@link maxLength}, {@link array}, {@link object},
|
|
@@ -448,7 +448,7 @@
|
|
|
448
448
|
*
|
|
449
449
|
* @module
|
|
450
450
|
*/
|
|
451
|
-
import { utf8ToBytes } from "@noble/ciphers/utils.js";
|
|
451
|
+
import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/ciphers/utils.js";
|
|
452
452
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
453
453
|
import * as bip39 from "@scure/bip39";
|
|
454
454
|
import { wordlist } from "@scure/bip39/wordlists/english.js";
|
|
@@ -7598,6 +7598,74 @@ export const Name = /*#__PURE__*/ brand(
|
|
|
7598
7598
|
);
|
|
7599
7599
|
export type Name = typeof Name.Output;
|
|
7600
7600
|
|
|
7601
|
+
/**
|
|
7602
|
+
* Error returned when a string is not a valid {@link Email}.
|
|
7603
|
+
*
|
|
7604
|
+
* @group String
|
|
7605
|
+
*/
|
|
7606
|
+
export interface EmailError extends TypeError<"Email"> {
|
|
7607
|
+
readonly value: string;
|
|
7608
|
+
}
|
|
7609
|
+
|
|
7610
|
+
/**
|
|
7611
|
+
* Email address as defined by the WHATWG HTML Standard.
|
|
7612
|
+
*
|
|
7613
|
+
* Email accepts a [valid email
|
|
7614
|
+
* address](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address),
|
|
7615
|
+
* the syntax browsers enforce for `<input type="email">`. The HTML Standard
|
|
7616
|
+
* deliberately departs from RFC 5322: it rejects quoted local parts, comments,
|
|
7617
|
+
* IP address literals, and whitespace, and it accepts only ASCII. Use the ASCII
|
|
7618
|
+
* (`xn--`) form of internationalized domains.
|
|
7619
|
+
*
|
|
7620
|
+
* Email does not normalize. The local part is case-sensitive, so different
|
|
7621
|
+
* Email strings can still reach the same mailbox, and only a delivered message
|
|
7622
|
+
* proves that an address exists.
|
|
7623
|
+
*
|
|
7624
|
+
* The HTML Standard limits each domain label to 63 characters but sets no total
|
|
7625
|
+
* length. SMTP limits an address to 254 characters; compose
|
|
7626
|
+
* `maxLength(254)(Email)` when that limit matters.
|
|
7627
|
+
*
|
|
7628
|
+
* ### Example
|
|
7629
|
+
*
|
|
7630
|
+
* ```ts
|
|
7631
|
+
* import {
|
|
7632
|
+
* assertEqual,
|
|
7633
|
+
* assertErr,
|
|
7634
|
+
* assertOk,
|
|
7635
|
+
* Email,
|
|
7636
|
+
* maxLength,
|
|
7637
|
+
* } from "@evolu/common";
|
|
7638
|
+
*
|
|
7639
|
+
* assertOk(Email.fromUnknown("ada@example.com"), "ada@example.com");
|
|
7640
|
+
* assertOk(Email.fromUnknown("Ada@Example.com"), "Ada@Example.com");
|
|
7641
|
+
*
|
|
7642
|
+
* const invalid = Email.fromUnknown("Ada <ada@example.com>");
|
|
7643
|
+
* assertErr(invalid);
|
|
7644
|
+
* assertEqual(invalid.error, {
|
|
7645
|
+
* type: "Email",
|
|
7646
|
+
* value: "Ada <ada@example.com>",
|
|
7647
|
+
* });
|
|
7648
|
+
*
|
|
7649
|
+
* const SmtpEmail = maxLength(254)(Email);
|
|
7650
|
+
* assertErr(SmtpEmail.fromUnknown(`${"a".repeat(251)}@b.c`));
|
|
7651
|
+
* ```
|
|
7652
|
+
*
|
|
7653
|
+
* @group String
|
|
7654
|
+
*/
|
|
7655
|
+
export const Email = /*#__PURE__*/ brand(
|
|
7656
|
+
"Email",
|
|
7657
|
+
String,
|
|
7658
|
+
(value) =>
|
|
7659
|
+
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/u.test(
|
|
7660
|
+
value,
|
|
7661
|
+
)
|
|
7662
|
+
? ok()
|
|
7663
|
+
: err<EmailError>({ type: "Email", value }),
|
|
7664
|
+
(error) =>
|
|
7665
|
+
`The value ${safelyStringifyUnknownValue(error.value)} is not a valid email address.`,
|
|
7666
|
+
);
|
|
7667
|
+
export type Email = typeof Email.Output;
|
|
7668
|
+
|
|
7601
7669
|
/**
|
|
7602
7670
|
* Stable valid {@link Name} for tests and internal fixtures.
|
|
7603
7671
|
*
|
|
@@ -7967,6 +8035,134 @@ export const idToIdBytes = (value: Id): IdBytes =>
|
|
|
7967
8035
|
export const idBytesToId = (value: IdBytes): Id =>
|
|
7968
8036
|
uint8ArrayToBase64Url(value) as unknown as Id;
|
|
7969
8037
|
|
|
8038
|
+
/**
|
|
8039
|
+
* Error returned when a string is not a canonical {@link Uuid}.
|
|
8040
|
+
*
|
|
8041
|
+
* @group String
|
|
8042
|
+
*/
|
|
8043
|
+
export interface UuidError extends TypeError<"Uuid"> {
|
|
8044
|
+
readonly value: string;
|
|
8045
|
+
}
|
|
8046
|
+
|
|
8047
|
+
/**
|
|
8048
|
+
* UUID in its canonical lowercase text form, as defined by [RFC
|
|
8049
|
+
* 9562](https://www.rfc-editor.org/rfc/rfc9562).
|
|
8050
|
+
*
|
|
8051
|
+
* Uuid accepts 32 lowercase hexadecimal digits in the 8-4-4-4-12 layout, as
|
|
8052
|
+
* produced by `crypto.randomUUID()`. It accepts every version and variant,
|
|
8053
|
+
* including the Nil and Max UUIDs, so every 128-bit value has exactly one
|
|
8054
|
+
* Uuid.
|
|
8055
|
+
*
|
|
8056
|
+
* RFC 9562 reads UUIDs in any case. Uuid requires lowercase so that equal UUIDs
|
|
8057
|
+
* are equal strings; lowercase text from other sources before validating it.
|
|
8058
|
+
*
|
|
8059
|
+
* Convert a Uuid to an {@link Id} with {@link uuidToId} and back with
|
|
8060
|
+
* {@link idToUuid}.
|
|
8061
|
+
*
|
|
8062
|
+
* ### Example
|
|
8063
|
+
*
|
|
8064
|
+
* ```ts
|
|
8065
|
+
* import { assertEqual, assertErr, assertOk, Uuid } from "@evolu/common";
|
|
8066
|
+
*
|
|
8067
|
+
* const value = "0190a6f4-8c3e-7b2a-9d41-5e6f7a8b9c0d";
|
|
8068
|
+
* assertOk(Uuid.fromUnknown(value), value);
|
|
8069
|
+
*
|
|
8070
|
+
* const uppercase = value.toUpperCase();
|
|
8071
|
+
* const invalid = Uuid.fromUnknown(uppercase);
|
|
8072
|
+
* assertErr(invalid);
|
|
8073
|
+
* assertEqual(invalid.error, { type: "Uuid", value: uppercase });
|
|
8074
|
+
* assertOk(Uuid.fromUnknown(uppercase.toLowerCase()), value);
|
|
8075
|
+
* ```
|
|
8076
|
+
*
|
|
8077
|
+
* @group String
|
|
8078
|
+
*/
|
|
8079
|
+
export const Uuid = /*#__PURE__*/ brand(
|
|
8080
|
+
"Uuid",
|
|
8081
|
+
String,
|
|
8082
|
+
(value) =>
|
|
8083
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.test(
|
|
8084
|
+
value,
|
|
8085
|
+
)
|
|
8086
|
+
? ok()
|
|
8087
|
+
: err<UuidError>({ type: "Uuid", value }),
|
|
8088
|
+
(error) =>
|
|
8089
|
+
`The value ${safelyStringifyUnknownValue(error.value)} is not a canonical lowercase UUID.`,
|
|
8090
|
+
);
|
|
8091
|
+
export type Uuid = typeof Uuid.Output;
|
|
8092
|
+
|
|
8093
|
+
/**
|
|
8094
|
+
* Converts a {@link Uuid} to the {@link Id} with the same 16 bytes.
|
|
8095
|
+
*
|
|
8096
|
+
* Use this to store records whose external keys are UUIDs. Unlike
|
|
8097
|
+
* {@link createIdFromString}, the mapping is reversible with {@link idToUuid}, so
|
|
8098
|
+
* the original UUID does not need its own column. A time-based UUID, such as
|
|
8099
|
+
* version 1, 6, or 7, keeps its creation time in the Id.
|
|
8100
|
+
*
|
|
8101
|
+
* ### Example
|
|
8102
|
+
*
|
|
8103
|
+
* ```ts
|
|
8104
|
+
* import {
|
|
8105
|
+
* assertEqual,
|
|
8106
|
+
* assertType,
|
|
8107
|
+
* idToUuid,
|
|
8108
|
+
* Uuid,
|
|
8109
|
+
* uuidToId,
|
|
8110
|
+
* type Brand,
|
|
8111
|
+
* type Id,
|
|
8112
|
+
* } from "@evolu/common";
|
|
8113
|
+
*
|
|
8114
|
+
* const uuid = Uuid.orThrow("0190a6f4-8c3e-7b2a-9d41-5e6f7a8b9c0d");
|
|
8115
|
+
* const todoId = uuidToId<"Todo">(uuid);
|
|
8116
|
+
*
|
|
8117
|
+
* assertEqual(todoId, "AZCm9Iw-eyqdQV5veoucDQ");
|
|
8118
|
+
* assertType<typeof todoId, Id & Brand<"Todo">>();
|
|
8119
|
+
* assertEqual(idToUuid(todoId), uuid);
|
|
8120
|
+
* ```
|
|
8121
|
+
*
|
|
8122
|
+
* @group String
|
|
8123
|
+
*/
|
|
8124
|
+
export const uuidToId = <B extends string = never>(
|
|
8125
|
+
value: Uuid,
|
|
8126
|
+
..._validation: IdBrandValidation<B>
|
|
8127
|
+
): CreatedId<B> =>
|
|
8128
|
+
idBytesToId(hexToBytes(value.replaceAll("-", "")) as IdBytes) as CreatedId<B>;
|
|
8129
|
+
|
|
8130
|
+
/**
|
|
8131
|
+
* Converts an {@link Id} to the {@link Uuid} with the same 16 bytes.
|
|
8132
|
+
*
|
|
8133
|
+
* The result is a Uuid of any version or variant, because Ids created by
|
|
8134
|
+
* {@link createId} are random 128-bit values. Ids created by
|
|
8135
|
+
* {@link createIdAsUuidv7} convert to version 7 UUIDs.
|
|
8136
|
+
*
|
|
8137
|
+
* ### Example
|
|
8138
|
+
*
|
|
8139
|
+
* ```ts
|
|
8140
|
+
* import {
|
|
8141
|
+
* assertEqual,
|
|
8142
|
+
* createIdAsUuidv7,
|
|
8143
|
+
* createRandomBytes,
|
|
8144
|
+
* createTime,
|
|
8145
|
+
* idToUuid,
|
|
8146
|
+
* uuidToId,
|
|
8147
|
+
* } from "@evolu/common";
|
|
8148
|
+
*
|
|
8149
|
+
* const id = createIdAsUuidv7({
|
|
8150
|
+
* randomBytes: createRandomBytes(),
|
|
8151
|
+
* time: createTime(),
|
|
8152
|
+
* });
|
|
8153
|
+
* const uuid = idToUuid(id);
|
|
8154
|
+
*
|
|
8155
|
+
* assertEqual(uuid[14], "7");
|
|
8156
|
+
* assertEqual(uuidToId(uuid), id);
|
|
8157
|
+
* ```
|
|
8158
|
+
*
|
|
8159
|
+
* @group String
|
|
8160
|
+
*/
|
|
8161
|
+
export const idToUuid = (value: Id): Uuid => {
|
|
8162
|
+
const hex = bytesToHex(idToIdBytes(value));
|
|
8163
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}` as Uuid;
|
|
8164
|
+
};
|
|
8165
|
+
|
|
7970
8166
|
/**
|
|
7971
8167
|
* Error returned when a string is not a canonical {@link Int64String}.
|
|
7972
8168
|
*
|
package/src/WebSocket.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { assert } from "./Assert.ts";
|
|
8
8
|
import { constTrue } from "./Function.ts";
|
|
9
9
|
import type { Result } from "./Result.ts";
|
|
10
|
-
import { err, ok } from "./Result.ts";
|
|
10
|
+
import { err, ok, trySync } from "./Result.ts";
|
|
11
11
|
import type { Schedule } from "./Schedule.ts";
|
|
12
12
|
import { exponential, jitter, maxDelay } from "./Schedule.ts";
|
|
13
13
|
import type { RetryError, Task } from "./Task.ts";
|
|
@@ -266,10 +266,24 @@ export interface WebSocketOptions {
|
|
|
266
266
|
* @group Errors
|
|
267
267
|
*/
|
|
268
268
|
export type WebSocketError =
|
|
269
|
+
| WebSocketCreateError
|
|
269
270
|
| WebSocketConnectError
|
|
270
271
|
| WebSocketConnectionError
|
|
271
272
|
| RetryError<WebSocketRetryError>;
|
|
272
273
|
|
|
274
|
+
/**
|
|
275
|
+
* An error that occurs when the platform's WebSocket constructor throws, for
|
|
276
|
+
* example for a URL it rejects, a `ws:` URL on an `https:` page, or a Content
|
|
277
|
+
* Security Policy that blocks the URL. These fail the same way on every
|
|
278
|
+
* attempt, so the {@link WebSocket} stops connecting. The thrown value is not
|
|
279
|
+
* always an `Error`.
|
|
280
|
+
*
|
|
281
|
+
* @group Errors
|
|
282
|
+
*/
|
|
283
|
+
export interface WebSocketCreateError extends Typed<"WebSocketCreateError"> {
|
|
284
|
+
readonly error: unknown;
|
|
285
|
+
}
|
|
286
|
+
|
|
273
287
|
/**
|
|
274
288
|
* An error that occurs when a connection cannot be established due to a network
|
|
275
289
|
* error. Fires before `onclose`.
|
|
@@ -344,6 +358,9 @@ const defaultHealthyConnectionDuration = /*#__PURE__*/ durationToMillis("30s");
|
|
|
344
358
|
/**
|
|
345
359
|
* Create a new {@link WebSocket}.
|
|
346
360
|
*
|
|
361
|
+
* When the platform's WebSocket constructor throws, `onError` receives a
|
|
362
|
+
* {@link WebSocketCreateError} and the WebSocket stops connecting.
|
|
363
|
+
*
|
|
347
364
|
* @group Core
|
|
348
365
|
*/
|
|
349
366
|
export const createWebSocket: CreateWebSocket =
|
|
@@ -403,10 +420,22 @@ export const createWebSocket: CreateWebSocket =
|
|
|
403
420
|
closeSocket();
|
|
404
421
|
resolveConnect = resolve;
|
|
405
422
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
423
|
+
const created = trySync(
|
|
424
|
+
() =>
|
|
425
|
+
new WebSocketConstructor(
|
|
426
|
+
url,
|
|
427
|
+
String.is(protocols) ? protocols : protocols && [...protocols],
|
|
428
|
+
),
|
|
409
429
|
);
|
|
430
|
+
// A throw that escaped this Task would panic the Run, which in Evolu's
|
|
431
|
+
// SharedWorker stops sync for every tab.
|
|
432
|
+
if (!created.ok) {
|
|
433
|
+
resolveConnect = null;
|
|
434
|
+
onError?.({ type: "WebSocketCreateError", error: created.error });
|
|
435
|
+
resolve(ok());
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
socket = created.value;
|
|
410
439
|
|
|
411
440
|
if (binaryType) socket.binaryType = binaryType;
|
|
412
441
|
|