@evolu/common 8.0.0-next.5 → 8.0.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/dist/src/Array.d.ts +279 -173
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +105 -101
- package/dist/src/Assert.d.ts +39 -44
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +38 -31
- package/dist/src/BigInt.d.ts +6 -3
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -2
- package/dist/src/Brand.d.ts +27 -40
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Buffer.d.ts +27 -11
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Cache.d.ts +9 -4
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +8 -3
- package/dist/src/Callbacks.d.ts +30 -19
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Console.d.ts +145 -66
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +91 -43
- package/dist/src/Crypto.d.ts +85 -42
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +44 -20
- package/dist/src/Eq.d.ts +87 -21
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +67 -19
- package/dist/src/Error.d.ts +3 -3
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Function.d.ts +119 -58
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +86 -43
- package/dist/src/Http.d.ts +394 -0
- package/dist/src/Http.d.ts.map +1 -0
- package/dist/src/Http.js +102 -0
- package/dist/src/Identicon.d.ts +16 -7
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +14 -5
- package/dist/src/LeakDetector.d.ts +88 -0
- package/dist/src/LeakDetector.d.ts.map +1 -0
- package/dist/src/LeakDetector.js +86 -0
- package/dist/src/LockManager.d.ts +18 -9
- package/dist/src/LockManager.d.ts.map +1 -1
- package/dist/src/LockManager.js +7 -8
- package/dist/src/Lookup.d.ts +43 -13
- package/dist/src/Lookup.d.ts.map +1 -1
- package/dist/src/Lookup.js +28 -8
- package/dist/src/Microtask.d.ts +1 -1
- package/dist/src/Microtask.d.ts.map +1 -1
- package/dist/src/Microtask.js +3 -7
- package/dist/src/Number.d.ts +49 -13
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +34 -5
- package/dist/src/Object.d.ts +160 -40
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +92 -46
- package/dist/src/Option.d.ts +10 -7
- package/dist/src/Option.d.ts.map +1 -1
- package/dist/src/Option.js +9 -6
- package/dist/src/Order.d.ts +21 -21
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +18 -21
- package/dist/src/Platform.d.ts +31 -0
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +98 -0
- package/dist/src/Random.d.ts +32 -17
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +12 -8
- package/dist/src/Redacted.d.ts +43 -29
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +14 -3
- package/dist/src/Ref.d.ts +11 -3
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +2 -2
- package/dist/src/RefCount.d.ts +2 -6
- package/dist/src/RefCount.d.ts.map +1 -1
- package/dist/src/RefCount.js +33 -90
- package/dist/src/Relation.d.ts +70 -5
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +77 -2
- package/dist/src/Resource.d.ts +590 -155
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +592 -343
- package/dist/src/Result.d.ts +838 -191
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +163 -102
- package/dist/src/Schedule.d.ts +684 -231
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Schedule.js +766 -295
- package/dist/src/Set.d.ts +89 -40
- package/dist/src/Set.d.ts.map +1 -1
- package/dist/src/Set.js +50 -29
- package/dist/src/Sqlite.d.ts +29 -72
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +39 -45
- package/dist/src/StackTrace.d.ts +40 -0
- package/dist/src/StackTrace.d.ts.map +1 -0
- package/dist/src/StackTrace.js +140 -0
- package/dist/src/Store.d.ts +5 -4
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +5 -4
- package/dist/src/String.d.ts +7 -1
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +20 -7
- package/dist/src/Task.d.ts +3428 -1629
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1876 -1279
- package/dist/src/Test.d.ts +17 -124
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +17 -68
- package/dist/src/Time.d.ts +108 -49
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +172 -73
- package/dist/src/Type.d.ts +3542 -1926
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +3070 -1784
- package/dist/src/Types.d.ts +200 -100
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +39 -8
- package/dist/src/WebSocket.d.ts +48 -23
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +15 -6
- package/dist/src/Worker.d.ts +94 -36
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +58 -24
- package/dist/src/index.d.ts +55 -52
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/intl/cs.d.ts +94 -0
- package/dist/src/intl/cs.d.ts.map +1 -0
- package/dist/src/intl/cs.js +220 -0
- package/dist/src/intl/index.d.ts +7 -0
- package/dist/src/intl/index.d.ts.map +1 -0
- package/dist/src/intl/index.js +6 -0
- package/dist/src/local-first/Db.d.ts +12 -8
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +107 -130
- package/dist/src/local-first/Error.d.ts +4 -4
- package/dist/src/local-first/Evolu.d.ts +338 -92
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +47 -55
- package/dist/src/local-first/LocalAuth.d.ts +4 -4
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/Owner.d.ts +61 -24
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +45 -8
- package/dist/src/local-first/Protocol.d.ts +44 -31
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +36 -13
- package/dist/src/local-first/Query.d.ts +147 -62
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +112 -58
- package/dist/src/local-first/Relay.d.ts +44 -14
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Schema.d.ts +92 -41
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +25 -8
- package/dist/src/local-first/Shared.d.ts +23 -21
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +85 -42
- package/dist/src/local-first/Storage.d.ts +53 -32
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +194 -234
- package/dist/src/local-first/Timestamp.d.ts +21 -21
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +2 -2
- package/dist/src/local-first/index.d.ts +11 -11
- package/package.json +14 -13
- package/src/Array.ts +306 -205
- package/src/Assert.ts +48 -68
- package/src/BigInt.ts +6 -3
- package/src/Brand.ts +27 -40
- package/src/Buffer.ts +28 -12
- package/src/Cache.ts +9 -4
- package/src/Callbacks.ts +30 -19
- package/src/Console.ts +156 -84
- package/src/Crypto.ts +83 -41
- package/src/Eq.ts +95 -22
- package/src/Error.ts +5 -2
- package/src/Function.ts +167 -59
- package/src/Http.ts +549 -0
- package/src/Identicon.ts +16 -7
- package/src/LeakDetector.ts +182 -0
- package/src/LockManager.ts +43 -39
- package/src/Lookup.ts +62 -28
- package/src/Microtask.ts +19 -23
- package/src/Number.ts +64 -12
- package/src/Object.ts +173 -49
- package/src/Option.ts +10 -7
- package/src/Order.ts +23 -21
- package/src/Platform.ts +136 -0
- package/src/Polyfills.ts +6 -12
- package/src/Random.ts +32 -19
- package/src/Redacted.ts +44 -30
- package/src/Ref.ts +14 -6
- package/src/RefCount.ts +38 -80
- package/src/Relation.ts +202 -10
- package/src/Resource.ts +1199 -591
- package/src/Result.ts +930 -271
- package/src/Schedule.ts +859 -343
- package/src/Set.ts +89 -40
- package/src/Sqlite.ts +142 -137
- package/src/StackTrace.ts +230 -0
- package/src/Store.ts +8 -7
- package/src/String.ts +22 -5
- package/src/Task.ts +5109 -2845
- package/src/Test.ts +20 -167
- package/src/Time.ts +293 -90
- package/src/Type.ts +10034 -4117
- package/src/Types.ts +227 -101
- package/src/WebSocket.ts +64 -36
- package/src/Worker.ts +180 -72
- package/src/index.ts +55 -52
- package/src/intl/cs.ts +388 -0
- package/src/intl/index.ts +7 -0
- package/src/local-first/Db.ts +161 -190
- package/src/local-first/Error.ts +4 -4
- package/src/local-first/Evolu.ts +495 -250
- package/src/local-first/LocalAuth.ts +6 -12
- package/src/local-first/Owner.ts +58 -21
- package/src/local-first/Protocol.ts +49 -31
- package/src/local-first/Query.ts +146 -61
- package/src/local-first/Relay.ts +51 -21
- package/src/local-first/Schema.ts +109 -51
- package/src/local-first/Shared.ts +253 -214
- package/src/local-first/Storage.ts +266 -264
- package/src/local-first/Timestamp.ts +25 -18
- package/src/local-first/index.ts +11 -11
- package/dist/src/Skiplist.d.ts +0 -28
- package/dist/src/Skiplist.d.ts.map +0 -1
- package/dist/src/Skiplist.js +0 -64
- package/dist/src/Tracer.d.ts +0 -48
- package/dist/src/Tracer.d.ts.map +0 -1
- package/dist/src/Tracer.js +0 -6
- package/src/Skiplist.ts +0 -108
- package/src/Tracer.ts +0 -54
package/dist/src/Http.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task-aware HTTP helpers.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { err, ok, tryAsync } from "./Result.js";
|
|
7
|
+
export function fetch(input, modeOrConsume, init) {
|
|
8
|
+
return async (run) => {
|
|
9
|
+
const { signal } = run;
|
|
10
|
+
// Abort control flow wins over step errors, normalizing host abort errors
|
|
11
|
+
// from native fetch, body reads, body cancel, and consumers.
|
|
12
|
+
const orAbort = (error) => {
|
|
13
|
+
signal.throwIfAborted();
|
|
14
|
+
return error;
|
|
15
|
+
};
|
|
16
|
+
const fetched = await tryAsync(() => run.deps.nativeFetch(input, { ...init, signal }), orAbort);
|
|
17
|
+
let result;
|
|
18
|
+
if (!fetched.ok) {
|
|
19
|
+
result = err({ type: "FetchTransportError", error: fetched.error });
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const response = fetched.value;
|
|
23
|
+
// A response that arrives after abort is not processed: cancel its body
|
|
24
|
+
// best-effort, then let abort win before any mode or consumer runs.
|
|
25
|
+
if (signal.aborted) {
|
|
26
|
+
await tryAsync(() => response.body?.cancel());
|
|
27
|
+
signal.throwIfAborted();
|
|
28
|
+
}
|
|
29
|
+
if (typeof modeOrConsume === "function") {
|
|
30
|
+
const consumed = await tryAsync(() => modeOrConsume(response), orAbort);
|
|
31
|
+
// A consumer throw is a defect once orAbort has ruled out abort.
|
|
32
|
+
if (!consumed.ok)
|
|
33
|
+
throw consumed.error;
|
|
34
|
+
result = consumed.value;
|
|
35
|
+
}
|
|
36
|
+
else if (modeOrConsume === "headers") {
|
|
37
|
+
// The head is already a value; body cancel failures are irrelevant.
|
|
38
|
+
await tryAsync(() => response.body?.cancel(), orAbort);
|
|
39
|
+
result = ok(response);
|
|
40
|
+
}
|
|
41
|
+
else if (!response.ok) {
|
|
42
|
+
result = err({
|
|
43
|
+
type: "FetchStatusError",
|
|
44
|
+
response,
|
|
45
|
+
// Error bodies are diagnostics; text preserves proxy/HTML/plain responses across modes.
|
|
46
|
+
body: await tryAsync(() => response.text(), orAbort),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const body = await tryAsync(() => {
|
|
51
|
+
switch (modeOrConsume) {
|
|
52
|
+
case "text":
|
|
53
|
+
return response.text();
|
|
54
|
+
case "json":
|
|
55
|
+
return response.json();
|
|
56
|
+
case "bytes":
|
|
57
|
+
return response.bytes();
|
|
58
|
+
}
|
|
59
|
+
}, orAbort);
|
|
60
|
+
result = body.ok
|
|
61
|
+
? body
|
|
62
|
+
: err({ type: "FetchBodyError", response, error: body.error });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// An Err produced after abort is treated as abort control flow.
|
|
66
|
+
if (!result.ok)
|
|
67
|
+
signal.throwIfAborted();
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Creates {@link TestNativeFetch}.
|
|
73
|
+
*
|
|
74
|
+
* @group Testing
|
|
75
|
+
*/
|
|
76
|
+
export const testCreateNativeFetch = (...handlers) => {
|
|
77
|
+
const calls = [];
|
|
78
|
+
const handlerQueue = [...handlers];
|
|
79
|
+
return Object.assign((input, init) => {
|
|
80
|
+
calls.push({ input, init });
|
|
81
|
+
const handler = handlerQueue.shift();
|
|
82
|
+
if (!handler)
|
|
83
|
+
throw new Error("Unexpected NativeFetch call");
|
|
84
|
+
return Promise.resolve(handler(input, init));
|
|
85
|
+
}, {
|
|
86
|
+
calls,
|
|
87
|
+
handle: (handler) => {
|
|
88
|
+
handlerQueue.push(handler);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Creates a Response body for {@link NativeFetch} tests that errors on first
|
|
94
|
+
* read.
|
|
95
|
+
*
|
|
96
|
+
* @group Testing
|
|
97
|
+
*/
|
|
98
|
+
export const testCreateNativeFetchErroringBody = (error = new Error("stream failed")) => new ReadableStream({
|
|
99
|
+
pull: (controller) => {
|
|
100
|
+
controller.error(error);
|
|
101
|
+
},
|
|
102
|
+
});
|
package/dist/src/Identicon.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import type { Brand } from "./Brand.
|
|
7
|
-
import { Id } from "./Type.
|
|
6
|
+
import type { Brand } from "./Brand.ts";
|
|
7
|
+
import { Id } from "./Type.ts";
|
|
8
8
|
/**
|
|
9
9
|
* SVG string representing a visual identicon for an {@link Id}, created with
|
|
10
10
|
* {@link createIdenticon}.
|
|
@@ -27,13 +27,22 @@ export type IdenticonStyle = "github" | "quadrant" | "gradient" | "sutnar";
|
|
|
27
27
|
* ### Example
|
|
28
28
|
*
|
|
29
29
|
* ```ts
|
|
30
|
+
* import { createIdFromString, createIdenticon } from "@evolu/common";
|
|
31
|
+
*
|
|
32
|
+
* const id = createIdFromString("identicon-example");
|
|
30
33
|
* const svg = createIdenticon(id);
|
|
31
|
-
* const
|
|
32
|
-
* const
|
|
33
|
-
*
|
|
34
|
+
* const styles = ["quadrant", "gradient", "sutnar"] as const;
|
|
35
|
+
* const alternativeSvgs = styles.map((style) =>
|
|
36
|
+
* createIdenticon(id, style),
|
|
37
|
+
* );
|
|
38
|
+
*
|
|
39
|
+
* // Branded IDs work too.
|
|
40
|
+
* const todoId = createIdFromString<"Todo">("todo-1");
|
|
41
|
+
* const todoSvg = createIdenticon(todoId);
|
|
34
42
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
43
|
+
* expect(svg.startsWith("<svg")).toBe(true);
|
|
44
|
+
* expect(new Set([svg, ...alternativeSvgs]).size).toBe(4);
|
|
45
|
+
* expect(todoSvg.startsWith("<svg")).toBe(true);
|
|
37
46
|
* ```
|
|
38
47
|
*/
|
|
39
48
|
export declare const createIdenticon: (id: Id, style?: IdenticonStyle) => Identicon;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Identicon.d.ts","sourceRoot":"","sources":["../../src/Identicon.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,EAAE,EAAe,MAAM,WAAW,CAAC;AAE5C;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;AAEpD,+BAA+B;AAC/B,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE3E
|
|
1
|
+
{"version":3,"file":"Identicon.d.ts","sourceRoot":"","sources":["../../src/Identicon.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,EAAE,EAAe,MAAM,WAAW,CAAC;AAE5C;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;AAEpD,+BAA+B;AAC/B,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,eAAe,OACtB,EAAE,UACC,cAAc,KACpB,SA4JF,CAAC"}
|
package/dist/src/Identicon.js
CHANGED
|
@@ -20,13 +20,22 @@ import { Id, idToIdBytes } from "./Type.js";
|
|
|
20
20
|
* ### Example
|
|
21
21
|
*
|
|
22
22
|
* ```ts
|
|
23
|
+
* import { createIdFromString, createIdenticon } from "@evolu/common";
|
|
24
|
+
*
|
|
25
|
+
* const id = createIdFromString("identicon-example");
|
|
23
26
|
* const svg = createIdenticon(id);
|
|
24
|
-
* const
|
|
25
|
-
* const
|
|
26
|
-
*
|
|
27
|
+
* const styles = ["quadrant", "gradient", "sutnar"] as const;
|
|
28
|
+
* const alternativeSvgs = styles.map((style) =>
|
|
29
|
+
* createIdenticon(id, style),
|
|
30
|
+
* );
|
|
31
|
+
*
|
|
32
|
+
* // Branded IDs work too.
|
|
33
|
+
* const todoId = createIdFromString<"Todo">("todo-1");
|
|
34
|
+
* const todoSvg = createIdenticon(todoId);
|
|
27
35
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
36
|
+
* expect(svg.startsWith("<svg")).toBe(true);
|
|
37
|
+
* expect(new Set([svg, ...alternativeSvgs]).size).toBe(4);
|
|
38
|
+
* expect(todoSvg.startsWith("<svg")).toBe(true);
|
|
30
39
|
* ```
|
|
31
40
|
*/
|
|
32
41
|
export const createIdenticon = (id, style = "github") => {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Development-time detection of leaked handles via garbage collection.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { type ConsoleDep } from "./Console.ts";
|
|
7
|
+
/**
|
|
8
|
+
* Detects handles that are garbage-collected while still considered held.
|
|
9
|
+
*
|
|
10
|
+
* A handle such as a Lease must be explicitly cleaned up. Nothing in JavaScript
|
|
11
|
+
* enforces that, so a handle whose last reference is dropped without cleanup
|
|
12
|
+
* leaks silently. Garbage collection proves the leak: once the handle is
|
|
13
|
+
* unreachable, no code can ever clean it up. A LeakDetector observes collection
|
|
14
|
+
* with `FinalizationRegistry` and warns when the collected handle was still
|
|
15
|
+
* held — a proven leak with zero false positives, pointing at the tracking
|
|
16
|
+
* site.
|
|
17
|
+
*
|
|
18
|
+
* Detection is best-effort: garbage collection timing is not deterministic, so
|
|
19
|
+
* a warning may come late or, in short-lived processes, never. It is a
|
|
20
|
+
* development canary, not a guarantee. Production uses
|
|
21
|
+
* {@link noopLeakDetector}.
|
|
22
|
+
*/
|
|
23
|
+
export interface LeakDetector {
|
|
24
|
+
/**
|
|
25
|
+
* Starts tracking `target` until {@link LeakDetector.untrack | untrack} or
|
|
26
|
+
* garbage collection.
|
|
27
|
+
*
|
|
28
|
+
* When `target` is garbage-collected and {@link Leak.isLeaked} returns `true`,
|
|
29
|
+
* the leak is reported with the stack captured at this call. The leak must
|
|
30
|
+
* not reference `target`, or the target would never become unreachable.
|
|
31
|
+
*/
|
|
32
|
+
readonly track: (target: object, leak: Leak, unregisterToken: object) => void;
|
|
33
|
+
/** Stops tracking the target registered with `unregisterToken`. */
|
|
34
|
+
readonly untrack: (unregisterToken: object) => void;
|
|
35
|
+
}
|
|
36
|
+
/** Describes a tracked handle for {@link LeakDetector.track}. */
|
|
37
|
+
export interface Leak {
|
|
38
|
+
/** Handle name used in the warning, for example `"Lease"`. */
|
|
39
|
+
readonly name: string;
|
|
40
|
+
/** Returns whether the collected handle was still considered held. */
|
|
41
|
+
readonly isLeaked: () => boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Dependency wrapper for {@link LeakDetector}.
|
|
45
|
+
*
|
|
46
|
+
* @see {@link LeakDetector}
|
|
47
|
+
*/
|
|
48
|
+
export interface LeakDetectorDep {
|
|
49
|
+
readonly leakDetector: LeakDetector;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Creates {@link LeakDetector} backed by `FinalizationRegistry`.
|
|
53
|
+
*
|
|
54
|
+
* Capturing a stack per track call is too expensive for production; use
|
|
55
|
+
* {@link noopLeakDetector} there.
|
|
56
|
+
*/
|
|
57
|
+
export declare const createLeakDetector: (deps: ConsoleDep) => LeakDetector;
|
|
58
|
+
/** No-op {@link LeakDetector} for production. */
|
|
59
|
+
export declare const noopLeakDetector: LeakDetector;
|
|
60
|
+
/**
|
|
61
|
+
* Test {@link LeakDetector} with deterministic collection.
|
|
62
|
+
*
|
|
63
|
+
* @see {@link testCreateLeakDetector}
|
|
64
|
+
*/
|
|
65
|
+
export interface TestLeakDetector extends LeakDetector {
|
|
66
|
+
/**
|
|
67
|
+
* Simulates garbage collection of all tracked targets.
|
|
68
|
+
*
|
|
69
|
+
* Reports every tracked handle that is still held and clears tracking.
|
|
70
|
+
* Returns the number of reported leaks.
|
|
71
|
+
*/
|
|
72
|
+
readonly collect: () => number;
|
|
73
|
+
/** Returns the number of currently tracked targets. */
|
|
74
|
+
readonly getTrackedCount: (options?: {
|
|
75
|
+
name?: string;
|
|
76
|
+
}) => number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Dependency wrapper for {@link TestLeakDetector}.
|
|
80
|
+
*
|
|
81
|
+
* @see {@link TestLeakDetector}
|
|
82
|
+
*/
|
|
83
|
+
export interface TestLeakDetectorDep extends LeakDetectorDep {
|
|
84
|
+
readonly leakDetector: TestLeakDetector;
|
|
85
|
+
}
|
|
86
|
+
/** Creates {@link TestLeakDetector}. */
|
|
87
|
+
export declare const testCreateLeakDetector: (deps: ConsoleDep) => TestLeakDetector;
|
|
88
|
+
//# sourceMappingURL=LeakDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LeakDetector.d.ts","sourceRoot":"","sources":["../../src/LeakDetector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;IAE9E,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AAED,iEAAiE;AACjE,MAAM,WAAW,IAAI;IACnB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;CACrC;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,SAAU,UAAU,KAAG,YAoBrD,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,EAAE,YAG9B,CAAC;AAyBF;;;;GAIG;AACH,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC;IAE/B,uDAAuD;IACvD,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;CACnE;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;CACzC;AAED,wCAAwC;AACxC,eAAO,MAAM,sBAAsB,SAAU,UAAU,KAAG,gBAsCzD,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Development-time detection of leaked handles via garbage collection.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import {} from "./Console.js";
|
|
7
|
+
import { constVoid } from "./Function.js";
|
|
8
|
+
/**
|
|
9
|
+
* Creates {@link LeakDetector} backed by `FinalizationRegistry`.
|
|
10
|
+
*
|
|
11
|
+
* Capturing a stack per track call is too expensive for production; use
|
|
12
|
+
* {@link noopLeakDetector} there.
|
|
13
|
+
*/
|
|
14
|
+
export const createLeakDetector = (deps) => {
|
|
15
|
+
if (typeof globalThis.FinalizationRegistry !== "function")
|
|
16
|
+
return noopLeakDetector;
|
|
17
|
+
const registry = new globalThis.FinalizationRegistry(reportLeak(deps));
|
|
18
|
+
return {
|
|
19
|
+
track: (target, leak, unregisterToken) => {
|
|
20
|
+
registry.register(target, { ...leak, stack: new Error().stack }, unregisterToken);
|
|
21
|
+
},
|
|
22
|
+
untrack: (unregisterToken) => {
|
|
23
|
+
registry.unregister(unregisterToken);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
/** No-op {@link LeakDetector} for production. */
|
|
28
|
+
export const noopLeakDetector = {
|
|
29
|
+
track: constVoid,
|
|
30
|
+
untrack: constVoid,
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Reports a collected handle that was still held.
|
|
34
|
+
*
|
|
35
|
+
* Shared by {@link createLeakDetector} as the `FinalizationRegistry` callback
|
|
36
|
+
* and by {@link testCreateLeakDetector.collect | collect}, so tests cover the
|
|
37
|
+
* same reporting path without forcing garbage collection. Returns whether the
|
|
38
|
+
* leak was reported.
|
|
39
|
+
*/
|
|
40
|
+
const reportLeak = (deps) => (leak) => {
|
|
41
|
+
if (!leak.isLeaked())
|
|
42
|
+
return false;
|
|
43
|
+
deps.console.warn(`${leak.name} was garbage-collected without cleanup. Tracked at:`, leak.stack);
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
46
|
+
/** Creates {@link TestLeakDetector}. */
|
|
47
|
+
export const testCreateLeakDetector = (deps) => {
|
|
48
|
+
const trackedLeaksByToken = new Map();
|
|
49
|
+
const report = reportLeak(deps);
|
|
50
|
+
return {
|
|
51
|
+
track: (_target, leak, unregisterToken) => {
|
|
52
|
+
const trackedLeaks = trackedLeaksByToken.get(unregisterToken) ?? [];
|
|
53
|
+
trackedLeaksByToken.set(unregisterToken, [
|
|
54
|
+
...trackedLeaks,
|
|
55
|
+
{
|
|
56
|
+
...leak,
|
|
57
|
+
stack: new Error().stack,
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
},
|
|
61
|
+
untrack: (unregisterToken) => {
|
|
62
|
+
trackedLeaksByToken.delete(unregisterToken);
|
|
63
|
+
},
|
|
64
|
+
collect: () => {
|
|
65
|
+
let leakCount = 0;
|
|
66
|
+
for (const trackedLeaks of trackedLeaksByToken.values()) {
|
|
67
|
+
for (const leak of trackedLeaks) {
|
|
68
|
+
if (report(leak))
|
|
69
|
+
leakCount++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
trackedLeaksByToken.clear();
|
|
73
|
+
return leakCount;
|
|
74
|
+
},
|
|
75
|
+
getTrackedCount: ({ name } = {}) => {
|
|
76
|
+
let count = 0;
|
|
77
|
+
for (const trackedLeaks of trackedLeaksByToken.values()) {
|
|
78
|
+
for (const leak of trackedLeaks) {
|
|
79
|
+
if (name === undefined || leak.name === name)
|
|
80
|
+
count++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return count;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -3,32 +3,41 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import type { Callback } from "./Types.
|
|
6
|
+
import type { Task } from "./Task.ts";
|
|
7
|
+
import type { Callback } from "./Types.ts";
|
|
8
8
|
/**
|
|
9
9
|
* Dependency wrapper for {@link LockManager}.
|
|
10
10
|
*
|
|
11
11
|
* For React Native, use the `lockManager` ponyfill from `@evolu/react-native`.
|
|
12
12
|
* For tests, use {@link testCreateLockManager}.
|
|
13
13
|
*
|
|
14
|
-
* ###
|
|
14
|
+
* ### Web and Node.js 24+
|
|
15
15
|
*
|
|
16
16
|
* ```ts
|
|
17
|
-
*
|
|
18
|
-
* import { testCreateLockManager } from "@evolu/common";
|
|
17
|
+
* import type { LockManagerDep } from "@evolu/common";
|
|
19
18
|
*
|
|
20
19
|
* const deps: LockManagerDep = {
|
|
21
|
-
* lockManager:
|
|
20
|
+
* lockManager: globalThis.navigator.locks,
|
|
22
21
|
* };
|
|
22
|
+
*
|
|
23
|
+
* const result = await deps.lockManager.request("example", (lock) => {
|
|
24
|
+
* return lock?.name;
|
|
25
|
+
* });
|
|
26
|
+
* expect(result).toBe("example");
|
|
23
27
|
* ```
|
|
24
28
|
*
|
|
25
|
-
* ###
|
|
29
|
+
* ### React Native
|
|
26
30
|
*
|
|
27
31
|
* ```ts
|
|
28
|
-
*
|
|
32
|
+
* import type { LockManagerDep } from "@evolu/common";
|
|
29
33
|
* import { lockManager } from "@evolu/react-native";
|
|
30
34
|
*
|
|
31
35
|
* const deps: LockManagerDep = { lockManager };
|
|
36
|
+
*
|
|
37
|
+
* const result = await deps.lockManager.request("example", (lock) => {
|
|
38
|
+
* return lock?.name;
|
|
39
|
+
* });
|
|
40
|
+
* expect(result).toBe("example");
|
|
32
41
|
* ```
|
|
33
42
|
*/
|
|
34
43
|
export interface LockManagerDep {
|
|
@@ -58,7 +67,7 @@ export declare const testCreateLockManager: (nativeLockManager?: LockManager) =>
|
|
|
58
67
|
* another waiting caller may become the next leader. Waiting for leadership is
|
|
59
68
|
* abortable via the calling {@link Task}'s signal.
|
|
60
69
|
*/
|
|
61
|
-
export declare const acquireLeaderLock: (name: string) => Task<AsyncDisposable,
|
|
70
|
+
export declare const acquireLeaderLock: (name: string) => Task<AsyncDisposable, never, LockManagerDep>;
|
|
62
71
|
/**
|
|
63
72
|
* Competes to become the current leader for `name` using {@link LockManager}.
|
|
64
73
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LockManager.d.ts","sourceRoot":"","sources":["../../src/LockManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,
|
|
1
|
+
{"version":3,"file":"LockManager.d.ts","sourceRoot":"","sources":["../../src/LockManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,uBACb,WAAW,KAC7B,WAwCF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,SACrB,MAAM,KAAG,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,cAAc,CAuB1D,CAAC;AAEJ;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,SAC7B,cAAc,YACd,MAAM,YAAY,QAAQ,CAAC,IAAI,CAAC,KAAG,UAsBzC,CAAC"}
|
package/dist/src/LockManager.js
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
import { createRandomBytes } from "./Crypto.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { AbortError } from "./Task.js";
|
|
7
|
+
import { constVoid } from "./Function.js";
|
|
8
|
+
import { ok } from "./Result.js";
|
|
10
9
|
import { createId } from "./Type.js";
|
|
11
10
|
/**
|
|
12
11
|
* Creates a test {@link LockManager} backed by the native platform
|
|
@@ -56,7 +55,7 @@ export const testCreateLockManager = (nativeLockManager = globalThis.navigator.l
|
|
|
56
55
|
* another waiting caller may become the next leader. Waiting for leadership is
|
|
57
56
|
* abortable via the calling {@link Task}'s signal.
|
|
58
57
|
*/
|
|
59
|
-
export const acquireLeaderLock = (name) =>
|
|
58
|
+
export const acquireLeaderLock = (name) => async (run) => {
|
|
60
59
|
const acquisition = Promise.withResolvers();
|
|
61
60
|
const released = Promise.withResolvers();
|
|
62
61
|
const request = run.deps.lockManager.request(createLeaderLockName(name), { mode: "exclusive", signal: run.signal }, async () => {
|
|
@@ -65,13 +64,13 @@ export const acquireLeaderLock = (name) => (run) => tryAsync(async () => {
|
|
|
65
64
|
});
|
|
66
65
|
void request.catch(acquisition.reject);
|
|
67
66
|
await acquisition.promise;
|
|
68
|
-
return {
|
|
67
|
+
return ok({
|
|
69
68
|
[Symbol.asyncDispose]: async () => {
|
|
70
69
|
released.resolve();
|
|
71
70
|
await request;
|
|
72
71
|
},
|
|
73
|
-
};
|
|
74
|
-
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
75
74
|
/**
|
|
76
75
|
* Competes to become the current leader for `name` using {@link LockManager}.
|
|
77
76
|
*
|
|
@@ -92,7 +91,7 @@ export const acquireLeaderLockCallback = (deps) => (name, callback) => {
|
|
|
92
91
|
callback();
|
|
93
92
|
await released.promise;
|
|
94
93
|
});
|
|
95
|
-
void request.catch(
|
|
94
|
+
void request.catch(constVoid);
|
|
96
95
|
return {
|
|
97
96
|
[Symbol.dispose]: () => {
|
|
98
97
|
abortController.abort();
|
package/dist/src/Lookup.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import type { Brand } from "./Brand.
|
|
6
|
+
import type { Brand } from "./Brand.ts";
|
|
7
7
|
/**
|
|
8
8
|
* Derives the native lookup key used by lookup-based collections.
|
|
9
9
|
*
|
|
@@ -18,6 +18,16 @@ import type { Brand } from "./Brand.js";
|
|
|
18
18
|
* Use this when callers provide keys in one form but logical identity should be
|
|
19
19
|
* based on another stable key, such as a natural id or {@link StructuralKey}.
|
|
20
20
|
*
|
|
21
|
+
* Lookup functions must be pure and stable. The same logical key must derive
|
|
22
|
+
* the same native `Map` key while an entry is stored, so use lookup-based
|
|
23
|
+
* collections with immutable key data, preferably readonly values. Mutating key
|
|
24
|
+
* data after insertion can make later `get`, `has`, and `delete` calls disagree
|
|
25
|
+
* with the inserted entry.
|
|
26
|
+
*
|
|
27
|
+
* The returned lookup key uses native `Map` equality. Return primitives or
|
|
28
|
+
* stable object references; returning a new object for each call prevents equal
|
|
29
|
+
* logical keys from matching.
|
|
30
|
+
*
|
|
21
31
|
* @see {@link createLookupMap}
|
|
22
32
|
* @see {@link createLookupSet}
|
|
23
33
|
*/
|
|
@@ -113,17 +123,19 @@ export type StructuralKey = StructuralScalar | ReadonlyArray<StructuralKey> | {
|
|
|
113
123
|
*
|
|
114
124
|
* `Structural<T>` checks a concrete type recursively at compile time instead:
|
|
115
125
|
* scalars pass through, arrays recurse, object properties recurse, and
|
|
116
|
-
* function-valued properties are rejected.
|
|
117
|
-
*
|
|
126
|
+
* function-valued and symbol-keyed properties are rejected. Symbol-keyed
|
|
127
|
+
* properties are rejected because runtime serialization reads only enumerable
|
|
128
|
+
* string keys, so they would be silently ignored. This keeps public structural
|
|
129
|
+
* APIs ergonomic for interface-based inputs while preserving the same runtime
|
|
118
130
|
* constraints as structural lookup serialization.
|
|
119
131
|
*
|
|
120
132
|
* @see {@link StructuralKey}
|
|
121
133
|
* @see {@link structuralLookup}
|
|
122
134
|
*/
|
|
123
135
|
export type Structural<T> = T extends StructuralScalar ? T : T extends ReadonlyArray<infer Item> ? ReadonlyArray<Structural<Item>> : T extends StructuralFunction ? never : T extends object ? {
|
|
124
|
-
readonly [K in keyof T as Extract<T[K], StructuralFunction> extends never ? K : never]: Structural<Exclude<T[K], StructuralFunction>>;
|
|
136
|
+
readonly [K in keyof T as K extends symbol ? never : Extract<T[K], StructuralFunction> extends never ? K : never]: Structural<Exclude<T[K], StructuralFunction>>;
|
|
125
137
|
} & {
|
|
126
|
-
readonly [K in keyof T as Extract<T[K], StructuralFunction> extends never ? never : K]?: never;
|
|
138
|
+
readonly [K in keyof T as K extends symbol ? K : Extract<T[K], StructuralFunction> extends never ? never : K]?: never;
|
|
127
139
|
} : never;
|
|
128
140
|
/** Function branch excluded by {@link Structural}. */
|
|
129
141
|
export type StructuralFunction = (...args: ReadonlyArray<unknown>) => unknown;
|
|
@@ -132,7 +144,8 @@ export type StructuralFunction = (...args: ReadonlyArray<unknown>) => unknown;
|
|
|
132
144
|
*
|
|
133
145
|
* Structural lookup keys are derived from JSON-like values plus `Uint8Array`.
|
|
134
146
|
* Equal structures produce the same lookup key even when they are different
|
|
135
|
-
* JavaScript instances.
|
|
147
|
+
* JavaScript instances. `undefined` values and sparse array holes are rejected
|
|
148
|
+
* rather than skipped.
|
|
136
149
|
*
|
|
137
150
|
* The derived key is memoized by non-null object identity in a module-scoped
|
|
138
151
|
* `WeakMap` shared by all callers, so keys must be immutable.
|
|
@@ -143,16 +156,33 @@ export type StructuralFunction = (...args: ReadonlyArray<unknown>) => unknown;
|
|
|
143
156
|
* ### Example
|
|
144
157
|
*
|
|
145
158
|
* ```ts
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* StructuralLookupKey
|
|
150
|
-
*
|
|
159
|
+
* import {
|
|
160
|
+
* createLookupMap,
|
|
161
|
+
* structuralLookup,
|
|
162
|
+
* type StructuralLookupKey,
|
|
163
|
+
* } from "@evolu/common";
|
|
164
|
+
*
|
|
165
|
+
* interface Filter {
|
|
166
|
+
* readonly table: string;
|
|
167
|
+
* readonly where: readonly [string, string];
|
|
168
|
+
* }
|
|
169
|
+
*
|
|
170
|
+
* const byFilter = createLookupMap<Filter, string, StructuralLookupKey>({
|
|
151
171
|
* lookup: structuralLookup,
|
|
152
172
|
* });
|
|
153
173
|
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
174
|
+
* const cachedFilter: Filter = {
|
|
175
|
+
* table: "todo",
|
|
176
|
+
* where: ["owner", "ada"],
|
|
177
|
+
* };
|
|
178
|
+
* const equivalentFilter: Filter = {
|
|
179
|
+
* where: ["owner", "ada"],
|
|
180
|
+
* table: "todo",
|
|
181
|
+
* };
|
|
182
|
+
* byFilter.set(cachedFilter, "cached");
|
|
183
|
+
*
|
|
184
|
+
* expect(byFilter.get(equivalentFilter)).toBe("cached");
|
|
185
|
+
* expect(byFilter.getKey(equivalentFilter)).toBe(cachedFilter);
|
|
156
186
|
* ```
|
|
157
187
|
*
|
|
158
188
|
* @see {@link StructuralKey}
|
package/dist/src/Lookup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Lookup.d.ts","sourceRoot":"","sources":["../../src/Lookup.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAIxC
|
|
1
|
+
{"version":3,"file":"Lookup.d.ts","sourceRoot":"","sources":["../../src/Lookup.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAIxC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;AAEzC,+BAA+B;AAC/B,MAAM,WAAW,YAAY,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC;IACpC,wDAAwD;IACxD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC;IACrD,QAAQ,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACvE,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,OAAO,EAAE,CAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,KACvD,IAAI,CAAC;IACV,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;CACrE;AAED,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAE,SAAQ,QAAQ,CAC/D,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CACnB;IACC,mCAAmC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;CAC9C;AAED,iCAAiC;AACjC,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,wBAGpC,sBAAsB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAG,SAAS,CAAC,CAAC,EAAE,CAAC,CAmFlD,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,CAAE,SAAQ,QAAQ,CAAC,CAAC,CAAC;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,gBAAgB,CAAC,CAAC,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,OAAO,EAAE,CAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,KACpD,IAAI,CAAC;IACV,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,gBAAgB,CAAC,CAAC,CAAC,CAAC;CACvD;AAED,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,QAAQ,CAC5D,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CACnB;IACC,kCAAkC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;CAC/B;AAED,iCAAiC;AACjC,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,CAAC,uBAGjC,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,SAAS,CAAC,CAAC,CAmD5C,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,UAAU,CAAC;AAE7E,+DAA+D;AAC/D,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,KAAK,CAAC,qBAAqB,CAAC,CAAC;AAExE;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GACrB,gBAAgB,GAChB,aAAa,CAAC,aAAa,CAAC,GAC5B;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CAAA;CAAE,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,SAAS,gBAAgB,GAClD,CAAC,GACD,CAAC,SAAS,aAAa,CAAC,MAAM,IAAI,CAAC,GACjC,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAC/B,CAAC,SAAS,kBAAkB,GAC1B,KAAK,GACL,CAAC,SAAS,MAAM,GACd;IACE,QAAQ,EACN,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,MAAM,GAC5B,KAAK,GACL,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,SAAS,KAAK,GAC7C,CAAC,GACD,KAAK,GACV,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC;CACjD,GAAG;IACF,QAAQ,EACN,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,MAAM,GAC5B,CAAC,GACD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,SAAS,KAAK,GAC7C,KAAK,GACL,CAAC,CACR,CAAC,EAAE,KAAK;CACV,GACD,KAAK,CAAC;AAEhB,sDAAsD;AACtD,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,KAAG,mBACH,CAAC"}
|
package/dist/src/Lookup.js
CHANGED
|
@@ -113,7 +113,8 @@ export const createLookupSet = ({ lookup, values, }) => {
|
|
|
113
113
|
*
|
|
114
114
|
* Structural lookup keys are derived from JSON-like values plus `Uint8Array`.
|
|
115
115
|
* Equal structures produce the same lookup key even when they are different
|
|
116
|
-
* JavaScript instances.
|
|
116
|
+
* JavaScript instances. `undefined` values and sparse array holes are rejected
|
|
117
|
+
* rather than skipped.
|
|
117
118
|
*
|
|
118
119
|
* The derived key is memoized by non-null object identity in a module-scoped
|
|
119
120
|
* `WeakMap` shared by all callers, so keys must be immutable.
|
|
@@ -124,16 +125,33 @@ export const createLookupSet = ({ lookup, values, }) => {
|
|
|
124
125
|
* ### Example
|
|
125
126
|
*
|
|
126
127
|
* ```ts
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* StructuralLookupKey
|
|
131
|
-
*
|
|
128
|
+
* import {
|
|
129
|
+
* createLookupMap,
|
|
130
|
+
* structuralLookup,
|
|
131
|
+
* type StructuralLookupKey,
|
|
132
|
+
* } from "@evolu/common";
|
|
133
|
+
*
|
|
134
|
+
* interface Filter {
|
|
135
|
+
* readonly table: string;
|
|
136
|
+
* readonly where: readonly [string, string];
|
|
137
|
+
* }
|
|
138
|
+
*
|
|
139
|
+
* const byFilter = createLookupMap<Filter, string, StructuralLookupKey>({
|
|
132
140
|
* lookup: structuralLookup,
|
|
133
141
|
* });
|
|
134
142
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
143
|
+
* const cachedFilter: Filter = {
|
|
144
|
+
* table: "todo",
|
|
145
|
+
* where: ["owner", "ada"],
|
|
146
|
+
* };
|
|
147
|
+
* const equivalentFilter: Filter = {
|
|
148
|
+
* where: ["owner", "ada"],
|
|
149
|
+
* table: "todo",
|
|
150
|
+
* };
|
|
151
|
+
* byFilter.set(cachedFilter, "cached");
|
|
152
|
+
*
|
|
153
|
+
* expect(byFilter.get(equivalentFilter)).toBe("cached");
|
|
154
|
+
* expect(byFilter.getKey(equivalentFilter)).toBe(cachedFilter);
|
|
137
155
|
* ```
|
|
138
156
|
*
|
|
139
157
|
* @see {@link StructuralKey}
|
|
@@ -180,6 +198,8 @@ const structuralLookupInternal = (value, path = new Set()) => {
|
|
|
180
198
|
else {
|
|
181
199
|
assert(false, "Structural lookup keys must be JSON-like values or Uint8Array.");
|
|
182
200
|
}
|
|
201
|
+
// Cache only after traversal succeeds so cyclic values never leave a
|
|
202
|
+
// partial lookup key in the memo.
|
|
183
203
|
structuralLookupKeyByValue.set(value, lookupKey);
|
|
184
204
|
return lookupKey;
|
|
185
205
|
}
|