@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
package/src/Identicon.ts CHANGED
@@ -1,6 +1,12 @@
1
+ /**
2
+ * Visual identicon generation from IDs.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import { md5 } from "@noble/hashes/legacy.js";
1
8
  import type { Brand } from "./Brand.js";
2
9
  import { Id, idToIdBytes } from "./Type.js";
3
- import { md5 } from "@noble/hashes/legacy.js";
4
10
 
5
11
  /**
6
12
  * SVG string representing a visual identicon for an {@link Id}, created with
package/src/Lookup.ts ADDED
@@ -0,0 +1,415 @@
1
+ /**
2
+ * Lookup-key collections and helpers.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import { assert } from "./Assert.js";
8
+ import type { Brand } from "./Brand.js";
9
+ import { isPlainObject } from "./Object.js";
10
+ import { Uint8Array, uint8ArrayToBase64Url } from "./Type.js";
11
+
12
+ /**
13
+ * Derives the native lookup key used by lookup-based collections.
14
+ *
15
+ * Native `Map` and `Set` are suitable for primitive keys and keys where
16
+ * reference identity is the intended key, but they are not useful when
17
+ * different references should represent the same logical key.
18
+ *
19
+ * Lookup-based collections solve that by deriving a stable lookup key from each
20
+ * input key and using that lookup key for the internal native `Map` lookup.
21
+ * Keys that derive the same lookup key are treated as the same logical key.
22
+ *
23
+ * Use this when callers provide keys in one form but logical identity should be
24
+ * based on another stable key, such as a natural id or {@link StructuralKey}.
25
+ *
26
+ * @see {@link createLookupMap}
27
+ * @see {@link createLookupSet}
28
+ */
29
+ export type Lookup<K, L> = (key: K) => L;
30
+
31
+ /** Optional {@link Lookup}. */
32
+ export interface LookupOption<K, L = K> {
33
+ /** Derives the lookup key used for logical equality. */
34
+ readonly lookup?: Lookup<K, L>;
35
+ }
36
+
37
+ /**
38
+ * `Map`-like collection keyed by a {@link Lookup derived lookup key}.
39
+ *
40
+ * `set` preserves the first inserted representative for a logical key. `getKey`
41
+ * returns that representative for callers that need stable identity.
42
+ *
43
+ * @see {@link createLookupMap}
44
+ */
45
+ export interface LookupMap<K, V> extends Iterable<readonly [K, V]> {
46
+ readonly size: number;
47
+ readonly has: (key: K) => boolean;
48
+ readonly get: (key: K) => V | undefined;
49
+ readonly getKey: (key: K) => K | undefined;
50
+ readonly set: (key: K, value: V) => LookupMap<K, V>;
51
+ readonly delete: (key: K) => boolean;
52
+ readonly clear: () => void;
53
+ readonly keys: () => IterableIterator<K>;
54
+ readonly values: () => IterableIterator<V>;
55
+ readonly entries: () => IterableIterator<readonly [K, V]>;
56
+ readonly forEach: (
57
+ callback: (value: V, key: K, map: LookupMap<K, V>) => void,
58
+ ) => void;
59
+ readonly [Symbol.iterator]: () => IterableIterator<readonly [K, V]>;
60
+ }
61
+
62
+ /** Options for {@link createLookupMap}. */
63
+ export interface CreateLookupMapOptions<K, V, L> extends Required<
64
+ LookupOption<K, L>
65
+ > {
66
+ /** Initial entries for the map. */
67
+ readonly entries?: Iterable<readonly [K, V]>;
68
+ }
69
+
70
+ /** Creates {@link LookupMap}. */
71
+ export const createLookupMap = <K, V, L>({
72
+ lookup,
73
+ entries,
74
+ }: CreateLookupMapOptions<K, V, L>): LookupMap<K, V> => {
75
+ interface LookupEntry<K, V> {
76
+ readonly key: K;
77
+ readonly value: V;
78
+ }
79
+
80
+ const entriesByLookupKey = new Map<L, LookupEntry<K, V>>();
81
+
82
+ const map: LookupMap<K, V> = {
83
+ get size() {
84
+ return entriesByLookupKey.size;
85
+ },
86
+
87
+ has: (key) => entriesByLookupKey.has(lookup(key)),
88
+
89
+ get: (key) => entriesByLookupKey.get(lookup(key))?.value,
90
+
91
+ getKey: (key) => entriesByLookupKey.get(lookup(key))?.key,
92
+
93
+ set: (key, value) => {
94
+ const lookupKey = lookup(key);
95
+ const existingEntry = entriesByLookupKey.get(lookupKey);
96
+ entriesByLookupKey.set(lookupKey, {
97
+ key: existingEntry?.key ?? key,
98
+ value,
99
+ });
100
+ return map;
101
+ },
102
+
103
+ delete: (key) => entriesByLookupKey.delete(lookup(key)),
104
+
105
+ clear: () => {
106
+ entriesByLookupKey.clear();
107
+ },
108
+
109
+ keys: function* () {
110
+ for (const entry of entriesByLookupKey.values()) {
111
+ yield entry.key;
112
+ }
113
+ },
114
+
115
+ values: function* () {
116
+ for (const entry of entriesByLookupKey.values()) {
117
+ yield entry.value;
118
+ }
119
+ },
120
+
121
+ entries: function* () {
122
+ for (const entry of entriesByLookupKey.values()) {
123
+ yield [entry.key, entry.value] as const;
124
+ }
125
+ },
126
+
127
+ forEach: (callback) => {
128
+ for (const entry of entriesByLookupKey.values()) {
129
+ callback(entry.value, entry.key, map);
130
+ }
131
+ },
132
+
133
+ [Symbol.iterator]: function () {
134
+ return map.entries();
135
+ },
136
+ };
137
+
138
+ if (entries) {
139
+ for (const [key, value] of entries) {
140
+ map.set(key, value);
141
+ }
142
+ }
143
+
144
+ return map;
145
+ };
146
+
147
+ /**
148
+ * `Set`-like collection keyed by a {@link Lookup derived lookup key}.
149
+ *
150
+ * `add` preserves the first inserted representative for a logical key. `get`
151
+ * returns that representative for callers that need stable identity.
152
+ *
153
+ * @see {@link createLookupSet}
154
+ */
155
+ export interface LookupSet<K> extends Iterable<K> {
156
+ readonly size: number;
157
+ readonly has: (key: K) => boolean;
158
+ readonly get: (key: K) => K | undefined;
159
+ readonly add: (key: K) => LookupSet<K>;
160
+ readonly delete: (key: K) => boolean;
161
+ readonly clear: () => void;
162
+ readonly keys: () => IterableIterator<K>;
163
+ readonly values: () => IterableIterator<K>;
164
+ readonly entries: () => IterableIterator<readonly [K, K]>;
165
+ readonly forEach: (
166
+ callback: (value: K, key: K, set: LookupSet<K>) => void,
167
+ ) => void;
168
+ readonly [Symbol.iterator]: () => IterableIterator<K>;
169
+ }
170
+
171
+ /** Options for {@link createLookupSet}. */
172
+ export interface CreateLookupSetOptions<K, L> extends Required<
173
+ LookupOption<K, L>
174
+ > {
175
+ /** Initial values for the set. */
176
+ readonly values?: Iterable<K>;
177
+ }
178
+
179
+ /** Creates {@link LookupSet}. */
180
+ export const createLookupSet = <K, L>({
181
+ lookup,
182
+ values,
183
+ }: CreateLookupSetOptions<K, L>): LookupSet<K> => {
184
+ const map = createLookupMap<K, true, L>({ lookup });
185
+
186
+ const set: LookupSet<K> = {
187
+ get size() {
188
+ return map.size;
189
+ },
190
+
191
+ has: (key) => map.has(key),
192
+
193
+ get: (key) => map.getKey(key),
194
+
195
+ add: (key) => {
196
+ map.set(key, true);
197
+ return set;
198
+ },
199
+
200
+ delete: (key) => map.delete(key),
201
+
202
+ clear: () => {
203
+ map.clear();
204
+ },
205
+
206
+ keys: () => map.keys(),
207
+
208
+ values: () => map.keys(),
209
+
210
+ entries: function* () {
211
+ for (const key of map.keys()) {
212
+ yield [key, key] as const;
213
+ }
214
+ },
215
+
216
+ forEach: (callback) => {
217
+ for (const key of map.keys()) {
218
+ callback(key, key, set);
219
+ }
220
+ },
221
+
222
+ [Symbol.iterator]: function () {
223
+ return set.keys();
224
+ },
225
+ };
226
+
227
+ if (values) {
228
+ for (const value of values) {
229
+ set.add(value);
230
+ }
231
+ }
232
+
233
+ return set;
234
+ };
235
+
236
+ /** Scalar branch shared by {@link StructuralKey} and {@link Structural}. */
237
+ export type StructuralScalar = string | number | boolean | null | Uint8Array;
238
+
239
+ /** Branded native key produced by {@link structuralLookup}. */
240
+ export type StructuralLookupKey = string & Brand<"StructuralLookupKey">;
241
+
242
+ /**
243
+ * Immutable structural key.
244
+ *
245
+ * Structural keys are JSON-like values with `NaN` and positive/negative
246
+ * infinity also supported, or `Uint8Array`.
247
+ *
248
+ * @see {@link Structural}
249
+ * @see {@link structuralLookup}
250
+ */
251
+ export type StructuralKey =
252
+ | StructuralScalar
253
+ | ReadonlyArray<StructuralKey>
254
+ | { readonly [key: string]: StructuralKey };
255
+
256
+ /**
257
+ * Compile-time structural form of `T` for structural lookup APIs.
258
+ *
259
+ * This exists because {@link StructuralKey} is the runtime serialization model,
260
+ * not a good public generic constraint for interface-shaped objects.
261
+ * `StructuralKey` models object values with a string index signature, which is
262
+ * stricter than ordinary interfaces like `{ readonly id: string }` even though
263
+ * the runtime serializer accepts such plain objects.
264
+ *
265
+ * `Structural<T>` checks a concrete type recursively at compile time instead:
266
+ * scalars pass through, arrays recurse, object properties recurse, and
267
+ * function-valued properties are rejected. This keeps public structural APIs
268
+ * ergonomic for interface-based inputs while preserving the same runtime
269
+ * constraints as structural lookup serialization.
270
+ *
271
+ * @see {@link StructuralKey}
272
+ * @see {@link structuralLookup}
273
+ */
274
+ export type Structural<T> = T extends StructuralScalar
275
+ ? T
276
+ : T extends ReadonlyArray<infer Item>
277
+ ? ReadonlyArray<Structural<Item>>
278
+ : T extends StructuralFunction
279
+ ? never
280
+ : T extends object
281
+ ? {
282
+ readonly [K in keyof T as Extract<
283
+ T[K],
284
+ StructuralFunction
285
+ > extends never
286
+ ? K
287
+ : never]: Structural<Exclude<T[K], StructuralFunction>>;
288
+ } & {
289
+ readonly [K in keyof T as Extract<
290
+ T[K],
291
+ StructuralFunction
292
+ > extends never
293
+ ? never
294
+ : K]?: never;
295
+ }
296
+ : never;
297
+
298
+ /** Function branch excluded by {@link Structural}. */
299
+ export type StructuralFunction = (...args: ReadonlyArray<unknown>) => unknown;
300
+
301
+ /**
302
+ * Returns the structural lookup key for `key`.
303
+ *
304
+ * Structural lookup keys are derived from JSON-like values plus `Uint8Array`.
305
+ * Equal structures produce the same lookup key even when they are different
306
+ * JavaScript instances.
307
+ *
308
+ * The derived key is memoized by non-null object identity in a module-scoped
309
+ * `WeakMap` shared by all callers, so keys must be immutable.
310
+ *
311
+ * Use this as a {@link Lookup} when logical equality should be based on
312
+ * structural value instead of reference identity.
313
+ *
314
+ * ### Example
315
+ *
316
+ * ```ts
317
+ * const byFilter = createLookupMap<
318
+ * { readonly table: string; readonly where: readonly [string, string] },
319
+ * string,
320
+ * StructuralLookupKey
321
+ * >({
322
+ * lookup: structuralLookup,
323
+ * });
324
+ *
325
+ * byFilter.set({ table: "todo", where: ["owner", "ada"] }, "cached");
326
+ * byFilter.get({ table: "todo", where: ["owner", "ada"] }); // "cached"
327
+ * ```
328
+ *
329
+ * @see {@link StructuralKey}
330
+ * @see {@link Structural}
331
+ */
332
+ export const structuralLookup = <K>(key: Structural<K>): StructuralLookupKey =>
333
+ structuralLookupInternal(key) as StructuralLookupKey;
334
+
335
+ const structuralLookupKeyByValue = new WeakMap<object, string>();
336
+
337
+ const structuralLookupInternal = (
338
+ value: unknown,
339
+ path: Set<object> = new Set(),
340
+ ): string => {
341
+ switch (typeof value) {
342
+ case "string":
343
+ return `s:${JSON.stringify(value)}`;
344
+ case "number":
345
+ if (Number.isNaN(value)) return "n:NaN";
346
+ if (value === Number.POSITIVE_INFINITY) return "n:Infinity";
347
+ if (value === Number.NEGATIVE_INFINITY) return "n:-Infinity";
348
+ return Object.is(value, -0) ? "n:0" : `n:${value}`;
349
+ case "boolean":
350
+ return value ? "b:true" : "b:false";
351
+ case "object": {
352
+ if (value === null) return "l:null";
353
+
354
+ const cachedLookupKey = structuralLookupKeyByValue.get(value);
355
+ if (cachedLookupKey) return cachedLookupKey;
356
+
357
+ let lookupKey: string;
358
+
359
+ if (Array.isArray(value)) {
360
+ assert(
361
+ !path.has(value),
362
+ "Structural lookup keys must not contain cycles.",
363
+ );
364
+ path.add(value);
365
+ lookupKey = getStructuralArrayLookupKey(value, path);
366
+ path.delete(value);
367
+ } else if (isPlainObject(value)) {
368
+ assert(
369
+ !path.has(value),
370
+ "Structural lookup keys must not contain cycles.",
371
+ );
372
+ path.add(value);
373
+ lookupKey = getStructuralObjectLookupKey(value, path);
374
+ path.delete(value);
375
+ } else if (Uint8Array.is(value)) {
376
+ lookupKey = `u:${uint8ArrayToBase64Url(value)}`;
377
+ } else {
378
+ assert(
379
+ false,
380
+ "Structural lookup keys must be JSON-like values or Uint8Array.",
381
+ );
382
+ }
383
+
384
+ structuralLookupKeyByValue.set(value, lookupKey);
385
+ return lookupKey;
386
+ }
387
+ default:
388
+ assert(
389
+ false,
390
+ "Structural lookup keys must be JSON-like values or Uint8Array.",
391
+ );
392
+ }
393
+ };
394
+
395
+ const getStructuralArrayLookupKey = (
396
+ value: ReadonlyArray<unknown>,
397
+ path: Set<object>,
398
+ ): string =>
399
+ `a:[${Array.from(value, (item) => structuralLookupInternal(item, path)).join(",")}]`;
400
+
401
+ const getStructuralObjectLookupKey = (
402
+ value: object,
403
+ path: Set<object>,
404
+ ): string => {
405
+ const valueRecord = value as Record<string, unknown>;
406
+
407
+ const entries = Object.keys(valueRecord)
408
+ .toSorted()
409
+ .map((key) => {
410
+ const item = valueRecord[key];
411
+ return `${JSON.stringify(key)}:${structuralLookupInternal(item, path)}`;
412
+ });
413
+
414
+ return `o:{${entries.join(",")}}`;
415
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Microtask queue utilities.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import { appendToArray, type NonEmptyReadonlyArray } from "./Array.js";
8
+
9
+ /**
10
+ * Batches values and flushes them in a single microtask.
11
+ *
12
+ * Calls to {@link MicrotaskBatch.push} within the same tick are coalesced into
13
+ * one flush.
14
+ */
15
+ export interface MicrotaskBatch<T> extends Disposable {
16
+ /** Enqueues one item and schedules a microtask flush if needed. */
17
+ readonly push: (item: T) => void;
18
+
19
+ /** Flushes queued items immediately. */
20
+ readonly flushNow: () => void;
21
+ }
22
+
23
+ /** Creates {@link MicrotaskBatch}. */
24
+ export const createMicrotaskBatch = <T>(
25
+ onFlush: (items: NonEmptyReadonlyArray<T>) => void,
26
+ ): MicrotaskBatch<T> => {
27
+ let queue: NonEmptyReadonlyArray<T> | null = null;
28
+ let disposed = false;
29
+
30
+ const flushNow = () => {
31
+ if (disposed || queue == null) return;
32
+ const queuedItems = queue;
33
+ queue = null;
34
+ onFlush(queuedItems);
35
+ };
36
+
37
+ return {
38
+ push: (item) => {
39
+ if (disposed) return;
40
+
41
+ if (queue == null) {
42
+ queue = [item];
43
+ queueMicrotask(flushNow);
44
+ return;
45
+ }
46
+
47
+ queue = appendToArray(queue, item);
48
+ },
49
+
50
+ flushNow,
51
+
52
+ [Symbol.dispose]: () => {
53
+ if (disposed) return;
54
+ disposed = true;
55
+ queue = null;
56
+ },
57
+ };
58
+ };
package/src/Number.ts CHANGED
@@ -1,9 +1,22 @@
1
- import { NonEmptyReadonlyArray } from "./Array.js";
1
+ /**
2
+ * Number arithmetic, comparison, and branded numeric types.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import type { NonEmptyReadonlyArray } from "./Array.js";
2
8
  import { assertNonEmptyReadonlyArray } from "./Assert.js";
3
- import { err, ok, Result } from "./Result.js";
4
- import { NonNegativeInt, PositiveInt } from "./Type.js";
5
- import { IntentionalNever, Predicate, WidenLiteral } from "./Types.js";
6
- import { IsBranded } from "./Brand.js";
9
+ import type { IsBranded } from "./Brand.js";
10
+ import type { Result } from "./Result.js";
11
+ import { err, ok } from "./Result.js";
12
+ import {
13
+ brand,
14
+ lessThanOrEqualTo,
15
+ onePositiveInt,
16
+ NonNegativeInt,
17
+ PositiveInt,
18
+ } from "./Type.js";
19
+ import type { Predicate, WidenLiteral } from "./Types.js";
7
20
 
8
21
  export const increment = (n: number): number => n + 1;
9
22
 
@@ -35,13 +48,13 @@ export const isBetween =
35
48
  export const min = <T extends number>(
36
49
  ...values: [T, ...ReadonlyArray<T>]
37
50
  ): IsBranded<T> extends true ? T : WidenLiteral<T> =>
38
- values.reduce((a, b) => (a < b ? a : b)) as IntentionalNever;
51
+ values.reduce((a, b) => (a < b ? a : b)) as never;
39
52
 
40
53
  /** Returns the maximum value, preserving branded type if applicable. */
41
54
  export const max = <T extends number>(
42
55
  ...values: [T, ...ReadonlyArray<T>]
43
56
  ): IsBranded<T> extends true ? T : WidenLiteral<T> =>
44
- values.reduce((a, b) => (a > b ? a : b)) as IntentionalNever;
57
+ values.reduce((a, b) => (a > b ? a : b)) as never;
45
58
 
46
59
  /**
47
60
  * Divides items into buckets as evenly as possible, ensuring each bucket has at
@@ -84,3 +97,23 @@ export const computeBalancedBuckets = (
84
97
  assertNonEmptyReadonlyArray(indexes);
85
98
  return ok(indexes);
86
99
  };
100
+
101
+ /**
102
+ * Valid index for {@link fibonacciAt}, constrained to 1-78.
103
+ *
104
+ * Limited to 78 because F(79) exceeds JavaScript's `MAX_SAFE_INTEGER`.
105
+ */
106
+ export const FibonacciIndex = /*#__PURE__*/ brand(
107
+ "FibonacciIndex",
108
+ /*#__PURE__*/ lessThanOrEqualTo(78)(PositiveInt),
109
+ );
110
+ export type FibonacciIndex = typeof FibonacciIndex.Type;
111
+
112
+ /** Returns the Fibonacci number at the given index (1-indexed: 1,1,2,3,5,8,...). */
113
+ export const fibonacciAt = (index: FibonacciIndex): PositiveInt => {
114
+ if (index <= 2) return onePositiveInt;
115
+ let a = 1;
116
+ let b = 1;
117
+ for (let i = 3; i <= index; i++) [a, b] = [b, a + b];
118
+ return PositiveInt.orThrow(b);
119
+ };