@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.
Files changed (236) hide show
  1. package/dist/src/Array.d.ts +279 -173
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +105 -101
  4. package/dist/src/Assert.d.ts +39 -44
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +38 -31
  7. package/dist/src/BigInt.d.ts +6 -3
  8. package/dist/src/BigInt.d.ts.map +1 -1
  9. package/dist/src/BigInt.js +5 -2
  10. package/dist/src/Brand.d.ts +27 -40
  11. package/dist/src/Brand.d.ts.map +1 -1
  12. package/dist/src/Buffer.d.ts +27 -11
  13. package/dist/src/Buffer.d.ts.map +1 -1
  14. package/dist/src/Cache.d.ts +9 -4
  15. package/dist/src/Cache.d.ts.map +1 -1
  16. package/dist/src/Cache.js +8 -3
  17. package/dist/src/Callbacks.d.ts +30 -19
  18. package/dist/src/Callbacks.d.ts.map +1 -1
  19. package/dist/src/Console.d.ts +145 -66
  20. package/dist/src/Console.d.ts.map +1 -1
  21. package/dist/src/Console.js +91 -43
  22. package/dist/src/Crypto.d.ts +85 -42
  23. package/dist/src/Crypto.d.ts.map +1 -1
  24. package/dist/src/Crypto.js +44 -20
  25. package/dist/src/Eq.d.ts +87 -21
  26. package/dist/src/Eq.d.ts.map +1 -1
  27. package/dist/src/Eq.js +67 -19
  28. package/dist/src/Error.d.ts +3 -3
  29. package/dist/src/Error.d.ts.map +1 -1
  30. package/dist/src/Function.d.ts +119 -58
  31. package/dist/src/Function.d.ts.map +1 -1
  32. package/dist/src/Function.js +86 -43
  33. package/dist/src/Http.d.ts +394 -0
  34. package/dist/src/Http.d.ts.map +1 -0
  35. package/dist/src/Http.js +102 -0
  36. package/dist/src/Identicon.d.ts +16 -7
  37. package/dist/src/Identicon.d.ts.map +1 -1
  38. package/dist/src/Identicon.js +14 -5
  39. package/dist/src/LeakDetector.d.ts +88 -0
  40. package/dist/src/LeakDetector.d.ts.map +1 -0
  41. package/dist/src/LeakDetector.js +86 -0
  42. package/dist/src/LockManager.d.ts +18 -9
  43. package/dist/src/LockManager.d.ts.map +1 -1
  44. package/dist/src/LockManager.js +7 -8
  45. package/dist/src/Lookup.d.ts +43 -13
  46. package/dist/src/Lookup.d.ts.map +1 -1
  47. package/dist/src/Lookup.js +28 -8
  48. package/dist/src/Microtask.d.ts +1 -1
  49. package/dist/src/Microtask.d.ts.map +1 -1
  50. package/dist/src/Microtask.js +3 -7
  51. package/dist/src/Number.d.ts +49 -13
  52. package/dist/src/Number.d.ts.map +1 -1
  53. package/dist/src/Number.js +34 -5
  54. package/dist/src/Object.d.ts +160 -40
  55. package/dist/src/Object.d.ts.map +1 -1
  56. package/dist/src/Object.js +92 -46
  57. package/dist/src/Option.d.ts +10 -7
  58. package/dist/src/Option.d.ts.map +1 -1
  59. package/dist/src/Option.js +9 -6
  60. package/dist/src/Order.d.ts +21 -21
  61. package/dist/src/Order.d.ts.map +1 -1
  62. package/dist/src/Order.js +18 -21
  63. package/dist/src/Platform.d.ts +31 -0
  64. package/dist/src/Platform.d.ts.map +1 -1
  65. package/dist/src/Platform.js +98 -0
  66. package/dist/src/Random.d.ts +32 -17
  67. package/dist/src/Random.d.ts.map +1 -1
  68. package/dist/src/Random.js +12 -8
  69. package/dist/src/Redacted.d.ts +43 -29
  70. package/dist/src/Redacted.d.ts.map +1 -1
  71. package/dist/src/Redacted.js +14 -3
  72. package/dist/src/Ref.d.ts +11 -3
  73. package/dist/src/Ref.d.ts.map +1 -1
  74. package/dist/src/Ref.js +2 -2
  75. package/dist/src/RefCount.d.ts +2 -6
  76. package/dist/src/RefCount.d.ts.map +1 -1
  77. package/dist/src/RefCount.js +33 -90
  78. package/dist/src/Relation.d.ts +70 -5
  79. package/dist/src/Relation.d.ts.map +1 -1
  80. package/dist/src/Relation.js +77 -2
  81. package/dist/src/Resource.d.ts +590 -155
  82. package/dist/src/Resource.d.ts.map +1 -1
  83. package/dist/src/Resource.js +592 -343
  84. package/dist/src/Result.d.ts +838 -191
  85. package/dist/src/Result.d.ts.map +1 -1
  86. package/dist/src/Result.js +163 -102
  87. package/dist/src/Schedule.d.ts +684 -231
  88. package/dist/src/Schedule.d.ts.map +1 -1
  89. package/dist/src/Schedule.js +766 -295
  90. package/dist/src/Set.d.ts +89 -40
  91. package/dist/src/Set.d.ts.map +1 -1
  92. package/dist/src/Set.js +50 -29
  93. package/dist/src/Sqlite.d.ts +29 -72
  94. package/dist/src/Sqlite.d.ts.map +1 -1
  95. package/dist/src/Sqlite.js +39 -45
  96. package/dist/src/StackTrace.d.ts +40 -0
  97. package/dist/src/StackTrace.d.ts.map +1 -0
  98. package/dist/src/StackTrace.js +140 -0
  99. package/dist/src/Store.d.ts +5 -4
  100. package/dist/src/Store.d.ts.map +1 -1
  101. package/dist/src/Store.js +5 -4
  102. package/dist/src/String.d.ts +7 -1
  103. package/dist/src/String.d.ts.map +1 -1
  104. package/dist/src/String.js +20 -7
  105. package/dist/src/Task.d.ts +3428 -1629
  106. package/dist/src/Task.d.ts.map +1 -1
  107. package/dist/src/Task.js +1876 -1279
  108. package/dist/src/Test.d.ts +17 -124
  109. package/dist/src/Test.d.ts.map +1 -1
  110. package/dist/src/Test.js +17 -68
  111. package/dist/src/Time.d.ts +108 -49
  112. package/dist/src/Time.d.ts.map +1 -1
  113. package/dist/src/Time.js +172 -73
  114. package/dist/src/Type.d.ts +3542 -1926
  115. package/dist/src/Type.d.ts.map +1 -1
  116. package/dist/src/Type.js +3070 -1784
  117. package/dist/src/Types.d.ts +200 -100
  118. package/dist/src/Types.d.ts.map +1 -1
  119. package/dist/src/Types.js +39 -8
  120. package/dist/src/WebSocket.d.ts +48 -23
  121. package/dist/src/WebSocket.d.ts.map +1 -1
  122. package/dist/src/WebSocket.js +15 -6
  123. package/dist/src/Worker.d.ts +94 -36
  124. package/dist/src/Worker.d.ts.map +1 -1
  125. package/dist/src/Worker.js +58 -24
  126. package/dist/src/index.d.ts +55 -52
  127. package/dist/src/index.d.ts.map +1 -1
  128. package/dist/src/index.js +3 -1
  129. package/dist/src/intl/cs.d.ts +94 -0
  130. package/dist/src/intl/cs.d.ts.map +1 -0
  131. package/dist/src/intl/cs.js +220 -0
  132. package/dist/src/intl/index.d.ts +7 -0
  133. package/dist/src/intl/index.d.ts.map +1 -0
  134. package/dist/src/intl/index.js +6 -0
  135. package/dist/src/local-first/Db.d.ts +12 -8
  136. package/dist/src/local-first/Db.d.ts.map +1 -1
  137. package/dist/src/local-first/Db.js +107 -130
  138. package/dist/src/local-first/Error.d.ts +4 -4
  139. package/dist/src/local-first/Evolu.d.ts +338 -92
  140. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  141. package/dist/src/local-first/Evolu.js +47 -55
  142. package/dist/src/local-first/LocalAuth.d.ts +4 -4
  143. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  144. package/dist/src/local-first/Owner.d.ts +61 -24
  145. package/dist/src/local-first/Owner.d.ts.map +1 -1
  146. package/dist/src/local-first/Owner.js +45 -8
  147. package/dist/src/local-first/Protocol.d.ts +44 -31
  148. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  149. package/dist/src/local-first/Protocol.js +36 -13
  150. package/dist/src/local-first/Query.d.ts +147 -62
  151. package/dist/src/local-first/Query.d.ts.map +1 -1
  152. package/dist/src/local-first/Query.js +112 -58
  153. package/dist/src/local-first/Relay.d.ts +44 -14
  154. package/dist/src/local-first/Relay.d.ts.map +1 -1
  155. package/dist/src/local-first/Schema.d.ts +92 -41
  156. package/dist/src/local-first/Schema.d.ts.map +1 -1
  157. package/dist/src/local-first/Schema.js +25 -8
  158. package/dist/src/local-first/Shared.d.ts +23 -21
  159. package/dist/src/local-first/Shared.d.ts.map +1 -1
  160. package/dist/src/local-first/Shared.js +85 -42
  161. package/dist/src/local-first/Storage.d.ts +53 -32
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +194 -234
  164. package/dist/src/local-first/Timestamp.d.ts +21 -21
  165. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  166. package/dist/src/local-first/Timestamp.js +2 -2
  167. package/dist/src/local-first/index.d.ts +11 -11
  168. package/package.json +14 -13
  169. package/src/Array.ts +306 -205
  170. package/src/Assert.ts +48 -68
  171. package/src/BigInt.ts +6 -3
  172. package/src/Brand.ts +27 -40
  173. package/src/Buffer.ts +28 -12
  174. package/src/Cache.ts +9 -4
  175. package/src/Callbacks.ts +30 -19
  176. package/src/Console.ts +156 -84
  177. package/src/Crypto.ts +83 -41
  178. package/src/Eq.ts +95 -22
  179. package/src/Error.ts +5 -2
  180. package/src/Function.ts +167 -59
  181. package/src/Http.ts +549 -0
  182. package/src/Identicon.ts +16 -7
  183. package/src/LeakDetector.ts +182 -0
  184. package/src/LockManager.ts +43 -39
  185. package/src/Lookup.ts +62 -28
  186. package/src/Microtask.ts +19 -23
  187. package/src/Number.ts +64 -12
  188. package/src/Object.ts +173 -49
  189. package/src/Option.ts +10 -7
  190. package/src/Order.ts +23 -21
  191. package/src/Platform.ts +136 -0
  192. package/src/Polyfills.ts +6 -12
  193. package/src/Random.ts +32 -19
  194. package/src/Redacted.ts +44 -30
  195. package/src/Ref.ts +14 -6
  196. package/src/RefCount.ts +38 -80
  197. package/src/Relation.ts +202 -10
  198. package/src/Resource.ts +1199 -591
  199. package/src/Result.ts +930 -271
  200. package/src/Schedule.ts +859 -343
  201. package/src/Set.ts +89 -40
  202. package/src/Sqlite.ts +142 -137
  203. package/src/StackTrace.ts +230 -0
  204. package/src/Store.ts +8 -7
  205. package/src/String.ts +22 -5
  206. package/src/Task.ts +5109 -2845
  207. package/src/Test.ts +20 -167
  208. package/src/Time.ts +293 -90
  209. package/src/Type.ts +10034 -4117
  210. package/src/Types.ts +227 -101
  211. package/src/WebSocket.ts +64 -36
  212. package/src/Worker.ts +180 -72
  213. package/src/index.ts +55 -52
  214. package/src/intl/cs.ts +388 -0
  215. package/src/intl/index.ts +7 -0
  216. package/src/local-first/Db.ts +161 -190
  217. package/src/local-first/Error.ts +4 -4
  218. package/src/local-first/Evolu.ts +495 -250
  219. package/src/local-first/LocalAuth.ts +6 -12
  220. package/src/local-first/Owner.ts +58 -21
  221. package/src/local-first/Protocol.ts +49 -31
  222. package/src/local-first/Query.ts +146 -61
  223. package/src/local-first/Relay.ts +51 -21
  224. package/src/local-first/Schema.ts +109 -51
  225. package/src/local-first/Shared.ts +253 -214
  226. package/src/local-first/Storage.ts +266 -264
  227. package/src/local-first/Timestamp.ts +25 -18
  228. package/src/local-first/index.ts +11 -11
  229. package/dist/src/Skiplist.d.ts +0 -28
  230. package/dist/src/Skiplist.d.ts.map +0 -1
  231. package/dist/src/Skiplist.js +0 -64
  232. package/dist/src/Tracer.d.ts +0 -48
  233. package/dist/src/Tracer.d.ts.map +0 -1
  234. package/dist/src/Tracer.js +0 -6
  235. package/src/Skiplist.ts +0 -108
  236. package/src/Tracer.ts +0 -54
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * @module
5
5
  */
