@evolu/common 7.4.1 → 8.0.0-next.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 (251) hide show
  1. package/README.md +4 -61
  2. package/dist/src/Array.d.ts +343 -102
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +181 -85
  5. package/dist/src/Assert.d.ts +57 -11
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +47 -11
  8. package/dist/src/BigInt.d.ts +6 -1
  9. package/dist/src/BigInt.d.ts.map +1 -1
  10. package/dist/src/BigInt.js +5 -0
  11. package/dist/src/Brand.d.ts +40 -12
  12. package/dist/src/Brand.d.ts.map +1 -1
  13. package/dist/src/Brand.js +5 -0
  14. package/dist/src/Buffer.d.ts +6 -1
  15. package/dist/src/Buffer.d.ts.map +1 -1
  16. package/dist/src/Buffer.js +7 -2
  17. package/dist/src/Cache.d.ts +10 -2
  18. package/dist/src/Cache.d.ts.map +1 -1
  19. package/dist/src/Cache.js +6 -0
  20. package/dist/src/Callbacks.d.ts +10 -7
  21. package/dist/src/Callbacks.d.ts.map +1 -1
  22. package/dist/src/Callbacks.js +23 -13
  23. package/dist/src/Console.d.ts +361 -69
  24. package/dist/src/Console.d.ts.map +1 -1
  25. package/dist/src/Console.js +217 -117
  26. package/dist/src/Crypto.d.ts +70 -21
  27. package/dist/src/Crypto.d.ts.map +1 -1
  28. package/dist/src/Crypto.js +72 -27
  29. package/dist/src/Eq.d.ts +22 -2
  30. package/dist/src/Eq.d.ts.map +1 -1
  31. package/dist/src/Eq.js +21 -1
  32. package/dist/src/Error.d.ts +23 -12
  33. package/dist/src/Error.d.ts.map +1 -1
  34. package/dist/src/Error.js +27 -11
  35. package/dist/src/Function.d.ts +128 -56
  36. package/dist/src/Function.d.ts.map +1 -1
  37. package/dist/src/Function.js +118 -14
  38. package/dist/src/Identicon.d.ts +5 -0
  39. package/dist/src/Identicon.d.ts.map +1 -1
  40. package/dist/src/Identicon.js +6 -1
  41. package/dist/src/Lookup.d.ts +160 -0
  42. package/dist/src/Lookup.d.ts.map +1 -0
  43. package/dist/src/Lookup.js +192 -0
  44. package/dist/src/Microtask.d.ts +21 -0
  45. package/dist/src/Microtask.d.ts.map +1 -0
  46. package/dist/src/Microtask.js +37 -0
  47. package/dist/src/Number.d.ts +18 -4
  48. package/dist/src/Number.d.ts.map +1 -1
  49. package/dist/src/Number.js +23 -1
  50. package/dist/src/Object.d.ts +110 -5
  51. package/dist/src/Object.d.ts.map +1 -1
  52. package/dist/src/Object.js +108 -5
  53. package/dist/src/Option.d.ts +58 -0
  54. package/dist/src/Option.d.ts.map +1 -0
  55. package/dist/src/Option.js +43 -0
  56. package/dist/src/Order.d.ts +5 -0
  57. package/dist/src/Order.d.ts.map +1 -1
  58. package/dist/src/Order.js +8 -3
  59. package/dist/src/Platform.d.ts +36 -2
  60. package/dist/src/Platform.d.ts.map +1 -1
  61. package/dist/src/Platform.js +10 -6
  62. package/dist/src/Polyfills.d.ts +27 -0
  63. package/dist/src/Polyfills.d.ts.map +1 -0
  64. package/dist/src/Polyfills.js +299 -0
  65. package/dist/src/Random.d.ts +18 -7
  66. package/dist/src/Random.d.ts.map +1 -1
  67. package/dist/src/Random.js +9 -7
  68. package/dist/src/Redacted.d.ts +7 -4
  69. package/dist/src/Redacted.d.ts.map +1 -1
  70. package/dist/src/Redacted.js +5 -0
  71. package/dist/src/Ref.d.ts +31 -16
  72. package/dist/src/Ref.d.ts.map +1 -1
  73. package/dist/src/Ref.js +35 -7
  74. package/dist/src/RefCount.d.ts +62 -0
  75. package/dist/src/RefCount.d.ts.map +1 -0
  76. package/dist/src/RefCount.js +83 -0
  77. package/dist/src/Relation.d.ts +84 -29
  78. package/dist/src/Relation.d.ts.map +1 -1
  79. package/dist/src/Relation.js +83 -105
  80. package/dist/src/Resource.d.ts +263 -0
  81. package/dist/src/Resource.d.ts.map +1 -0
  82. package/dist/src/Resource.js +389 -0
  83. package/dist/src/Result.d.ts +390 -374
  84. package/dist/src/Result.d.ts.map +1 -1
  85. package/dist/src/Result.js +142 -70
  86. package/dist/src/Schedule.d.ts +953 -0
  87. package/dist/src/Schedule.d.ts.map +1 -0
  88. package/dist/src/Schedule.js +1199 -0
  89. package/dist/src/Set.d.ts +181 -0
  90. package/dist/src/Set.d.ts.map +1 -0
  91. package/dist/src/Set.js +137 -0
  92. package/dist/src/Skiplist.d.ts +6 -1
  93. package/dist/src/Skiplist.d.ts.map +1 -1
  94. package/dist/src/Skiplist.js +5 -0
  95. package/dist/src/Sqlite.d.ts +160 -60
  96. package/dist/src/Sqlite.d.ts.map +1 -1
  97. package/dist/src/Sqlite.js +290 -190
  98. package/dist/src/Store.d.ts +36 -28
  99. package/dist/src/Store.d.ts.map +1 -1
  100. package/dist/src/Store.js +55 -15
  101. package/dist/src/String.d.ts +5 -0
  102. package/dist/src/String.d.ts.map +1 -1
  103. package/dist/src/String.js +5 -0
  104. package/dist/src/Task.d.ts +2263 -423
  105. package/dist/src/Task.d.ts.map +1 -1
  106. package/dist/src/Task.js +1488 -348
  107. package/dist/src/Test.d.ts +122 -0
  108. package/dist/src/Test.d.ts.map +1 -0
  109. package/dist/src/Test.js +66 -0
  110. package/dist/src/Time.d.ts +201 -48
  111. package/dist/src/Time.d.ts.map +1 -1
  112. package/dist/src/Time.js +190 -86
  113. package/dist/src/Tracer.d.ts +48 -0
  114. package/dist/src/Tracer.d.ts.map +1 -0
  115. package/dist/src/Tracer.js +6 -0
  116. package/dist/src/Type.d.ts +582 -209
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +362 -240
  119. package/dist/src/Types.d.ts +160 -6
  120. package/dist/src/Types.d.ts.map +1 -1
  121. package/dist/src/Types.js +22 -2
  122. package/dist/src/WebSocket.d.ts +113 -58
  123. package/dist/src/WebSocket.d.ts.map +1 -1
  124. package/dist/src/WebSocket.js +210 -118
  125. package/dist/src/Worker.d.ts +245 -60
  126. package/dist/src/Worker.d.ts.map +1 -1
  127. package/dist/src/Worker.js +220 -64
  128. package/dist/src/index.d.ts +25 -3
  129. package/dist/src/index.d.ts.map +1 -1
  130. package/dist/src/index.js +19 -3
  131. package/dist/src/local-first/Db.d.ts +24 -213
  132. package/dist/src/local-first/Db.d.ts.map +1 -1
  133. package/dist/src/local-first/Db.js +622 -293
  134. package/dist/src/local-first/Error.d.ts +12 -0
  135. package/dist/src/local-first/Error.d.ts.map +1 -0
  136. package/dist/src/local-first/Error.js +6 -0
  137. package/dist/src/local-first/Evolu.d.ts +330 -285
  138. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  139. package/dist/src/local-first/Evolu.js +472 -402
  140. package/dist/src/local-first/LocalAuth.d.ts +10 -10
  141. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  142. package/dist/src/local-first/LocalAuth.js +6 -1
  143. package/dist/src/local-first/Owner.d.ts +70 -27
  144. package/dist/src/local-first/Owner.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.js +27 -14
  146. package/dist/src/local-first/Protocol.d.ts +58 -50
  147. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  148. package/dist/src/local-first/Protocol.js +253 -155
  149. package/dist/src/local-first/Query.d.ts +133 -43
  150. package/dist/src/local-first/Query.d.ts.map +1 -1
  151. package/dist/src/local-first/Query.js +139 -90
  152. package/dist/src/local-first/Relay.d.ts +18 -38
  153. package/dist/src/local-first/Relay.d.ts.map +1 -1
  154. package/dist/src/local-first/Relay.js +59 -171
  155. package/dist/src/local-first/Schema.d.ts +139 -144
  156. package/dist/src/local-first/Schema.d.ts.map +1 -1
  157. package/dist/src/local-first/Schema.js +78 -163
  158. package/dist/src/local-first/Shared.d.ts +125 -0
  159. package/dist/src/local-first/Shared.d.ts.map +1 -0
  160. package/dist/src/local-first/Shared.js +569 -0
  161. package/dist/src/local-first/Storage.d.ts +50 -53
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +121 -173
  164. package/dist/src/local-first/Timestamp.d.ts +28 -35
  165. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  166. package/dist/src/local-first/Timestamp.js +24 -25
  167. package/dist/src/local-first/index.d.ts +2 -15
  168. package/dist/src/local-first/index.d.ts.map +1 -1
  169. package/dist/src/local-first/index.js +2 -15
  170. package/package.json +36 -26
  171. package/src/Array.ts +467 -106
  172. package/src/Assert.ts +87 -12
  173. package/src/BigInt.ts +7 -1
  174. package/src/Brand.ts +41 -12
  175. package/src/Buffer.ts +10 -4
  176. package/src/Cache.ts +11 -2
  177. package/src/Callbacks.ts +33 -23
  178. package/src/Console.ts +567 -178
  179. package/src/Crypto.ts +111 -62
  180. package/src/Eq.ts +30 -3
  181. package/src/Error.ts +50 -34
  182. package/src/Function.ts +140 -68
  183. package/src/Identicon.ts +7 -1
  184. package/src/Lookup.ts +415 -0
  185. package/src/Microtask.ts +58 -0
  186. package/src/Number.ts +40 -7
  187. package/src/Object.ts +155 -16
  188. package/src/Option.ts +74 -0
  189. package/src/Order.ts +11 -3
  190. package/src/Platform.ts +43 -7
  191. package/src/Polyfills.ts +465 -0
  192. package/src/Random.ts +25 -11
  193. package/src/Redacted.ts +8 -4
  194. package/src/Ref.ts +72 -21
  195. package/src/RefCount.ts +170 -0
  196. package/src/Relation.ts +188 -136
  197. package/src/Resource.ts +864 -0
  198. package/src/Result.ts +548 -381
  199. package/src/Schedule.ts +1471 -0
  200. package/src/Set.ts +247 -0
  201. package/src/Skiplist.ts +7 -1
  202. package/src/Sqlite.ts +409 -304
  203. package/src/Store.ts +94 -48
  204. package/src/String.ts +6 -0
  205. package/src/Task.ts +3685 -740
  206. package/src/Test.ts +162 -0
  207. package/src/Time.ts +348 -145
  208. package/src/Tracer.ts +54 -0
  209. package/src/Type.ts +851 -430
  210. package/src/Types.ts +202 -7
  211. package/src/WebSocket.ts +311 -178
  212. package/src/Worker.ts +527 -147
  213. package/src/index.ts +74 -3
  214. package/src/local-first/Db.ts +871 -597
  215. package/src/local-first/Error.ts +17 -0
  216. package/src/local-first/Evolu.ts +884 -824
  217. package/src/local-first/LocalAuth.ts +10 -10
  218. package/src/local-first/Owner.ts +94 -42
  219. package/src/local-first/Protocol.ts +440 -303
  220. package/src/local-first/Query.ts +243 -194
  221. package/src/local-first/Relay.ts +91 -252
  222. package/src/local-first/Schema.ts +284 -394
  223. package/src/local-first/Shared.ts +932 -0
  224. package/src/local-first/Storage.ts +206 -277
  225. package/src/local-first/Timestamp.ts +38 -45
  226. package/src/local-first/index.ts +2 -16
  227. package/LICENSE +0 -21
  228. package/dist/src/Instances.d.ts +0 -34
  229. package/dist/src/Instances.d.ts.map +0 -1
  230. package/dist/src/Instances.js +0 -44
  231. package/dist/src/Resources.d.ts +0 -118
  232. package/dist/src/Resources.d.ts.map +0 -1
  233. package/dist/src/Resources.js +0 -197
  234. package/dist/src/local-first/Platform.d.ts +0 -25
  235. package/dist/src/local-first/Platform.d.ts.map +0 -1
  236. package/dist/src/local-first/Platform.js +0 -1
  237. package/dist/src/local-first/Public.d.ts +0 -22
  238. package/dist/src/local-first/Public.d.ts.map +0 -1
  239. package/dist/src/local-first/Public.js +0 -15
  240. package/dist/src/local-first/PublicKysely.d.ts +0 -148
  241. package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
  242. package/dist/src/local-first/PublicKysely.js +0 -184
  243. package/dist/src/local-first/Sync.d.ts +0 -112
  244. package/dist/src/local-first/Sync.d.ts.map +0 -1
  245. package/dist/src/local-first/Sync.js +0 -526
  246. package/src/Instances.ts +0 -92
  247. package/src/Resources.ts +0 -367
  248. package/src/local-first/Platform.ts +0 -27
  249. package/src/local-first/Public.ts +0 -42
  250. package/src/local-first/PublicKysely.ts +0 -239
  251. package/src/local-first/Sync.ts +0 -949