6
+ import { assertNotDisposed } from "./Assert.js";
7
+ import { isFunction } from "./Object.js";
6
8
  /**
7
9
  * Helper function to ensure exhaustive matching in a switch statement. Throws
8
10
  * an error if an unhandled case is encountered.
@@ -10,32 +12,36 @@
10
12
  * ### Example
11
13
  *
12
14
  * ```ts
15
+ * import { exhaustiveCheck } from "@evolu/common";
16
+ *
13
17
  * type Color = "red" | "green" | "blue";
18
+ * const handled: Array<string> = [];
14
19
  *
15
20
  * const handleColor = (color: Color): void => {
16
21
  * switch (color) {
17
22
  * case "red":
18
- * console.log("Handling red");
23
+ * handled.push("Handling red");
19
24
  * break;
20
25
  * case "green":
21
- * console.log("Handling green");
26
+ * handled.push("Handling green");
22
27
  * break;
23
28
  * case "blue":
24
- * console.log("Handling blue");
29
+ * handled.push("Handling blue");
25
30
  * break;
26
31
  * default:
27
- * exhaustiveCheck(color); // Ensures all cases are handled
32
+ * exhaustiveCheck(color);
28
33
  * }
29
34
  * };
35
+ *
36
+ * handleColor("blue");
37
+ * expect(handled).toEqual(["Handling blue"]);
30
38
  * ```
31
39
  *
32
40
  * Use this primarily in side-effect switches (`void` branches). For
33
41
  * value-producing switches, TypeScript can enforce exhaustiveness without a
34
- * `default` branch.
35
- *
36
- * ### Example
42
+ * `default` branch in either of the following styles.
37
43
  *
38
- * Return from each case for value-producing switches.
44
+ * ### Return from every case
39
45
  *
40
46
  * ```ts
41
47
  * type Color = "red" | "green" | "blue";
@@ -50,11 +56,11 @@
50
56
  * return "#0000ff";
51
57
  * }
52
58
  * };
53
- * ```
54
59
  *
55
- * ### Example
60
+ * expect(colorToHex("green")).toBe("#00ff00");
61
+ * ```
56
62
  *
57
- * Use assignment + no `default` to get exhaustiveness by definite assignment.
63
+ * ### Assign in every case
58
64
  *
59
65
  * ```ts
60
66
  * type Input =
@@ -62,7 +68,7 @@
62
68
  * | { readonly type: "Query" }
63
69
  * | { readonly type: "Export" };
64
70
  *
65
- * const onInput = (input: Input): void => {
71
+ * const inputToKind = (input: Input): "A" | "B" | "C" => {
66
72
  * let result: "A" | "B" | "C";
67
73
  *
68
74
  * switch (input.type) {
@@ -77,12 +83,10 @@
77
83
  * break;
78
84
  * }
79
85
  *
80
- * handleKind(result);
86
+ * return result;
81
87
  * };
82
88
  *
83
- * const handleKind = (kind: "A" | "B" | "C"): void => {
84
- * console.log(kind);
85
- * };
89
+ * expect(inputToKind({ type: "Query" })).toBe("B");
86
90
  * ```
87
91
  */
88
92
  export const exhaustiveCheck = (value) => {
@@ -97,41 +101,78 @@ export const exhaustiveCheck = (value) => {
97
101
  * ### Example
98
102
  *
99
103
  * ```ts
100
- * const values = [1, 2, 3];
101
- * const same = values.map(identity); // [1, 2, 3]
104
+ * import { identity } from "@evolu/common";
102
105
  *
106
+ * const values = [1, 2, 3];
107
+ * const object = { value: 1 };
103
108
  * const getTransform = (shouldDouble: boolean) =>
104
- * shouldDouble ? (x: number) => x * 2 : identity;
109
+ * shouldDouble ? (value: number) => value * 2 : identity;
110
+ *
111
+ * expect(values.map(identity)).toEqual([1, 2, 3]);
112
+ * expect(identity(object)).toBe(object);
113
+ * expect(getTransform(false)(2)).toBe(2);
105
114
  * ```
106
115
  */
107
116
  export const identity = (a) => a;
117
+ export function disposable(value, disposer = null) {
118
+ const mutableValue = value;
119
+ const ownedDisposer = disposer?.move() ?? new DisposableStack();
120
+ for (const [key, property] of Object.entries(value)) {
121
+ if (!isFunction(property))
122
+ continue;
123
+ mutableValue[key] = (...args) => {
124
+ assertNotDisposed(ownedDisposer);
125
+ return property(...args);
126
+ };
127
+ }
128
+ if (ownedDisposer instanceof AsyncDisposableStack) {
129
+ value[Symbol.asyncDispose] = () => ownedDisposer.disposeAsync();
130
+ }
131
+ else {
132
+ value[Symbol.dispose] = () => ownedDisposer.dispose();
133
+ }
134
+ return value;
135
+ }
136
+ export const isDisposable = (value) => {
137
+ if (typeof value !== "object" || value === null)
138
+ return false;
139
+ return (isFunction(value[Symbol.dispose]) ||
140
+ isFunction(value[Symbol.asyncDispose]));
141
+ };
108
142
  /**
109
- * Creates a {@link Lazy} from a precomputed value.
143
+ * Creates a {@link Thunk} that always returns a precomputed value.
110
144
  *
111
- * Use when the value is expensive to compute and want to compute it once at
145
+ * Use when the value is expensive to compute and you want to compute it once at
112
146
  * definition time rather than on every call.
113
147
  *
114
148
  * ### Example
115
149
  *
116
150
  * ```ts
117
- * // Computed once at definition, returned on every call
118
- * const getConfig = lazy(parseConfig(rawConfig));
151
+ * import { constant } from "@evolu/common";
152
+ *
153
+ * let version = 0;
154
+ * const readConfig = () => ({ version: ++version });
155
+ * const getConstantConfig = constant(readConfig());
156
+ * const getFreshConfig = () => readConfig();
119
157
  *
120
- * // vs. computed on every call
121
- * const getConfig = () => parseConfig(rawConfig);
158
+ * expect(getConstantConfig()).toBe(getConstantConfig());
159
+ * expect(getConstantConfig().version).toBe(1);
160
+ * expect(getFreshConfig().version).toBe(2);
161
+ * expect(getFreshConfig().version).toBe(3);
122
162
  * ```
123
163
  */
124
- export const lazy = (value) => () => value;
125
- /** A {@link Lazy} that returns `true`. */
126
- export const lazyTrue = /*#__PURE__*/ lazy(true);
127
- /** A {@link Lazy} that returns `false`. */
128
- export const lazyFalse = /*#__PURE__*/ lazy(false);
129
- /** A {@link Lazy} that returns `null`. */
130
- export const lazyNull = /*#__PURE__*/ lazy(null);
131
- /** A {@link Lazy} that returns `undefined`. */
132
- export const lazyUndefined = /*#__PURE__*/ lazy(undefined);
133
- /** A {@link Lazy} that returns `undefined` for void callbacks. */
134
- export const lazyVoid = lazyUndefined;
164
+ export const constant = (value) => () => value;
165
+ /** A {@link Thunk} that returns `true`. */
166
+ export const constTrue = /*#__PURE__*/ constant(true);
167
+ /** A {@link Thunk} that returns `false`. */
168
+ export const constFalse = /*#__PURE__*/ constant(false);
169
+ /** A {@link Thunk} that returns `null`. */
170
+ export const constNull = /*#__PURE__*/ constant(null);
171
+ /** A {@link Thunk} that returns `undefined`. */
172
+ export const constUndefined =
173
+ /*#__PURE__*/ constant(undefined);
174
+ /** A {@link Thunk} that returns `undefined` for void callbacks. */
175
+ export const constVoid = constUndefined;
135
176
  /**
136
177
  * Development placeholder that always throws.
137
178
  *
@@ -142,17 +183,19 @@ export const lazyVoid = lazyUndefined;
142
183
  * ### Example
143
184
  *
144
185
  * ```ts
145
- * // Type inferred from return type annotation
146
- * const fetchUser = (id: UserId): Result<User, FetchError> => todo();
186
+ * import { todo } from "@evolu/common";
147
187
  *
148
- * expectTypeOf(fetchUser).returns.toEqualTypeOf<
149
- * Result<User, FetchError>
150
- * >();
188
+ * interface Config {
189
+ * readonly theme: string;
190
+ * }
151
191
  *
152
- * // Explicit generic when no return type
192
+ * const getCount = (): number => todo();
153
193
  * const getConfig = () => todo<Config>();
154
194
  *
155
- * expectTypeOf(getConfig).returns.toEqualTypeOf<Config>();
195
+ * expectTypeOf<
196
+ * [ReturnType<typeof getCount>, ReturnType<typeof getConfig>]
197
+ * >().toEqualTypeOf<[number, Config]>();
198
+ * expect(getCount).toThrow("not yet implemented");
156
199
  * ```
157
200
  */
158
201
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
@@ -0,0 +1,394 @@
1
+ /**
2
+ * Task-aware HTTP helpers.
3
+ *
4
+ * @module
5
+ */
6
+ import { type Result } from "./Result.ts";
7
+ import type { Task } from "./Task.ts";
8
+ import type { Typed } from "./Type.ts";
9
+ import type { Awaitable } from "./Types.ts";
10
+ /**
11
+ * WHATWG-fetch-compatible function used by {@link fetch}.
12
+ *
13
+ * A default dependency, replaceable at the composition root — for a
14
+ * platform-optimized fetch such as React Native's, a configured undici
15
+ * instance, or a test double. Implementations must be pre-bound; unbound
16
+ * `globalThis.fetch` throws in browsers.
17
+ *
18
+ * @group Fetch
19
+ */
20
+ export type NativeFetch = typeof globalThis.fetch;
21
+ /**
22
+ * Dependency wrapper for {@link NativeFetch}.
23
+ *
24
+ * @group Fetch
25
+ */
26
+ export interface NativeFetchDep {
27
+ readonly nativeFetch: NativeFetch;
28
+ }
29
+ /**
30
+ * Body mode for {@link fetch}.
31
+ *
32
+ * A mode selects a built-in consumer that reads the {@link Response} inside the
33
+ * fetch Task:
34
+ *
35
+ * - `"bytes"` — reads the body as a `Uint8Array<ArrayBuffer>`.
36
+ * - `"headers"` — cancels the body and returns {@link FetchResponse}. Pair it with
37
+ * `{ method: "HEAD" }` when the server should not send a body.
38
+ * - `"json"` — parses the body as JSON and returns `unknown`. Narrow or decode at
39
+ * the call site; there is deliberately no generic to cast through.
40
+ * - `"text"` — reads the body as a string.
41
+ *
42
+ * Body modes (`"bytes"`, `"json"`, `"text"`) return {@link FetchStatusError} for
43
+ * non-2xx responses. `"headers"` does not judge status, because the status is
44
+ * usually the value being asked for.
45
+ *
46
+ * @group Fetch
47
+ */
48
+ export type FetchMode = "text" | "json" | "bytes" | "headers";
49
+ /**
50
+ * Body-free {@link Response} view safe to return from {@link fetch}.
51
+ *
52
+ * {@link fetch} returns this view only after the native body has been drained,
53
+ * errored, or cancelled, so none of its usable members depends on the request
54
+ * signal. Body-reading members and `Response.clone` are omitted because they
55
+ * would be invalid after the fetch Task settles.
56
+ *
57
+ * This is a TypeScript contract, not a runtime security boundary: the runtime
58
+ * value can still be a native Response, and casts can access omitted members.
59
+ *
60
+ * @group Fetch
61
+ */
62
+ export type FetchResponse = Omit<Response, "arrayBuffer" | "blob" | "body" | "bodyUsed" | "bytes" | "clone" | "formData" | "json" | "text">;
63
+ /**
64
+ * Error returned by {@link fetch} body modes.
65
+ *
66
+ * The union distinguishes native request failures, non-2xx responses, and body
67
+ * read failures. Narrow on `type` when handling a specific case. The
68
+ * distinction matters for retries: {@link FetchBodyError} means a 2xx response
69
+ * failed during reading, so the server may have committed the request.
70
+ *
71
+ * @group Fetch
72
+ */
73
+ export type FetchError = FetchTransportError | FetchStatusError | FetchBodyError;
74
+ /**
75
+ * Error returned when the native fetch request fails before a response exists.
76
+ *
77
+ * @group Fetch
78
+ */
79
+ export interface FetchTransportError extends Typed<"FetchTransportError"> {
80
+ readonly error: unknown;
81
+ }
82
+ /**
83
+ * Error returned when a body mode receives a non-2xx response.
84
+ *
85
+ * The response body is drained as text before this error is returned. Draining
86
+ * buffers the whole error body; error responses are expected to be small. If
87
+ * the drain itself fails, `body` contains the read error.
88
+ *
89
+ * @group Fetch
90
+ */
91
+ export interface FetchStatusError extends Typed<"FetchStatusError"> {
92
+ readonly response: FetchResponse;
93
+ readonly body: Result<string, unknown>;
94
+ }
95
+ /**
96
+ * Error returned when a body mode cannot read or decode a 2xx response.
97
+ *
98
+ * This includes mid-stream network failures, decoding failures, and — for the
99
+ * `"json"` mode — empty bodies such as 204 responses, which are not JSON. Do
100
+ * not use `"json"` for endpoints that return no content.
101
+ *
102
+ * @group Fetch
103
+ */
104
+ export interface FetchBodyError extends Typed<"FetchBodyError"> {
105
+ readonly response: FetchResponse;
106
+ readonly error: unknown;
107
+ }
108
+ /**
109
+ * Consumes a native {@link Response} before {@link fetch} settles.
110
+ *
111
+ * The callback runs inside the fetch Task, while the request signal is still
112
+ * alive. Return a Result error for expected domain failures. Throwing or
113
+ * rejecting with a non-abort error is a defect, like any other Task body.
114
+ *
115
+ * Consumers take values, not dependencies; close over anything they need. A
116
+ * consumer that needs its own timeout, retries, or child Tasks has outgrown
117
+ * being a consumer: fetch the body with a mode and compose Tasks on the plain
118
+ * value, or write a Task that owns the whole request.
119
+ *
120
+ * Consumers do not have to normalize abort. {@link fetch} rethrows Evolu
121
+ * {@link AbortError}, normalizes host abort errors after the Run aborts, and
122
+ * treats an Err returned after abort as abort control flow — so a consumer's
123
+ * `try`/`catch` around a body read cannot accidentally turn an abort into a
124
+ * domain error. An Ok returned after abort is kept; only errors are presumed to
125
+ * be abort in disguise.
126
+ *
127
+ * The Response and anything derived from its live body, such as a reader, must
128
+ * not escape the consumer. Consume to plain values before returning.
129
+ *
130
+ * @group Fetch
131
+ */
132
+ export type FetchConsume<T, E = never> = (response: Response) => Awaitable<Result<T, E>>;
133
+ /**
134
+ * Fetches a resource and consumes the {@link Response} inside the Task, so the
135
+ * body is read while the request signal is still alive.
136
+ *
137
+ * The request runs through {@link NativeFetchDep | run.deps.nativeFetch}.
138
+ * Because native fetch is a default dependency, platforms and tests can replace
139
+ * it without changing call sites.
140
+ *
141
+ * With a {@link FetchMode}, non-2xx responses return {@link FetchStatusError}
142
+ * (except `"headers"`, which reports status as a value) and unreadable bodies
143
+ * return {@link FetchBodyError}. With a {@link FetchConsume} callback, native
144
+ * status semantics apply: HTTP error statuses resolve, and the consumer decides
145
+ * how to interpret the status and body.
146
+ *
147
+ * `signal` is not accepted in init because abort is controlled by the current
148
+ * Run.
149
+ *
150
+ * Aborting the Run aborts the underlying request, any response that arrives
151
+ * after abort, and any in-progress body read. Abort is represented as
152
+ * {@link AbortError}, not FetchError: `run(fetch(...))` rejects with AbortError,
153
+ * and `run.abortable(fetch(...))` returns it as an {@link Err}.
154
+ *
155
+ * Some runtimes reject aborted fetches with their own error instead of
156
+ * `signal.reason`. This wrapper normalizes abort rejections from native fetch,
157
+ * built-in body reads, and consumer callbacks back to the Run's AbortError.
158
+ *
159
+ * `fetch` owns request lifetime and Response containment. It does not transform
160
+ * requests or interpret app protocols beyond the built-in modes. Use Task
161
+ * helpers for resilience, app helpers for app conventions, a replacement
162
+ * {@link NativeFetch} for request-wide behavior (base URLs, auth, logging), and
163
+ * consumers for response interpretation.
164
+ *
165
+ * ### Composing fetch
166
+ *
167
+ * Resilience is ordinary Task composition: wrap `fetch(url, "json")` in
168
+ * {@link timeout}, then in {@link retry}.
169
+ *
170
+ * ```ts
171
+ * import {
172
+ * createRun,
173
+ * exponential,
174
+ * fetch,
175
+ * retry,
176
+ * take,
177
+ * timeout,
178
+ * type NativeFetch,
179
+ * } from "@evolu/common";
180
+ *
181
+ * const fetchWithRetry = (url: string) =>
182
+ * retry(
183
+ * timeout(fetch(url, "json"), "30s"),
184
+ * take(2)(exponential("100ms")),
185
+ * );
186
+ *
187
+ * let requestCount = 0;
188
+ * const nativeFetch: NativeFetch = () => {
189
+ * requestCount++;
190
+ * return Promise.resolve(
191
+ * requestCount === 1
192
+ * ? new Response("Try again", { status: 503 })
193
+ * : new Response('{"name":"Ada"}'),
194
+ * );
195
+ * };
196
+ * await using run = createRun({ nativeFetch });
197
+ *
198
+ * expectOk(await run(fetchWithRetry("/api/user")), { name: "Ada" });
199
+ * ```
200
+ *
201
+ * App conventions belong in small app-owned helpers. For example, posting JSON
202
+ * is native `init` plus two conventions worth centralizing — the content-type
203
+ * header and the stringify:
204
+ *
205
+ * ```ts
206
+ * import {
207
+ * createRun,
208
+ * fetch,
209
+ * type FetchError,
210
+ * type NativeFetch,
211
+ * type Task,
212
+ * } from "@evolu/common";
213
+ *
214
+ * const postJson = (
215
+ * url: string,
216
+ * data: unknown,
217
+ * ): Task<unknown, FetchError> =>
218
+ * fetch(url, "json", {
219
+ * method: "POST",
220
+ * headers: { "content-type": "application/json" },
221
+ * body: JSON.stringify(data),
222
+ * });
223
+ *
224
+ * const nativeFetch: NativeFetch = () =>
225
+ * Promise.resolve(new Response('{"id":"user-1"}'));
226
+ * await using run = createRun({ nativeFetch });
227
+ *
228
+ * expectOk(await run(postJson("/api/users", { name: "Ada" })), {
229
+ * id: "user-1",
230
+ * });
231
+ * ```
232
+ *
233
+ * Your app's version will grow your conventions — auth, envelopes, error
234
+ * mapping — which is why it belongs to the app, not to `fetch`.
235
+ *
236
+ * ### Intercepting requests
237
+ *
238
+ * Request-wide behavior belongs to a replacement {@link NativeFetch} installed
239
+ * at the composition root. This is the equivalent of interceptors or hooks in
240
+ * libraries that expose client instances.
241
+ *
242
+ * ```ts
243
+ * import { createRun, fetch, type NativeFetch } from "@evolu/common";
244
+ *
245
+ * const token = "secret-token";
246
+ * const baseUrl = "https://api.example.com/v1/";
247
+ * let interceptedRequest: Request | undefined;
248
+ * const baseFetch: NativeFetch = (input, init) => {
249
+ * interceptedRequest = new Request(input, init);
250
+ * return Promise.resolve(new Response("ok"));
251
+ * };
252
+ *
253
+ * const nativeFetch: NativeFetch = (input, init) => {
254
+ * const headers = new Headers(init?.headers);
255
+ * headers.set("authorization", `Bearer ${token}`);
256
+ *
257
+ * // Only string inputs are resolved against the base URL; URL and Request
258
+ * // inputs are passed through unchanged.
259
+ * const url =
260
+ * typeof input === "string" ? new URL(input, baseUrl) : input;
261
+ * return baseFetch(url, { ...init, headers });
262
+ * };
263
+ *
264
+ * await using run = createRun({ nativeFetch });
265
+ * expectOk(await run(fetch("users", "text")), "ok");
266
+ * expect({
267
+ * url: interceptedRequest?.url,
268
+ * authorization: interceptedRequest?.headers.get("authorization"),
269
+ * }).toEqual({
270
+ * url: "https://api.example.com/v1/users",
271
+ * authorization: "Bearer secret-token",
272
+ * });
273
+ * ```
274
+ *
275
+ * ### Consuming responses
276
+ *
277
+ * Built-in modes handle common bodies. Specialized response interpretation
278
+ * belongs in a consumer. Typed decoders, response envelopes, streaming, and
279
+ * custom status semantics can be built on top without changing `fetch`.
280
+ *
281
+ * ```ts
282
+ * import {
283
+ * createRun,
284
+ * fetch,
285
+ * ok,
286
+ * type FetchTransportError,
287
+ * type NativeFetch,
288
+ * type Task,
289
+ * } from "@evolu/common";
290
+ *
291
+ * const nativeFetch: NativeFetch = (input) =>
292
+ * Promise.resolve(
293
+ * String(input).endsWith("/metadata")
294
+ * ? new Response(null, {
295
+ * status: 204,
296
+ * headers: { "cache-control": "max-age=60" },
297
+ * })
298
+ * : new Response('{"name":"Ada"}'),
299
+ * );
300
+ * await using run = createRun({ nativeFetch });
301
+ *
302
+ * const user = await run(fetch("/api/user", "json"));
303
+ * const metadata = fetch("/api/user/metadata", (response) =>
304
+ * ok({
305
+ * status: response.status,
306
+ * cache: response.headers.get("cache-control"),
307
+ * }),
308
+ * );
309
+ * expectTypeOf(metadata).toEqualTypeOf<
310
+ * Task<{ status: number; cache: string | null }, FetchTransportError>
311
+ * >();
312
+ * expectOk(user, { name: "Ada" });
313
+ * expectOk(await run(metadata), { status: 204, cache: "max-age=60" });
314
+ * ```
315
+ *
316
+ * ### Aborting fetch
317
+ *
318
+ * Abort follows the standard Task rules: a Fiber from `run(fetch(...))` rejects
319
+ * with {@link AbortError}, and `run.abortable(fetch(...))` returns it as a
320
+ * Result error.
321
+ *
322
+ * ```ts
323
+ * import {
324
+ * AbortError,
325
+ * createRun,
326
+ * fetch,
327
+ * type NativeFetch,
328
+ * } from "@evolu/common";
329
+ *
330
+ * const nativeFetch: NativeFetch = (_input, init) =>
331
+ * new Promise<Response>((_resolve, reject) => {
332
+ * const signal = init?.signal;
333
+ * if (!signal) throw new Error("Missing signal");
334
+ * signal.addEventListener("abort", () => reject(signal.reason), {
335
+ * once: true,
336
+ * });
337
+ * });
338
+ * await using run = createRun({ nativeFetch });
339
+ *
340
+ * const fiber = run.abortable(fetch("/api/user", "json"));
341
+ * fiber.abort();
342
+ * const result = await fiber;
343
+ *
344
+ * expect(!result.ok && AbortError.is(result.error)).toBe(true);
345
+ * ```
346
+ *
347
+ * @group Fetch
348
+ */
349
+ export declare function fetch(input: RequestInfo | URL, mode: "text", init?: Omit<RequestInit, "signal">): Task<string, FetchError>;
350
+ export declare function fetch(input: RequestInfo | URL, mode: "json", init?: Omit<RequestInit, "signal">): Task<unknown, FetchError>;
351
+ export declare function fetch(input: RequestInfo | URL, mode: "bytes", init?: Omit<RequestInit, "signal">): Task<Uint8Array<ArrayBuffer>, FetchError>;
352
+ export declare function fetch(input: RequestInfo | URL, mode: "headers", init?: Omit<RequestInit, "signal">): Task<FetchResponse, FetchTransportError>;
353
+ export declare function fetch<T, E = never>(input: RequestInfo | URL, consume: FetchConsume<T, E>, init?: Omit<RequestInit, "signal">): Task<T, FetchTransportError | E>;
354
+ /**
355
+ * Test {@link NativeFetch} that records calls and serves queued handlers.
356
+ *
357
+ * Each call shifts the next handler. Calling with an empty queue throws, so a
358
+ * test never silently reaches an unplanned request.
359
+ *
360
+ * @group Testing
361
+ */
362
+ export interface TestNativeFetch extends NativeFetch {
363
+ readonly calls: ReadonlyArray<TestNativeFetchCall>;
364
+ readonly handle: (handler: TestNativeFetchHandler) => void;
365
+ }
366
+ /**
367
+ * Recorded {@link TestNativeFetch} call.
368
+ *
369
+ * @group Testing
370
+ */
371
+ export interface TestNativeFetchCall {
372
+ readonly input: RequestInfo | URL;
373
+ readonly init: RequestInit | undefined;
374
+ }
375
+ /**
376
+ * Handler for one {@link TestNativeFetch} call.
377
+ *
378
+ * @group Testing
379
+ */
380
+ export type TestNativeFetchHandler = (input: RequestInfo | URL, init: RequestInit | undefined) => Awaitable<Response>;
381
+ /**
382
+ * Creates {@link TestNativeFetch}.
383
+ *
384
+ * @group Testing
385
+ */
386
+ export declare const testCreateNativeFetch: (...handlers: ReadonlyArray<TestNativeFetchHandler>) => TestNativeFetch;
387
+ /**
388
+ * Creates a Response body for {@link NativeFetch} tests that errors on first
389
+ * read.
390
+ *
391
+ * @group Testing
392
+ */
393
+ export declare const testCreateNativeFetchErroringBody: (error?: unknown) => ReadableStream<Uint8Array>;
394
+ //# sourceMappingURL=Http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Http.d.ts","sourceRoot":"","sources":["../../src/Http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAA+B,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAqB,IAAI,EAAW,MAAM,WAAW,CAAC;AAClE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;;;;;GASG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,UAAU,CAAC,KAAK,CAAC;AAElD;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,QAAQ,EACN,aAAa,GACb,MAAM,GACN,MAAM,GACN,UAAU,GACV,OAAO,GACP,OAAO,GACP,UAAU,GACV,MAAM,GACN,MAAM,CACT,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GACpB,mBAAmB,GAAG,gBAAgB,GAAG,cAAc,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,mBAAoB,SAAQ,KAAK,CAAC,qBAAqB,CAAC;IACvE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,kBAAkB,CAAC;IACjE,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,gBAAgB,CAAC;IAC7D,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,CACvC,QAAQ,EAAE,QAAQ,KACf,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuNG;AACH,wBAAgB,KAAK,CACnB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,GACjC,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAE5B,wBAAgB,KAAK,CACnB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,GACjC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AAE7B,wBAAgB,KAAK,CACnB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,GACjC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC,CAAC;AAE7C,wBAAgB,KAAK,CACnB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,EAAE,SAAS,EACf,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,GACjC,IAAI,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;AAE5C,wBAAgB,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAChC,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAC3B,IAAI,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,GACjC,IAAI,CAAC,CAAC,EAAE,mBAAmB,GAAG,CAAC,CAAC,CAAC;AA0EpC;;;;;;;GAOG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC5D;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,GAAG,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,CAAC;CACxC;AAED;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,CACnC,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,EAAE,WAAW,GAAG,SAAS,KAC1B,SAAS,CAAC,QAAQ,CAAC,CAAC;AAEzB;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,gBACnB,aAAa,CAAC,sBAAsB,CAAC,KACjD,eAkBF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,WACrC,OAAO,KACb,cAAc,CAAC,UAAU,CAKxB,CAAC"}