@@ -0,0 +1,465 @@
1
+ /**
2
+ * Polyfills.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ /**
8
+ * Installs polyfills required by `@evolu/common`.
9
+ *
10
+ * Installs resource-management polyfills (`Symbol.dispose`,
11
+ * `Symbol.asyncDispose`, `DisposableStack`, `AsyncDisposableStack`, and
12
+ * `SuppressedError`), which are not yet supported by Safari and React Native.
13
+ *
14
+ * Evolu currently does not require any additional polyfills. If that changes,
15
+ * this is where they will be installed.
16
+ *
17
+ * `@evolu/react-native` has its own `Polyfills` module and its
18
+ * `installPolyfills` calls this function first, then installs React Native
19
+ * specific polyfills.
20
+ *
21
+ * Call this explicitly from the app entry point.
22
+ *
23
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Resource_management
24
+ * @see https://github.com/es-shims/DisposableStack
25
+ * @see https://github.com/es-shims/DisposableStack/issues/9
26
+ */
27
+ export const installPolyfills = (): void => {
28
+ /**
29
+ * This module intentionally owns `DisposableStack` and `AsyncDisposableStack`
30
+ * polyfills instead of depending on `es-shims/DisposableStack` at runtime.
31
+ *
32
+ * Evolu originally used the upstream package, but WebKit hit a known async
33
+ * disposal completion bug (`completion["?"]` crash, see issue #9). The local
34
+ * implementation applies the fix and keeps behavior deterministic across
35
+ * runtimes used by Evolu.
36
+ *
37
+ * Conformance is validated by tests that combine upstream-style and test262
38
+ * parity cases with Evolu-specific regressions. Those tests run against
39
+ * native Node.js behavior and against this polyfill path in browser projects,
40
+ * including WebKit.
41
+ */
42
+ installDisposableStack();
43
+ };
44
+
45
+ interface DisposableResource {
46
+ readonly dispose: () => void;
47
+ }
48
+
49
+ interface AsyncDisposableResource {
50
+ readonly dispose: () => Promise<void>;
51
+ }
52
+
53
+ /** Installs `DisposableStack`-related polyfills missing from the runtime. */
54
+ const installDisposableStack = (): void => {
55
+ installSuppressedError();
56
+
57
+ const symbolDispose = getOrInstallSymbol("dispose", "Symbol.dispose");
58
+ const symbolAsyncDispose = getOrInstallSymbol(
59
+ "asyncDispose",
60
+ "Symbol.asyncDispose",
61
+ );
62
+
63
+ if (typeof globalThis.DisposableStack !== "function") {
64
+ defineGlobalValue(
65
+ globalThis,
66
+ "DisposableStack",
67
+ createDisposableStackPolyfill(symbolDispose),
68
+ );
69
+ }
70
+
71
+ if (typeof globalThis.AsyncDisposableStack !== "function") {
72
+ defineGlobalValue(
73
+ globalThis,
74
+ "AsyncDisposableStack",
75
+ createAsyncDisposableStackPolyfill(symbolDispose, symbolAsyncDispose),
76
+ );
77
+ }
78
+ };
79
+
80
+ type SymbolWithDisposable = SymbolConstructor & {
81
+ dispose?: symbol;
82
+ asyncDispose?: symbol;
83
+ };
84
+
85
+ const suppressedErrorMessage = "An error was suppressed during disposal.";
86
+
87
+ const disposedMessage = (className: string, method: string): string =>
88
+ `Cannot call ${className}.prototype.${method} on an already-disposed DisposableStack`;
89
+
90
+ const defineGlobalValue = (
91
+ target: object,
92
+ key: PropertyKey,
93
+ value: unknown,
94
+ ): void => {
95
+ Object.defineProperty(target, key, {
96
+ configurable: true,
97
+ enumerable: false,
98
+ writable: true,
99
+ value,
100
+ });
101
+ };
102
+
103
+ const defineMethodAlias = (
104
+ target: object,
105
+ alias: symbol,
106
+ methodName: string,
107
+ ): void => {
108
+ defineGlobalValue(
109
+ target,
110
+ alias,
111
+ Object.getOwnPropertyDescriptor(target, methodName)?.value,
112
+ );
113
+ };
114
+
115
+ const throwIfDisposed = (disposed: boolean, message: string): void => {
116
+ if (disposed) {
117
+ throw new ReferenceError(message);
118
+ }
119
+ };
120
+
121
+ const appendDisposeError = (
122
+ currentError: unknown,
123
+ previousError: unknown,
124
+ ): unknown =>
125
+ new globalThis.SuppressedError(
126
+ currentError,
127
+ previousError,
128
+ suppressedErrorMessage,
129
+ );
130
+
131
+ const assertObjectOrFunction = (value: unknown): object => {
132
+ if (
133
+ (typeof value !== "object" || value === null) &&
134
+ typeof value !== "function"
135
+ ) {
136
+ throw new TypeError("Disposable value must be an object or function.");
137
+ }
138
+
139
+ return value;
140
+ };
141
+
142
+ const getMethod = (
143
+ value: object,
144
+ key: symbol,
145
+ ): ((this: unknown) => unknown) | undefined => {
146
+ const method = (value as Record<symbol, unknown>)[key];
147
+ if (method === undefined) return undefined;
148
+ if (typeof method !== "function") {
149
+ throw new TypeError("Disposable method must be a function.");
150
+ }
151
+ return method as (this: unknown) => unknown;
152
+ };
153
+
154
+ const getOrInstallSymbol = (
155
+ key: "dispose" | "asyncDispose",
156
+ description: string,
157
+ ): symbol => {
158
+ const SymbolCtor = globalThis.Symbol as SymbolWithDisposable;
159
+ const installedValue = Object.getOwnPropertyDescriptor(SymbolCtor, key)
160
+ ?.value as unknown;
161
+ const installed =
162
+ typeof installedValue === "symbol" ? installedValue : undefined;
163
+ if (installed != null) return installed;
164
+
165
+ const symbol = Symbol(description);
166
+ Object.defineProperty(SymbolCtor, key, {
167
+ configurable: false,
168
+ enumerable: false,
169
+ writable: false,
170
+ value: symbol,
171
+ });
172
+ return symbol;
173
+ };
174
+
175
+ const installSuppressedError = (): void => {
176
+ if (typeof globalThis.SuppressedError === "function") return;
177
+
178
+ class SuppressedErrorPolyfill
179
+ extends Error
180
+ implements globalThis.SuppressedError
181
+ {
182
+ readonly error: unknown;
183
+ readonly suppressed: unknown;
184
+
185
+ constructor(error: unknown, suppressed: unknown, message?: string) {
186
+ super(message ?? suppressedErrorMessage);
187
+ this.name = "SuppressedError";
188
+ this.error = error;
189
+ this.suppressed = suppressed;
190
+ }
191
+ }
192
+
193
+ defineGlobalValue(globalThis, "SuppressedError", SuppressedErrorPolyfill);
194
+ };
195
+
196
+ const createDisposableStackPolyfill = (
197
+ symbolDispose: symbol,
198
+ ): new () => DisposableStack => {
199
+ class DisposableStackPolyfill implements DisposableStack {
200
+ #disposed = false;
201
+ #resources: Array<DisposableResource> = [];
202
+
203
+ declare readonly [Symbol.toStringTag]: "DisposableStack";
204
+ declare [Symbol.dispose]: () => void;
205
+
206
+ get disposed(): boolean {
207
+ return this.#disposed;
208
+ }
209
+
210
+ use<T extends object | null | undefined>(value: T): T {
211
+ throwIfDisposed(
212
+ this.#disposed,
213
+ disposedMessage("DisposableStack", "use"),
214
+ );
215
+ if (value == null) return value;
216
+
217
+ const target = assertObjectOrFunction(value);
218
+ const method = getMethod(target, symbolDispose);
219
+ if (method == null) {
220
+ throw new TypeError("Resource does not implement Symbol.dispose.");
221
+ }
222
+
223
+ this.#resources.push({
224
+ dispose: () => {
225
+ method.call(target);
226
+ },
227
+ });
228
+
229
+ return value;
230
+ }
231
+
232
+ adopt<T>(value: T, onDispose: (value: T) => void): T {
233
+ throwIfDisposed(
234
+ this.#disposed,
235
+ disposedMessage("DisposableStack", "adopt"),
236
+ );
237
+ if (typeof onDispose !== "function") {
238
+ throw new TypeError("onDispose must be a function.");
239
+ }
240
+
241
+ this.#resources.push({
242
+ dispose: () => {
243
+ onDispose(value);
244
+ },
245
+ });
246
+
247
+ return value;
248
+ }
249
+
250
+ defer(onDispose: () => void): void {
251
+ throwIfDisposed(
252
+ this.#disposed,
253
+ disposedMessage("DisposableStack", "defer"),
254
+ );
255
+ if (typeof onDispose !== "function") {
256
+ throw new TypeError("onDispose must be a function.");
257
+ }
258
+
259
+ this.#resources.push({
260
+ dispose: () => {
261
+ onDispose();
262
+ },
263
+ });
264
+ }
265
+
266
+ move(): DisposableStack {
267
+ throwIfDisposed(
268
+ this.#disposed,
269
+ disposedMessage("DisposableStack", "move"),
270
+ );
271
+
272
+ const moved = new DisposableStackPolyfill();
273
+ moved.#resources = this.#resources;
274
+
275
+ this.#resources = [];
276
+ this.#disposed = true;
277
+
278
+ return moved;
279
+ }
280
+
281
+ dispose(): void {
282
+ if (this.#disposed) return;
283
+
284
+ this.#disposed = true;
285
+ const resources = this.#resources;
286
+ this.#resources = [];
287
+
288
+ let completionError: unknown = undefined;
289
+ let hasCompletionError = false;
290
+
291
+ for (let i = resources.length - 1; i >= 0; i--) {
292
+ try {
293
+ resources[i].dispose();
294
+ } catch (error) {
295
+ completionError = hasCompletionError
296
+ ? appendDisposeError(error, completionError)
297
+ : error;
298
+ hasCompletionError = true;
299
+ }
300
+ }
301
+
302
+ if (hasCompletionError) {
303
+ throw completionError;
304
+ }
305
+ }
306
+ }
307
+
308
+ defineMethodAlias(
309
+ DisposableStackPolyfill.prototype,
310
+ symbolDispose,
311
+ "dispose",
312
+ );
313
+
314
+ Object.defineProperty(DisposableStackPolyfill.prototype, Symbol.toStringTag, {
315
+ configurable: true,
316
+ enumerable: false,
317
+ writable: false,
318
+ value: "DisposableStack",
319
+ });
320
+
321
+ return DisposableStackPolyfill;
322
+ };
323
+
324
+ const createAsyncDisposableStackPolyfill = (
325
+ symbolDispose: symbol,
326
+ symbolAsyncDispose: symbol,
327
+ ): new () => AsyncDisposableStack => {
328
+ class AsyncDisposableStackPolyfill implements AsyncDisposableStack {
329
+ #disposed = false;
330
+ #resources: Array<AsyncDisposableResource> = [];
331
+
332
+ declare readonly [Symbol.toStringTag]: "AsyncDisposableStack";
333
+ declare [Symbol.asyncDispose]: () => Promise<void>;
334
+
335
+ get disposed(): boolean {
336
+ return this.#disposed;
337
+ }
338
+
339
+ use<T extends object | null | undefined>(value: T): T {
340
+ throwIfDisposed(
341
+ this.#disposed,
342
+ disposedMessage("AsyncDisposableStack", "use"),
343
+ );
344
+ if (value == null) return value;
345
+
346
+ const target = assertObjectOrFunction(value);
347
+ const asyncMethod = getMethod(target, symbolAsyncDispose);
348
+
349
+ if (asyncMethod != null) {
350
+ this.#resources.push({
351
+ dispose: async () => {
352
+ await asyncMethod.call(target);
353
+ },
354
+ });
355
+ return value;
356
+ }
357
+
358
+ const syncMethod = getMethod(target, symbolDispose);
359
+
360
+ if (syncMethod != null) {
361
+ this.#resources.push({
362
+ dispose: () =>
363
+ Promise.resolve().then(() => {
364
+ syncMethod.call(target);
365
+ }),
366
+ });
367
+ return value;
368
+ }
369
+
370
+ throw new TypeError(
371
+ "Resource does not implement Symbol.asyncDispose or Symbol.dispose.",
372
+ );
373
+ }
374
+
375
+ adopt<T>(value: T, onDisposeAsync: (value: T) => void | Promise<void>): T {
376
+ throwIfDisposed(
377
+ this.#disposed,
378
+ disposedMessage("AsyncDisposableStack", "adopt"),
379
+ );
380
+ if (typeof onDisposeAsync !== "function") {
381
+ throw new TypeError("onDisposeAsync must be a function.");
382
+ }
383
+
384
+ this.#resources.push({
385
+ dispose: () => Promise.resolve().then(() => onDisposeAsync(value)),
386
+ });
387
+
388
+ return value;
389
+ }
390
+
391
+ defer(onDisposeAsync: () => void | Promise<void>): void {
392
+ throwIfDisposed(
393
+ this.#disposed,
394
+ disposedMessage("AsyncDisposableStack", "defer"),
395
+ );
396
+ if (typeof onDisposeAsync !== "function") {
397
+ throw new TypeError("onDisposeAsync must be a function.");
398
+ }
399
+
400
+ this.#resources.push({
401
+ dispose: () => Promise.resolve().then(() => onDisposeAsync()),
402
+ });
403
+ }
404
+
405
+ move(): AsyncDisposableStack {
406
+ throwIfDisposed(
407
+ this.#disposed,
408
+ disposedMessage("AsyncDisposableStack", "move"),
409
+ );
410
+
411
+ const moved = new AsyncDisposableStackPolyfill();
412
+ moved.#resources = this.#resources;
413
+
414
+ this.#resources = [];
415
+ this.#disposed = true;
416
+
417
+ return moved;
418
+ }
419
+
420
+ async disposeAsync(): Promise<void> {
421
+ if (this.#disposed) return;
422
+
423
+ this.#disposed = true;
424
+ const resources = this.#resources;
425
+ this.#resources = [];
426
+
427
+ let completionError: unknown = undefined;
428
+ let hasCompletionError = false;
429
+
430
+ for (let i = resources.length - 1; i >= 0; i--) {
431
+ try {
432
+ await resources[i].dispose();
433
+ } catch (error) {
434
+ completionError = hasCompletionError
435
+ ? appendDisposeError(error, completionError)
436
+ : error;
437
+ hasCompletionError = true;
438
+ }
439
+ }
440
+
441
+ if (hasCompletionError) {
442
+ throw completionError;
443
+ }
444
+ }
445
+ }
446
+
447
+ defineMethodAlias(
448
+ AsyncDisposableStackPolyfill.prototype,
449
+ symbolAsyncDispose,
450
+ "disposeAsync",
451
+ );
452
+
453
+ Object.defineProperty(
454
+ AsyncDisposableStackPolyfill.prototype,
455
+ Symbol.toStringTag,
456
+ {
457
+ configurable: true,
458
+ enumerable: false,
459
+ writable: false,
460
+ value: "AsyncDisposableStack",
461
+ },
462
+ );
463
+
464
+ return AsyncDisposableStackPolyfill;
465
+ };
package/src/Random.ts CHANGED
@@ -1,4 +1,18 @@
1
+ /**
2
+ * Seeded random number generation.
3
+ *
4
+ * @module
5
+ */
6
+
1
7
  import { Random as RandomLib } from "random";
8
+ import type { Brand } from "./Brand.js";
9
+
10
+ /**
11
+ * A random floating point number in [0, 1).
12
+ *
13
+ * Branded to distinguish random values from arbitrary numbers.
14
+ */
15
+ export type RandomNumber = number & Brand<"RandomNumber">;
2
16
 
3
17
  /**
4
18
  * A simple wrapper around Math.random().
@@ -19,7 +33,7 @@ import { Random as RandomLib } from "random";
19
33
  */
20
34
  export interface Random {
21
35
  /** Returns a floating point number in [0, 1). Just like Math.random(). */
22
- next: () => number;
36
+ next: () => RandomNumber;
23
37
  }
24
38
 
25
39
  export interface RandomDep {
@@ -28,9 +42,13 @@ export interface RandomDep {
28
42
 
29
43
  /** Creates a {@link Random} using Math.random(). */
30
44
  export const createRandom = (): Random => ({
31
- next: () => Math.random(),
45
+ next: () => Math.random() as RandomNumber,
32
46
  });
33
47
 
48
+ /** Creates a seeded {@link Random} for deterministic tests. Default seed "evolu". */
49
+ export const testCreateRandom = (seed = "evolu"): Random =>
50
+ createRandomWithSeed(seed);
51
+
34
52
  /**
35
53
  * Creates {@link Random} using {@link RandomLibDep} with a seed which is useful
36
54
  * for tests.
@@ -38,7 +56,7 @@ export const createRandom = (): Random => ({
38
56
  export const createRandomWithSeed = (seed: string): Random => {
39
57
  const random = new RandomLib(seed);
40
58
  return {
41
- next: () => random.next(),
59
+ next: () => random.next() as RandomNumber,
42
60
  };
43
61
  };
44
62
 
@@ -48,16 +66,12 @@ export const createRandomWithSeed = (seed: string): Random => {
48
66
  * https://github.com/transitive-bullshit/random
49
67
  */
50
68
  export interface RandomLibDep {
51
- random: RandomLib;
69
+ readonly randomLib: RandomLib;
52
70
  }
53
71
 
54
72
  /** Creates a `RandomLib` using the NPM `random` package. */
55
73
  export const createRandomLib = (): RandomLib => new RandomLib();
56
74
 
57
- /**
58
- * Creates {@link RandomLibDep} using the NPM `random` package with a seed which
59
- * is useful for tests.
60
- */
61
- export const createRandomLibWithSeed = (seed: string): RandomLibDep => ({
62
- random: new RandomLib(seed),
63
- });
75
+ /** Creates a seeded `RandomLib` for deterministic tests. Default seed "evolu". */
76
+ export const testCreateRandomLib = (seed = "evolu"): RandomLib =>
77
+ new RandomLib(seed);
package/src/Redacted.ts CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Sensitive value protection against accidental exposure.
3
+ *
4
+ * @module
5
+ */
6
+
1
7
  import { assert } from "./Assert.js";
2
8
  import type { Brand } from "./Brand.js";
3
9
  import type { Eq } from "./Eq.js";
@@ -44,8 +50,8 @@ import type { Eq } from "./Eq.js";
44
50
  * // use value...
45
51
  * };
46
52
  *
47
- * fetchUser(redactedKey); //
48
- * // fetchUser(createRedacted("x" as DbPassword)); // type error
53
+ * fetchUser(redactedKey); // ok
54
+ * // fetchUser(createRedacted("x" as DbPassword)); // type error
49
55
  *
50
56
  * // Automatic cleanup with `using`
51
57
  * {
@@ -53,8 +59,6 @@ import type { Eq } from "./Eq.js";
53
59
  * // ... use secret ...
54
60
  * } // automatically wiped from memory
55
61
  * ```
56
- *
57
- * @experimental
58
62
  */
59
63
  export interface Redacted<A> extends Brand<"Redacted">, Disposable {
60
64
  /** The inner type. Useful for inference via `typeof redacted.Type`. */
package/src/Ref.ts CHANGED
@@ -1,23 +1,29 @@
1
+ /**
2
+ * Mutable reference to an immutable value.
3
+ *
4
+ * @module
5
+ */
6
+
1
7
  import type { Store } from "./Store.js";
2
8
 
3
9
  /**
4
- * `Ref` provides a simple API to hold and update a value, similar to a "ref" in
5
- * functional programming or React. It exposes methods to get, set, and modify
6
- * the current state.
10
+ * Mutable reference to an immutable value.
7
11
  *
8
- * Use a Ref instead of a variable when you want to pass state around as an
9
- * object or update it in a controlled way. If you need subscriptions, see
10
- * {@link Store}.
12
+ * `Ref` holds the current value and exposes explicit `get`, `set`, `update`,
13
+ * and `modify` operations. The reference is mutable, but the value inside it
14
+ * must be immutable and replaced with a new value rather than mutated in place.
15
+ * Storing a mutable value in `Ref` does not make sense, because callers could
16
+ * mutate that value directly and pass it around without `Ref`.
11
17
  *
12
- * Updating in a controlled way means all changes go through specific methods
13
- * (`set` or `modify`), making state updates predictable and easy to track.
18
+ * Use it when mutable ownership of a value needs to be passed around as a
19
+ * value. For reactive state with subscriptions, see {@link Store}.
14
20
  *
15
21
  * ### Example
16
22
  *
17
23
  * ```ts
18
24
  * const count = createRef(0);
19
25
  * count.set(1);
20
- * count.modify((n) => n + 1);
26
+ * count.update((n) => n + 1);
21
27
  * console.log(count.get()); // 2
22
28
  * ```
23
29
  *
@@ -30,29 +36,74 @@ import type { Store } from "./Store.js";
30
36
  * ```
31
37
  */
32
38
  export interface Ref<T> {
33
- /** Returns the current state. */
39
+ /** Returns the current value. */
34
40
  readonly get: () => T;
35
41
 
36
- /** Sets the state. */
37
- readonly set: (state: T) => void;
42
+ /** Sets the current value. */
43
+ readonly set: (value: T) => void;
44
+
45
+ /** Sets the current value and returns the previous value. */
46
+ readonly getAndSet: (value: T) => T;
38
47
 
39
- /** Modifies the state using an updater function. */
40
- readonly modify: (updater: (current: T) => T) => void;
48
+ /** Sets the current value and returns it. */
49
+ readonly setAndGet: (value: T) => T;
50
+
51
+ /** Updates the current value. */
52
+ readonly update: (updater: (current: T) => T) => void;
53
+
54
+ /** Updates the current value and returns the previous value. */
55
+ readonly getAndUpdate: (updater: (current: T) => T) => T;
56
+
57
+ /** Updates the current value and returns it. */
58
+ readonly updateAndGet: (updater: (current: T) => T) => T;
59
+
60
+ /** Modifies the current value and returns a computed result. */
61
+ readonly modify: <R>(
62
+ updater: (current: T) => readonly [result: R, nextValue: T],
63
+ ) => R;
41
64
  }
42
65
 
43
- /** Creates a {@link Ref} with the given initial state. */
44
- export const createRef = <T>(initialState: T): Ref<T> => {
45
- let currentState = initialState;
66
+ /** Creates a {@link Ref} with the given initial immutable value. */
67
+ export const createRef = <T>(initialValue: T): Ref<T> => {
68
+ let currentValue = initialValue;
46
69
 
47
70
  return {
48
- get: () => currentState,
71
+ get: () => currentValue,
72
+
73
+ set: (value) => {
74
+ currentValue = value;
75
+ },
76
+
77
+ getAndSet: (value) => {
78
+ const previousValue = currentValue;
79
+ currentValue = value;
80
+ return previousValue;
81
+ },
82
+
83
+ setAndGet: (value) => {
84
+ currentValue = value;
85
+ return currentValue;
86
+ },
87
+
88
+ update: (updater) => {
89
+ currentValue = updater(currentValue);
90
+ },
91
+
92
+ getAndUpdate: (updater) => {
93
+ const previousValue = currentValue;
94
+ currentValue = updater(currentValue);
95
+ return previousValue;
96
+ },
49
97
 
50
- set: (state) => {
51
- currentState = state;
98
+ updateAndGet: (updater) => {
99
+ currentValue = updater(currentValue);
100
+ return currentValue;
52
101
  },
53
102
 
54
103
  modify: (updater) => {
55
- currentState = updater(currentState);
104
+ const [result, nextValue] = updater(currentValue);
105
+ currentValue = nextValue;
106
+ return result;
56
107
  },
57
108
  };
58
109
  };