@evolu/common 8.0.0-next.4 → 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 (237) 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 +44 -44
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +44 -32
  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 +5 -3
  29. package/dist/src/Error.d.ts.map +1 -1
  30. package/dist/src/Error.js +16 -0
  31. package/dist/src/Function.d.ts +119 -58
  32. package/dist/src/Function.d.ts.map +1 -1
  33. package/dist/src/Function.js +86 -43
  34. package/dist/src/Http.d.ts +394 -0
  35. package/dist/src/Http.d.ts.map +1 -0
  36. package/dist/src/Http.js +102 -0
  37. package/dist/src/Identicon.d.ts +16 -7
  38. package/dist/src/Identicon.d.ts.map +1 -1
  39. package/dist/src/Identicon.js +14 -5
  40. package/dist/src/LeakDetector.d.ts +88 -0
  41. package/dist/src/LeakDetector.d.ts.map +1 -0
  42. package/dist/src/LeakDetector.js +86 -0
  43. package/dist/src/LockManager.d.ts +18 -9
  44. package/dist/src/LockManager.d.ts.map +1 -1
  45. package/dist/src/LockManager.js +7 -8
  46. package/dist/src/Lookup.d.ts +43 -13
  47. package/dist/src/Lookup.d.ts.map +1 -1
  48. package/dist/src/Lookup.js +28 -8
  49. package/dist/src/Microtask.d.ts +1 -1
  50. package/dist/src/Microtask.d.ts.map +1 -1
  51. package/dist/src/Microtask.js +3 -7
  52. package/dist/src/Number.d.ts +49 -13
  53. package/dist/src/Number.d.ts.map +1 -1
  54. package/dist/src/Number.js +34 -5
  55. package/dist/src/Object.d.ts +160 -40
  56. package/dist/src/Object.d.ts.map +1 -1
  57. package/dist/src/Object.js +92 -46
  58. package/dist/src/Option.d.ts +10 -7
  59. package/dist/src/Option.d.ts.map +1 -1
  60. package/dist/src/Option.js +9 -6
  61. package/dist/src/Order.d.ts +21 -21
  62. package/dist/src/Order.d.ts.map +1 -1
  63. package/dist/src/Order.js +18 -21
  64. package/dist/src/Platform.d.ts +31 -0
  65. package/dist/src/Platform.d.ts.map +1 -1
  66. package/dist/src/Platform.js +98 -0
  67. package/dist/src/Random.d.ts +32 -17
  68. package/dist/src/Random.d.ts.map +1 -1
  69. package/dist/src/Random.js +12 -8
  70. package/dist/src/Redacted.d.ts +43 -29
  71. package/dist/src/Redacted.d.ts.map +1 -1
  72. package/dist/src/Redacted.js +14 -3
  73. package/dist/src/Ref.d.ts +11 -3
  74. package/dist/src/Ref.d.ts.map +1 -1
  75. package/dist/src/Ref.js +2 -2
  76. package/dist/src/RefCount.d.ts +2 -6
  77. package/dist/src/RefCount.d.ts.map +1 -1
  78. package/dist/src/RefCount.js +33 -90
  79. package/dist/src/Relation.d.ts +70 -5
  80. package/dist/src/Relation.d.ts.map +1 -1
  81. package/dist/src/Relation.js +77 -2
  82. package/dist/src/Resource.d.ts +590 -155
  83. package/dist/src/Resource.d.ts.map +1 -1
  84. package/dist/src/Resource.js +592 -343
  85. package/dist/src/Result.d.ts +838 -191
  86. package/dist/src/Result.d.ts.map +1 -1
  87. package/dist/src/Result.js +163 -102
  88. package/dist/src/Schedule.d.ts +684 -231
  89. package/dist/src/Schedule.d.ts.map +1 -1
  90. package/dist/src/Schedule.js +766 -295
  91. package/dist/src/Set.d.ts +89 -40
  92. package/dist/src/Set.d.ts.map +1 -1
  93. package/dist/src/Set.js +50 -29
  94. package/dist/src/Sqlite.d.ts +29 -72
  95. package/dist/src/Sqlite.d.ts.map +1 -1
  96. package/dist/src/Sqlite.js +41 -46
  97. package/dist/src/StackTrace.d.ts +40 -0
  98. package/dist/src/StackTrace.d.ts.map +1 -0
  99. package/dist/src/StackTrace.js +140 -0
  100. package/dist/src/Store.d.ts +5 -4
  101. package/dist/src/Store.d.ts.map +1 -1
  102. package/dist/src/Store.js +5 -4
  103. package/dist/src/String.d.ts +7 -1
  104. package/dist/src/String.d.ts.map +1 -1
  105. package/dist/src/String.js +20 -7
  106. package/dist/src/Task.d.ts +3572 -1770
  107. package/dist/src/Task.d.ts.map +1 -1
  108. package/dist/src/Task.js +1879 -1274
  109. package/dist/src/Test.d.ts +17 -125
  110. package/dist/src/Test.d.ts.map +1 -1
  111. package/dist/src/Test.js +17 -68
  112. package/dist/src/Time.d.ts +108 -49
  113. package/dist/src/Time.d.ts.map +1 -1
  114. package/dist/src/Time.js +172 -73
  115. package/dist/src/Type.d.ts +3542 -1926
  116. package/dist/src/Type.d.ts.map +1 -1
  117. package/dist/src/Type.js +3070 -1784
  118. package/dist/src/Types.d.ts +200 -108
  119. package/dist/src/Types.d.ts.map +1 -1
  120. package/dist/src/Types.js +39 -8
  121. package/dist/src/WebSocket.d.ts +48 -23
  122. package/dist/src/WebSocket.d.ts.map +1 -1
  123. package/dist/src/WebSocket.js +15 -6
  124. package/dist/src/Worker.d.ts +94 -36
  125. package/dist/src/Worker.d.ts.map +1 -1
  126. package/dist/src/Worker.js +58 -24
  127. package/dist/src/index.d.ts +55 -52
  128. package/dist/src/index.d.ts.map +1 -1
  129. package/dist/src/index.js +3 -1
  130. package/dist/src/intl/cs.d.ts +94 -0
  131. package/dist/src/intl/cs.d.ts.map +1 -0
  132. package/dist/src/intl/cs.js +220 -0
  133. package/dist/src/intl/index.d.ts +7 -0
  134. package/dist/src/intl/index.d.ts.map +1 -0
  135. package/dist/src/intl/index.js +6 -0
  136. package/dist/src/local-first/Db.d.ts +12 -8
  137. package/dist/src/local-first/Db.d.ts.map +1 -1
  138. package/dist/src/local-first/Db.js +109 -132
  139. package/dist/src/local-first/Error.d.ts +4 -4
  140. package/dist/src/local-first/Evolu.d.ts +338 -92
  141. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  142. package/dist/src/local-first/Evolu.js +47 -55
  143. package/dist/src/local-first/LocalAuth.d.ts +4 -4
  144. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.d.ts +61 -24
  146. package/dist/src/local-first/Owner.d.ts.map +1 -1
  147. package/dist/src/local-first/Owner.js +45 -8
  148. package/dist/src/local-first/Protocol.d.ts +44 -31
  149. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  150. package/dist/src/local-first/Protocol.js +36 -13
  151. package/dist/src/local-first/Query.d.ts +147 -62
  152. package/dist/src/local-first/Query.d.ts.map +1 -1
  153. package/dist/src/local-first/Query.js +112 -58
  154. package/dist/src/local-first/Relay.d.ts +44 -14
  155. package/dist/src/local-first/Relay.d.ts.map +1 -1
  156. package/dist/src/local-first/Schema.d.ts +92 -41
  157. package/dist/src/local-first/Schema.d.ts.map +1 -1
  158. package/dist/src/local-first/Schema.js +25 -8
  159. package/dist/src/local-first/Shared.d.ts +23 -21
  160. package/dist/src/local-first/Shared.d.ts.map +1 -1
  161. package/dist/src/local-first/Shared.js +100 -57
  162. package/dist/src/local-first/Storage.d.ts +53 -32
  163. package/dist/src/local-first/Storage.d.ts.map +1 -1
  164. package/dist/src/local-first/Storage.js +194 -234
  165. package/dist/src/local-first/Timestamp.d.ts +21 -21
  166. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  167. package/dist/src/local-first/Timestamp.js +2 -2
  168. package/dist/src/local-first/index.d.ts +11 -11
  169. package/package.json +14 -13
  170. package/src/Array.ts +306 -205
  171. package/src/Assert.ts +54 -69
  172. package/src/BigInt.ts +6 -3
  173. package/src/Brand.ts +27 -40
  174. package/src/Buffer.ts +28 -12
  175. package/src/Cache.ts +9 -4
  176. package/src/Callbacks.ts +30 -19
  177. package/src/Console.ts +156 -84
  178. package/src/Crypto.ts +83 -41
  179. package/src/Eq.ts +95 -22
  180. package/src/Error.ts +8 -4
  181. package/src/Function.ts +167 -59
  182. package/src/Http.ts +549 -0
  183. package/src/Identicon.ts +16 -7
  184. package/src/LeakDetector.ts +182 -0
  185. package/src/LockManager.ts +43 -39
  186. package/src/Lookup.ts +62 -28
  187. package/src/Microtask.ts +19 -23
  188. package/src/Number.ts +64 -12
  189. package/src/Object.ts +173 -49
  190. package/src/Option.ts +10 -7
  191. package/src/Order.ts +23 -21
  192. package/src/Platform.ts +136 -0
  193. package/src/Polyfills.ts +6 -12
  194. package/src/Random.ts +32 -19
  195. package/src/Redacted.ts +44 -30
  196. package/src/Ref.ts +14 -6
  197. package/src/RefCount.ts +38 -80
  198. package/src/Relation.ts +202 -10
  199. package/src/Resource.ts +1199 -591
  200. package/src/Result.ts +930 -271
  201. package/src/Schedule.ts +859 -343
  202. package/src/Set.ts +89 -40
  203. package/src/Sqlite.ts +144 -138
  204. package/src/StackTrace.ts +230 -0
  205. package/src/Store.ts +8 -7
  206. package/src/String.ts +22 -5
  207. package/src/Task.ts +5101 -2832
  208. package/src/Test.ts +20 -168
  209. package/src/Time.ts +293 -90
  210. package/src/Type.ts +10034 -4117
  211. package/src/Types.ts +227 -110
  212. package/src/WebSocket.ts +64 -36
  213. package/src/Worker.ts +180 -72
  214. package/src/index.ts +55 -52
  215. package/src/intl/cs.ts +388 -0
  216. package/src/intl/index.ts +7 -0
  217. package/src/local-first/Db.ts +152 -179
  218. package/src/local-first/Error.ts +4 -4
  219. package/src/local-first/Evolu.ts +495 -250
  220. package/src/local-first/LocalAuth.ts +6 -12
  221. package/src/local-first/Owner.ts +58 -21
  222. package/src/local-first/Protocol.ts +49 -31
  223. package/src/local-first/Query.ts +146 -61
  224. package/src/local-first/Relay.ts +51 -21
  225. package/src/local-first/Schema.ts +109 -51
  226. package/src/local-first/Shared.ts +266 -226
  227. package/src/local-first/Storage.ts +266 -264
  228. package/src/local-first/Timestamp.ts +25 -18
  229. package/src/local-first/index.ts +11 -11
  230. package/dist/src/Skiplist.d.ts +0 -28
  231. package/dist/src/Skiplist.d.ts.map +0 -1
  232. package/dist/src/Skiplist.js +0 -64
  233. package/dist/src/Tracer.d.ts +0 -48
  234. package/dist/src/Tracer.d.ts.map +0 -1
  235. package/dist/src/Tracer.js +0 -6
  236. package/src/Skiplist.ts +0 -108
  237. package/src/Tracer.ts +0 -54
@@ -3,261 +3,696 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import { type Lookup, type LookupOption } from "./Lookup.js";
7
- import { type SemaphoreSnapshot, type Task } from "./Task.js";
8
- import { type Duration } from "./Time.js";
9
- import { NonNegativeInt } from "./Type.js";
10
- export { createRefCount, createRefCountByKey, type CreateRefCountByKeyOptions, type RefCount, type RefCountByKey, } from "./RefCount.js";
6
+ import type { NonEmptyReadonlyArray } from "./Array.ts";
7
+ import { type Lookup, type LookupOption } from "./Lookup.ts";
8
+ import { type SemaphoreSnapshot, type Task } from "./Task.ts";
9
+ import { type PositiveDuration } from "./Time.ts";
10
+ import { NonNegativeInt } from "./Type.ts";
11
+ import { type DistributiveOmit } from "./Types.ts";
11
12
  /**
12
13
  * Disposable resource.
13
14
  *
14
15
  * A resource is any object that implements {@link Disposable} or
15
16
  * {@link AsyncDisposable}.
16
17
  *
18
+ * Successfully returning an owned Resource transfers ownership of a live
19
+ * resource to the caller. Return a typed Result error for a recoverable
20
+ * creation failure; use `undefined` only when absence is a valid success. If
21
+ * creation fails, aborts, or defects, use {@link AsyncDisposableStack} to
22
+ * dispose partially created resources and return no Resource. Never represent
23
+ * failed creation with an already-disposed Resource.
24
+ *
17
25
  * Disposal must succeed. A disposer that throws indicates an unrecoverable
18
26
  * invariant violation, not a recoverable domain error, so resource lifecycle
19
- * APIs let that error propagate. Disposal failures shall surface at the app
20
- * boundary, where they are shown to the user and logged. The purpose of
21
- * resource helpers is to guarantee cleanup and prevent leaks.
27
+ * APIs let that error propagate as a defect. The purpose of resource helpers is
28
+ * to guarantee cleanup and prevent leaks.
22
29
  *
23
- * @see {@link ResourceRef}
24
- * @see {@link createResourceRef}
25
30
  * @see {@link SharedResource}
26
31
  * @see {@link createSharedResource}
27
- * @see {@link SharedResourceByKey}
28
- * @see {@link createSharedResourceByKey}
29
32
  */
30
33
  export type Resource = Disposable | AsyncDisposable;
31
34
  /**
32
35
  * Borrowed {@link Resource}.
33
36
  *
34
- * A borrowed resource is a {@link Resource} without disposal methods.
37
+ * A borrowed resource removes disposal methods from a {@link Resource}'s type.
38
+ * The runtime object is unchanged.
35
39
  *
36
40
  * Another abstraction owns the resource and controls its lifecycle. Exposing
37
- * disposal would break that ownership and allow callers to dispose a resource
38
- * they do not own.
41
+ * disposal in the type would break that ownership and allow callers to dispose
42
+ * a resource they do not own.
39
43
  */
40
- export type BorrowedResource<T extends Resource> = Omit<T, typeof Symbol.dispose | typeof Symbol.asyncDispose>;
44
+ export type BorrowedResource<T extends Resource> = DistributiveOmit<T, typeof Symbol.dispose | typeof Symbol.asyncDispose>;
41
45
  /**
42
- * {@link Resource} reference.
46
+ * An owned lease on a shared {@link Resource}.
43
47
  *
44
- * A {@link MutexRef}-like reference for resources. `ResourceRef` controls the
45
- * resource lifecycle.
48
+ * A lease keeps the underlying resource alive. Release it with
49
+ * {@link Lease.release} or `using`; releasing the last lease starts the resource
50
+ * disposal path. Release is idempotent, so double release is a safe no-op.
46
51
  *
47
- * Callers get the current resource as {@link BorrowedResource} to ensure only
48
- * the `ResourceRef` can dispose it.
52
+ * Release is synchronous accounting and never waits for resource disposal.
53
+ * Disposal completion belongs to the owning {@link SharedResource}: it may be
54
+ * delayed by {@link SharedResourceOptions.idleDisposeAfter | idleDisposeAfter}
55
+ * and is awaited by the owner's async disposal.
49
56
  *
50
- * Setting a new resource first disposes the current one and then sets the next.
51
- * Calling abort on the returned Fiber does not roll that change back once it
52
- * has started. The create Task must not fail. If it could fail, the current
53
- * resource would be disposed without the next resource installed.
57
+ * Release does not revoke {@link Lease.resource | resource}; a released lease
58
+ * keeps a plain reference, and using it after the resource is disposed is a
59
+ * programmer error that nothing catches statically. Create resources with the
60
+ * {@link disposable} helper so use-after-dispose throws eagerly instead of
61
+ * operating on disposed state.
54
62
  */
55
- export interface ResourceRef<T extends Resource, D = unknown> extends AsyncDisposable {
56
- /** Returns the current resource. */
57
- readonly get: Task<BorrowedResource<T>, never, D>;
63
+ export interface Lease<T extends Resource> extends Disposable {
64
+ /** The leased resource. */
65
+ readonly resource: BorrowedResource<T>;
66
+ /** Whether this acquisition created the leased resource generation. */
67
+ readonly created: boolean;
58
68
  /**
59
- * Disposes the current resource and then creates and sets the next.
69
+ * Releases this lease.
60
70
  *
61
- * Once started, this operation runs to completion even if the caller aborts
62
- * its Fiber. Always await the result instead of treating abort as rollback.
71
+ * Returns whether this call released a still-held lease. Returns `false` if
72
+ * the lease was already released, drained by disposal of its owning
73
+ * {@link SharedResource}, or drained by disposal of the Run tree that owns
74
+ * it.
63
75
  */
64
- readonly set: (create: Task<T, never, D>) => Task<void, never, D>;
76
+ readonly release: () => boolean;
65
77
  }
66
- /** Creates {@link ResourceRef}. */
67
- export declare const createResourceRef: <T extends Resource, D>(create: Task<T, never, D>) => Task<ResourceRef<T, D>, never, D>;
68
78
  /**
69
79
  * Shared {@link Resource}.
70
80
  *
71
- * Lazily acquires the underlying resource on the first
72
- * {@link SharedResource.acquire | acquire} call, shares it across callers, and
73
- * disposes it when the last caller {@link SharedResource.release | releases}
74
- * it.
81
+ * Lazily creates the underlying resource on the first
82
+ * {@link SharedResource.acquire | acquire} call, shares it across callers via
83
+ * {@link Lease}s, and disposes it when the last lease is released.
84
+ *
85
+ * ### Example
86
+ *
87
+ * ```ts
88
+ * import {
89
+ * all,
90
+ * createRun,
91
+ * createSharedResource,
92
+ * ok,
93
+ * type Task,
94
+ * } from "@evolu/common";
95
+ *
96
+ * interface Connection extends Disposable {
97
+ * readonly send: (message: string) => void;
98
+ * }
99
+ *
100
+ * let createdCount = 0;
101
+ * const createConnection: Task<Connection> = () => {
102
+ * createdCount += 1;
103
+ * return ok({
104
+ * send: (_message) => {},
105
+ * [Symbol.dispose]: () => {},
106
+ * });
107
+ * };
108
+ *
109
+ * await using run = createRun();
110
+ * await using sharedConnection = await run.ok(
111
+ * createSharedResource(createConnection, { idleDisposeAfter: "5s" }),
112
+ * );
113
+ *
114
+ * // Creating the owner is lazy; no connection is open yet.
115
+ * expect(createdCount).toBe(0);
116
+ *
117
+ * // `use` owns and releases a lease around each operation.
118
+ * const send = (message: string): Task<void> =>
119
+ * sharedConnection.use((connection) => () => {
120
+ * connection.send(message);
121
+ * return ok();
122
+ * });
123
+ *
124
+ * {
125
+ * // An explicit lease can span several operations. Concurrent `use` calls
126
+ * // share the connection kept alive by this lease.
127
+ * using batchLease = await run.ok(sharedConnection.acquire);
128
+ * batchLease.resource.send("first");
129
+ * batchLease.resource.send("second");
130
+ * await run.ok(all([send("hello"), send("world")], { concurrency: 2 }));
131
+ * }
132
+ *
133
+ * // Reacquiring during the idle delay reuses the same generation.
134
+ * using reusedLease = await run.ok(sharedConnection.acquire);
135
+ * expect(reusedLease.created).toBe(false);
136
+ * expect(createdCount).toBe(1);
137
+ * ```
138
+ *
139
+ * ## FAQ
140
+ *
141
+ * ### Why release a lease with `using`?
142
+ *
143
+ * `using` guarantees a {@link Lease} is released on every exit path: normal
144
+ * completion, a thrown error, and abort — abort surfaces as an exception in
145
+ * Task code, so stack unwinding runs disposers. Binding a lease to an ordinary
146
+ * `const` instead is a deliberate ownership transfer; the new owner must
147
+ * guarantee release.
75
148
  *
76
- * Calls to {@link SharedResource.release | release} must be balanced with
77
- * successful calls to {@link SharedResource.acquire | acquire}. Releasing more
78
- * times than acquired is a programmer error checked with {@link assert}.
149
+ * ### What happens when a lease leaks?
150
+ *
151
+ * Nothing in JavaScript enforces `using` or release; a lease that is never
152
+ * released compiles silently (see [MDN resource management
153
+ * pitfalls](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Resource_management#pitfalls);
154
+ * a lint rule may eventually close this gap:
155
+ * https://github.com/typescript-eslint/typescript-eslint/issues/8255). Encoding
156
+ * resource lifetime in the type system would not be bulletproof either: types
157
+ * can force a lifetime to exist, but a lifetime scoped too widely leaks just as
158
+ * silently. Evolu therefore stays JS-native — `using` in the language — and
159
+ * bounds the damage structurally. A leaked lease keeps the resource alive but
160
+ * never past its owner: disposing a SharedResource drains all outstanding
161
+ * leases and disposes the resource. A leaked SharedResource is bounded by its
162
+ * Run, whose disposal also drains its leases and disposes its current resource.
163
+ * Leaks are also observable — {@link SharedResource.snapshot | snapshot} exposes
164
+ * a lease count that never returns to zero — and detected: in development
165
+ * builds, a lease that is garbage-collected without release logs a warning with
166
+ * its acquire stack via the LeakDetector dependency.
79
167
  */
80
- export interface SharedResource<T extends Resource, D = unknown> extends AsyncDisposable {
168
+ export interface SharedResource<T extends Resource> extends AsyncDisposable {
81
169
  /**
82
- * Acquires a shared reference.
170
+ * Acquires a {@link Lease} on the shared resource.
83
171
  *
84
172
  * The first call lazily creates the resource. Later calls reuse the same
85
- * resource until the final {@link SharedResource.release | release} starts the
86
- * final disposal path. Disposal happens immediately by default, or after
87
- * {@link SharedResourceOptions.idleDisposeAfter | idleDisposeAfter} elapses
88
- * when configured.
173
+ * resource until the last lease is released.
174
+ *
175
+ * Once started, acquire runs to completion even when the caller aborts its
176
+ * Fiber, and the returned lease still counts as held. If resource creation or
177
+ * disposal is in progress, acquire waits for it to complete. Always await the
178
+ * result and release the lease; a caller that stops waiting (for example with
179
+ * the `daemon` helper) abandons a lease that is never released, retaining the
180
+ * resource until the owning {@link SharedResource} is disposed.
89
181
  *
90
- * Once started, acquire runs to completion even if the caller aborts its
91
- * Fiber. Always await the result. A successful result still counts as an
92
- * acquired lease and must later be balanced with
93
- * {@link SharedResource.release | release}.
182
+ * Owner disposal is different from caller abort: if disposal starts before a
183
+ * lease is transferred, acquire aborts with `runDisposedAbortReason` from the
184
+ * SharedResource's internal Run. A resource returned by `create` after
185
+ * shutdown starts remains owned by this SharedResource and is disposed
186
+ * without a lease escaping. Transfer occurs when the internal acquisition
187
+ * completes, before the caller necessarily resumes. If owner disposal starts
188
+ * in that gap, the caller can receive a lease already drained by disposal.
189
+ * Its `release` returns `false`, and its resource must not be used.
94
190
  */
95
- readonly acquire: Task<BorrowedResource<T>, never, D>;
191
+ readonly acquire: Task<Lease<T>>;
96
192
  /**
97
- * Releases one previously acquired shared reference.
193
+ * Acquires a {@link Lease} on the current resource without creating one.
98
194
  *
99
- * When the last acquired reference is released, the current resource is
100
- * disposed immediately by default. If
101
- * {@link SharedResourceOptions.idleDisposeAfter | idleDisposeAfter} is set,
102
- * disposal is scheduled instead and a new acquire during that delay reuses
103
- * the current resource.
195
+ * Waits for preceding creation or disposal to finish. Returns `undefined` if
196
+ * no current resource remains. Owner disposal while waiting aborts the
197
+ * acquisition before a lease is transferred. As with `acquire`, owner
198
+ * disposal after transfer can drain the lease before the caller resumes.
199
+ */
200
+ readonly acquireCurrent: Task<Lease<T> | undefined>;
201
+ /**
202
+ * Acquires a {@link Lease}, runs a Task with the shared resource, and releases
203
+ * the lease after the Task settles.
104
204
  *
105
- * Once started, release runs to completion even if the caller aborts its
106
- * Fiber. Always await the result instead of assuming no cleanup happened.
205
+ * Creates the resource when absent. The callback receives whether this use
206
+ * created the resource generation. While the owner remains running, the lease
207
+ * prevents ordinary idle disposal. Disposing this SharedResource is forceful:
208
+ * it drains the lease and may dispose the resource before or while the
209
+ * callback Task runs.
107
210
  */
108
- readonly release: Task<void, never, D>;
109
- /** Returns the current acquire count. */
110
- readonly getCount: Task<NonNegativeInt, never, D>;
111
- /** Returns the current resource, or `undefined` if absent. */
112
- readonly get: () => BorrowedResource<T> | undefined;
211
+ readonly use: <R, E, D>(callback: (resource: BorrowedResource<T>, created: boolean) => Task<R, E, D>) => Task<R, E, D>;
113
212
  /** Returns the current shared-resource state for monitoring/debugging. */
114
213
  readonly snapshot: () => SharedResourceSnapshot;
115
214
  }
116
215
  /** Snapshot returned by {@link SharedResource.snapshot}. */
117
216
  export interface SharedResourceSnapshot {
118
217
  /**
119
- * Whether the resource has no current value, no borrowers, and no pending
120
- * idle disposal.
218
+ * Whether the resource has no current value, no leases, no pending idle
219
+ * disposal, and no acquisition in progress.
121
220
  */
122
221
  readonly isIdle: boolean;
123
- /** Current mutex state for monitoring/debugging. */
222
+ /** Current active lease count. */
223
+ readonly leaseCount: NonNegativeInt;
224
+ /** Whether a current resource exists. */
225
+ readonly hasResource: boolean;
226
+ /** Whether delayed idle disposal is scheduled and pending. */
227
+ readonly idleDisposePending: boolean;
228
+ /** Current internal mutex state for monitoring/debugging. */
124
229
  readonly mutex: SemaphoreSnapshot;
125
230
  }
126
231
  /** Options for {@link createSharedResource}. */
127
232
  export interface SharedResourceOptions {
128
233
  /**
129
- * Keeps the resource alive briefly after the last release.
234
+ * Keeps the resource alive briefly after the last lease is released.
130
235
  *
131
236
  * This avoids immediate disposal when the resource is expensive to create and
132
237
  * likely to be acquired again soon. A new acquire during this delay cancels
133
238
  * the pending disposal and reuses the current resource.
134
239
  */
135
- readonly idleDisposeAfter?: Duration | undefined;
136
- /** Called after the current resource is disposed and cleared. */
137
- readonly onDisposed?: () => void;
240
+ readonly idleDisposeAfter?: PositiveDuration | undefined;
241
+ /**
242
+ * Called after each current resource disposal is attempted and the current
243
+ * reference is cleared, including during {@link SharedResource} disposal.
244
+ * Native disposal still calls it if the resource disposer defects. Not called
245
+ * if no resource was created.
246
+ */
247
+ readonly onDisposed?: (() => void) | undefined;
138
248
  }
139
- /** Creates {@link SharedResource}. */
140
- export declare const createSharedResource: <T extends Resource, D>(create: Task<T, never, D>, { idleDisposeAfter, onDisposed }?: SharedResourceOptions) => Task<SharedResource<T, D>, never, D>;
249
+ /**
250
+ * Creates {@link SharedResource}.
251
+ *
252
+ * The `create` Task must not fail. Creation establishes shared resource state
253
+ * and may serve multiple concurrent acquirers, so a recoverable failure does
254
+ * not belong to one lease. Handle recoverable failures inside `create`: retry
255
+ * until creation succeeds (for example with a Schedule), or return a resource
256
+ * whose state models connection and reconnection failures.
257
+ *
258
+ * A successfully returned resource must be live and independently owned. The
259
+ * `create` Task must not bind its disposal to that Task's {@link DisposableRun}
260
+ * with `defer`.
261
+ *
262
+ * Create resources with {@link disposable} when their shape supports it. Its
263
+ * disposal guard makes calls through a lease fail eagerly after resource
264
+ * disposal instead of operating on disposed state.
265
+ *
266
+ * The `create` Task runs while acquisition is locked and must not directly or
267
+ * transitively acquire from the same SharedResource. The lock is non-reentrant,
268
+ * so doing so deadlocks.
269
+ *
270
+ * Internal work runs on a Run created from the Run that executes this Task, so
271
+ * dependencies are captured at creation time. Deps provided to later acquire
272
+ * calls do not change what `create` observes.
273
+ *
274
+ * Lifecycle callbacks must not throw. A throwing
275
+ * {@link SharedResourceOptions.onDisposed | onDisposed} is a defect that panics
276
+ * the Run tree.
277
+ */
278
+ export declare const createSharedResource: <T extends Resource, D>(create: Task<T, never, D>, { idleDisposeAfter, onDisposed }?: SharedResourceOptions) => Task<SharedResource<T>, never, D>;
141
279
  /**
142
280
  * Shared {@link Resource}s keyed by logical identity.
143
281
  *
144
282
  * A map-like registry of {@link SharedResource}s. Each key owns at most one
145
- * current resource instance.
146
- *
147
- * The first {@link SharedResourceByKey.acquire | acquire} for a key lazily
148
- * creates that key's resource. Later acquires for the same key reuse the same
149
- * resource until the final {@link SharedResourceByKey.release | release} starts
150
- * the final disposal path for that key. Disposal and registry removal happen
151
- * immediately by default, or after
152
- * {@link SharedResourceByKeyOptions.idleDisposeAfter | idleDisposeAfter} elapses
153
- * when configured.
283
+ * current resource instance. The first
284
+ * {@link SharedResourceByKey.acquire | acquire} for a key lazily creates that
285
+ * key's resource; releasing the key's last {@link Lease} disposes it and removes
286
+ * the key from the registry.
154
287
  *
155
288
  * Different keys are independent and may progress concurrently. Operations for
156
- * the same key are serialized. Calls to
157
- * {@link SharedResourceByKey.release | release} must be balanced with successful
158
- * calls to {@link SharedResourceByKey.acquire | acquire}. Acquire and release
159
- * may still be aborted before they start on an already-stopped Run, but once
160
- * started they run to completion. Releasing more times than acquired is a
161
- * programmer error checked with {@link assert}.
162
- *
163
- * By default, {@link createSharedResourceByKey} uses reference identity for
164
- * keys, matching native `Map`. Callers may instead provide a
165
- * {@link Lookup
166
- * lookup} so logical equality is based on a derived stable key.
289
+ * the same key are serialized.
290
+ *
291
+ * By default, keys use reference identity, matching native `Map`. Callers may
292
+ * instead provide a {@link Lookup lookup} so logical equality is based on a
293
+ * derived stable key.
294
+ *
295
+ * ### Example
296
+ *
297
+ * ```ts
298
+ * import {
299
+ * all,
300
+ * createRun,
301
+ * createSharedResourceByKey,
302
+ * ok,
303
+ * type Task,
304
+ * } from "@evolu/common";
305
+ *
306
+ * interface Connection extends Disposable {
307
+ * readonly ownerId: string;
308
+ * readonly send: (message: string) => void;
309
+ * readonly flush: () => ReadonlyArray<string>;
310
+ * }
311
+ *
312
+ * const createConnection =
313
+ * (ownerId: string): Task<Connection> =>
314
+ * () => {
315
+ * const pendingMessages: Array<string> = [];
316
+ * return ok({
317
+ * ownerId,
318
+ * send: (message) => {
319
+ * pendingMessages.push(message);
320
+ * },
321
+ * flush: () => pendingMessages.splice(0),
322
+ * [Symbol.dispose]: () => {},
323
+ * });
324
+ * };
325
+ *
326
+ * await using run = createRun();
327
+ * await using connections = await run.ok(
328
+ * createSharedResourceByKey(createConnection, {
329
+ * idleDisposeAfter: "30s",
330
+ * }),
331
+ * );
332
+ * const send = (ownerId: string, message: string): Task<void> =>
333
+ * connections.use(ownerId, (connection) => () => {
334
+ * connection.send(message);
335
+ * return ok();
336
+ * });
337
+ *
338
+ * // Same-key calls share one connection. Work for different keys remains
339
+ * // independent, so all three operations can run concurrently.
340
+ * await run.ok(
341
+ * all(
342
+ * [
343
+ * send("owner-1", "first"),
344
+ * send("owner-1", "second"),
345
+ * send("owner-2", "hello"),
346
+ * ],
347
+ * { concurrency: 3 },
348
+ * ),
349
+ * );
350
+ *
351
+ * // `acquireCurrent` does not create absent keys. The idle delay keeps the two
352
+ * // existing connections available to enumerate and flush.
353
+ * using missingLease = await run.ok(connections.acquireCurrent("owner-3"));
354
+ * const messagesByOwnerId = new Map<string, ReadonlyArray<string>>();
355
+ * await run.ok(
356
+ * connections.forEachCurrent((connection) => {
357
+ * messagesByOwnerId.set(connection.ownerId, connection.flush());
358
+ * }),
359
+ * );
360
+ *
361
+ * expect(missingLease).toBeUndefined();
362
+ * expect(messagesByOwnerId).toEqual(
363
+ * new Map([
364
+ * ["owner-1", ["first", "second"]],
365
+ * ["owner-2", ["hello"]],
366
+ * ]),
367
+ * );
368
+ * ```
167
369
  */
168
- export interface SharedResourceByKey<K, T extends Resource, D = unknown> extends AsyncDisposable {
370
+ export interface SharedResourceByKey<K, T extends Resource> extends AsyncDisposable {
169
371
  /**
170
- * Acquires the shared resource for `key`, creating it on first use.
372
+ * Acquires a {@link Lease} on the shared resource for `key`, creating the
373
+ * resource on first use.
171
374
  *
172
- * Once started, acquire runs to completion even if the caller aborts its
173
- * Fiber. Always await the result. A successful result still counts as an
174
- * acquired lease for `key` and must later be balanced with
175
- * {@link SharedResourceByKey.release | release}.
375
+ * The same contract as {@link SharedResource.acquire}: once started, acquire
376
+ * runs to completion even when the caller aborts its Fiber, and the returned
377
+ * lease still counts as held. Registry disposal before lease transfer aborts
378
+ * the acquisition. Always await the result and release the lease.
176
379
  */
177
- readonly acquire: (key: K) => Task<BorrowedResource<T>, never, D>;
380
+ readonly acquire: (key: K) => Task<Lease<T>>;
178
381
  /**
179
- * Releases one previously acquired shared reference for `key`.
382
+ * Acquires a {@link Lease} on the current resource for `key` without creating
383
+ * one.
180
384
  *
181
- * When the last acquired reference for `key` is released, that key's current
182
- * resource is disposed and removed from the registry immediately by default.
183
- * If {@link SharedResourceByKeyOptions.idleDisposeAfter | idleDisposeAfter} is
184
- * set, disposal and registry removal are scheduled instead and a new acquire
185
- * for the same key during that delay reuses the current resource.
385
+ * Waits for preceding creation or disposal for the same key to finish.
386
+ * Returns `undefined` if no current resource remains. Registry disposal while
387
+ * waiting aborts the acquisition before a lease is transferred. Registry
388
+ * disposal after transfer can drain the lease before the caller resumes,
389
+ * matching {@link SharedResource.acquireCurrent}.
390
+ */
391
+ readonly acquireCurrent: (key: K) => Task<Lease<T> | undefined>;
392
+ /**
393
+ * Acquires a {@link Lease}, runs a Task with the shared resource for `key`,
394
+ * and releases the lease after the Task settles.
186
395
  *
187
- * Once started, release runs to completion even if the caller aborts its
188
- * Fiber. Always await the result instead of assuming no cleanup happened.
396
+ * Creates the resource when absent. The callback receives whether this use
397
+ * created the resource generation. While the registry remains running, the
398
+ * lease prevents ordinary idle disposal. Disposing this registry is forceful:
399
+ * it drains the lease and may dispose the resource before or while the
400
+ * callback Task runs.
189
401
  */
190
- readonly release: (key: K) => Task<void, never, D>;
191
- /** Returns the current acquire count for `key`. Missing keys return `0`. */
192
- readonly getCount: (key: K) => Task<NonNegativeInt, never, D>;
193
- /** Returns the current resource for `key`, or `undefined` if absent. */
194
- readonly get: (key: K) => BorrowedResource<T> | undefined;
195
- /** Returns current keyed resources and their per-key mutex state. */
196
- readonly snapshot: () => SharedResourceByKeySnapshot<K, T>;
402
+ readonly use: <R, E, D>(key: K, callback: (resource: BorrowedResource<T>, created: boolean) => Task<R, E, D>) => Task<R, E, D>;
403
+ /**
404
+ * Calls `callback` for each current resource it can lease from keys
405
+ * registered when this Task starts.
406
+ *
407
+ * Never creates resources. A resource whose creation is in progress may be
408
+ * included after creation finishes; one disposed before its temporary lease
409
+ * is acquired is skipped. Each acquired lease stays held while later keys are
410
+ * awaited, and every callback runs while all acquired resources remain
411
+ * leased. Caller abort is observed between keys; already-collected leases are
412
+ * released and no callbacks run.
413
+ */
414
+ readonly forEachCurrent: (callback: (resource: BorrowedResource<T>, key: K) => void) => Task<void>;
415
+ /** Returns current per-key shared-resource states for monitoring/debugging. */
416
+ readonly snapshot: () => SharedResourceByKeySnapshot<K>;
197
417
  }
198
418
  /** Snapshot returned by {@link SharedResourceByKey.snapshot}. */
199
- export interface SharedResourceByKeySnapshot<K, T extends Resource> {
200
- /** Current borrowed resources by key. */
201
- readonly resourcesByKey: ReadonlyMap<K, BorrowedResource<T>>;
202
- /** Current mutex state for each key in the resource snapshot. */
203
- readonly mutexByKey: ReadonlyMap<K, SemaphoreSnapshot | null>;
419
+ export interface SharedResourceByKeySnapshot<K> {
420
+ /**
421
+ * Current registered {@link SharedResourceSnapshot} for each key.
422
+ *
423
+ * A key is absent during the brief first-acquire interval before its
424
+ * SharedResource is registered, even though that acquire may already hold the
425
+ * key mutex.
426
+ */
427
+ readonly resourcesByKey: ReadonlyMap<K, SharedResourceSnapshot>;
204
428
  }
205
429
  /** Options for {@link createSharedResourceByKey}. */
206
430
  export interface SharedResourceByKeyOptions<K, L = K> extends Pick<SharedResourceOptions, "idleDisposeAfter">, LookupOption<K, L> {
207
- /** Called after `key`'s current resource is disposed and cleared. */
208
- readonly onDisposed?: (key: K) => void;
431
+ /**
432
+ * Called with `key` after each current resource for that key is disposed,
433
+ * including during {@link SharedResourceByKey} disposal. Not called for keys
434
+ * whose resources were never created.
435
+ *
436
+ * After ordinary idle disposal, the key is removed before this callback
437
+ * unless a same-key operation is already in progress. A regular acquire can
438
+ * then create the next current resource. An acquireCurrent that finds no
439
+ * resource removes the retained key before returning.
440
+ */
441
+ readonly onDisposed?: ((key: K) => void) | undefined;
209
442
  }
210
443
  /**
211
444
  * Creates {@link SharedResourceByKey}.
212
445
  *
213
- * The `create` Task is scoped to one key. It must not fail, matching
214
- * {@link createSharedResource}.
446
+ * The `create` callback returns a Task scoped to one key. The returned Task
447
+ * must not fail, matching {@link createSharedResource}.
448
+ *
449
+ * Create each resource with {@link disposable} when its shape supports it. The
450
+ * same disposal-guard recommendation as {@link createSharedResource} applies.
451
+ *
452
+ * The returned Task must not directly or transitively acquire the same logical
453
+ * key or call `forEachCurrent` on this registry. Same-key acquisition is
454
+ * non-reentrant, and `forEachCurrent` attempts to acquire every registered key.
455
+ * Different keys remain independent.
456
+ *
457
+ * Like {@link createSharedResource}, dependencies are captured when the registry
458
+ * is created; dependencies provided to later acquire calls do not change what
459
+ * the returned Task observes.
215
460
  */
216
- export declare function createSharedResourceByKey<K = unknown, T extends Resource = Resource, D = unknown>(create: (key: K) => Task<T, never, D>): Task<SharedResourceByKey<K, T, D>, never, D>;
217
- export declare function createSharedResourceByKey<K, T extends Resource, D, L = K>(create: (key: K) => Task<T, never, D>, options: SharedResourceByKeyOptions<K, L>): Task<SharedResourceByKey<K, T, D>, never, D>;
461
+ export declare function createSharedResourceByKey<K = unknown, T extends Resource = Resource, D = unknown>(create: (key: K) => Task<T, never, D>): Task<SharedResourceByKey<K, T>, never, D>;
462
+ export declare function createSharedResourceByKey<K, T extends Resource, D, L = K>(create: (key: K) => Task<T, never, D>, options: SharedResourceByKeyOptions<K, L>): Task<SharedResourceByKey<K, T>, never, D>;
218
463
  /**
219
- * Shared {@link Resource}s keyed by logical identity and retained by claims.
464
+ * Shared keyed {@link Resource}s retained through claims.
220
465
  *
221
- * This combines {@link SharedResourceByKey} with claim tracking. Resources are
222
- * kept alive while at least one claim retains their key.
466
+ * A claim identifies an application-level owner such as an account, tenant, or
467
+ * open document. Resource keys identify the shared resources that owner needs,
468
+ * such as relay URLs, database names, or worker IDs.
223
469
  *
224
- * By default, {@link createSharedResourceByKeyWithClaims} uses reference
225
- * identity for both resource keys and claims, matching native `Map` and `Set`.
226
- * Callers may instead provide {@link Lookup lookup} functions so logical
227
- * equality is based on derived stable keys.
470
+ * Use this abstraction when each owner needs a set of keyed resources and those
471
+ * sets can overlap. Calling {@link SharedResourceByKeyWithClaims.claim |
472
+ * claim}
473
+ * retains every resource in one owner's set. Overlapping owners share one
474
+ * resource instance per key, and the resource remains alive until every
475
+ * {@link ClaimLease} retaining that key is released.
476
+ *
477
+ * Relation queries reflect active claims, not physical resource liveness. With
478
+ * `idleDisposeAfter`, a resource can outlive its last claim.
479
+ *
480
+ * Use {@link SharedResourceByKey} instead when callers only need independent
481
+ * leases by key and the application does not need to associate those leases
482
+ * with logical owners.
483
+ *
484
+ * Two accounts can share a relay connection while one also uses a local-network
485
+ * transport:
486
+ *
487
+ * ```ts
488
+ * import {
489
+ * createRun,
490
+ * createSharedResourceByKeyWithClaims,
491
+ * ok,
492
+ * type Brand,
493
+ * type Task,
494
+ * } from "@evolu/common";
495
+ *
496
+ * type AccountId = string & Brand<"AccountId">;
497
+ * type TransportUrl = string & Brand<"TransportUrl">;
498
+ *
499
+ * interface Connection extends Disposable {
500
+ * readonly url: TransportUrl;
501
+ * }
502
+ *
503
+ * const createConnection =
504
+ * (url: TransportUrl): Task<Connection> =>
505
+ * () =>
506
+ * ok({
507
+ * url,
508
+ * [Symbol.dispose]: () => {},
509
+ * });
510
+ * // These literals stand in for values validated at application boundaries.
511
+ * const accountA = "account-a" as AccountId;
512
+ * const accountB = "account-b" as AccountId;
513
+ * const relay = "wss://relay.example.com" as TransportUrl;
514
+ * const localNetwork = "ws://local-network" as TransportUrl;
515
+ *
516
+ * await using run = createRun();
517
+ * await using transports = await run.ok(
518
+ * createSharedResourceByKeyWithClaims<
519
+ * TransportUrl,
520
+ * AccountId,
521
+ * Connection
522
+ * >(createConnection),
523
+ * );
524
+ *
525
+ * {
526
+ * // Account A retains both transports.
527
+ * using accountATransports = await run.ok(
528
+ * transports.claim(accountA, [relay, localNetwork]),
529
+ * );
530
+ *
531
+ * {
532
+ * // Account B reuses the relay already retained by account A.
533
+ * using accountBRelay = await run.ok(
534
+ * transports.claim(accountB, [relay]),
535
+ * );
536
+ * expect(transports.getClaimsForResource(relay)).toEqual(
537
+ * new Set([accountA, accountB]),
538
+ * );
539
+ * expect(transports.getResourceKeysForClaim(accountA)).toEqual(
540
+ * new Set([relay, localNetwork]),
541
+ * );
542
+ * }
543
+ *
544
+ * // Releasing account B leaves account A's relay retain intact.
545
+ * expect(transports.getClaimsForResource(relay)).toEqual(
546
+ * new Set([accountA]),
547
+ * );
548
+ * }
549
+ *
550
+ * // Releasing the final claim removes the relation for both transports.
551
+ * expect(transports.getClaimsForResource(relay)).toEqual(new Set());
552
+ * expect(transports.getClaimsForResource(localNetwork)).toEqual(new Set());
553
+ * ```
228
554
  */
229
- export interface SharedResourceByKeyWithClaims<K, C, T extends Resource, D = unknown> extends AsyncDisposable {
230
- /** Retains each resource key for `claim`. */
231
- readonly addClaim: (claim: C, resourceKeys: ReadonlyArray<K>) => Task<void, never, D>;
232
- /** Releases each previously retained resource key for `claim`. */
233
- readonly removeClaim: (claim: C, resourceKeys: ReadonlyArray<K>) => Task<void, never, D>;
234
- /** Returns the current resource for `key`, or `undefined` if absent. */
235
- readonly getResource: (key: K) => BorrowedResource<T> | undefined;
236
- /** Returns the current unique claims retaining `key`. */
237
- readonly getClaimsForResource: (key: K) => ReadonlySet<C>;
238
- /** Returns the current unique resource keys retained by `claim`. */
555
+ export interface SharedResourceByKeyWithClaims<K, C, T extends Resource> extends AsyncDisposable {
556
+ /**
557
+ * Retains every resource key for `claim`, creating absent resources lazily.
558
+ *
559
+ * Keys are snapshotted when this Task starts and acquired sequentially in
560
+ * input order.
561
+ *
562
+ * `resourceKeys` must not contain logical duplicates according to
563
+ * {@link SharedResourceByKeyWithClaimsOptions.resourceLookup}. A duplicate is
564
+ * a programmer defect that panics the owning Run.
565
+ *
566
+ * The returned {@link ClaimLease} releases all retains added by this call.
567
+ * Resource creation must succeed, so this Task has no recoverable error.
568
+ */
569
+ readonly claim: (claim: C, resourceKeys: NonEmptyReadonlyArray<K>) => Task<ClaimLease>;
570
+ /**
571
+ * Retains every resource key for `claim`, runs a Task with those resources,
572
+ * and releases the resulting {@link ClaimLease} after the Task settles.
573
+ *
574
+ * The callback receives only the resources retained by this call, in input
575
+ * order. Other active ClaimLeases for the same logical claim are excluded.
576
+ * While this registry remains running, every borrowed resource remains valid
577
+ * until the callback Task settles. Disposing this registry is forceful: it
578
+ * drains the ClaimLease and may dispose resources before or while the
579
+ * callback Task runs.
580
+ */
581
+ readonly use: <R, E, D>(claim: C, resourceKeys: NonEmptyReadonlyArray<K>, callback: (resources: NonEmptyReadonlyArray<readonly [BorrowedResource<T>, K]>) => Task<R, E, D>) => Task<R, E, D>;
582
+ /**
583
+ * Returns the current unique claims retaining `resourceKey`.
584
+ *
585
+ * Returns an empty snapshot when no claim retains the key, including after
586
+ * this registry is disposed.
587
+ */
588
+ readonly getClaimsForResource: (resourceKey: K) => ReadonlySet<C>;
589
+ /**
590
+ * Returns the current unique resource keys retained by `claim`.
591
+ *
592
+ * Returns an empty snapshot when the claim retains no keys, including after
593
+ * this registry is disposed.
594
+ */
239
595
  readonly getResourceKeysForClaim: (claim: C) => ReadonlySet<K>;
240
- /** Returns the current unique resources retained by `claim`. */
241
- readonly getResourcesForClaim: (claim: C) => ReadonlySet<BorrowedResource<T>>;
596
+ /**
597
+ * Calls `callback` for each current resource retained by `claim`.
598
+ *
599
+ * The keys and resources are snapshotted before the first callback runs.
600
+ * Resources remain valid until this synchronous iteration returns, even if a
601
+ * callback releases the final retaining ClaimLease. Callbacks must not retain
602
+ * or asynchronously use borrowed resources afterward. Does nothing when the
603
+ * claim retains no resources, including after this registry is disposed.
604
+ */
605
+ readonly forEachResourceForClaim: (claim: C, callback: (resource: BorrowedResource<T>, resourceKey: K) => void) => void;
606
+ /**
607
+ * Returns current claim-retain and keyed-resource states.
608
+ *
609
+ * While a claim is acquiring keys, `resourcesByKey` can include inner leases
610
+ * not yet reflected by `claimLeaseCount` or
611
+ * `retainCountsByResourceKeyByClaim`.
612
+ */
613
+ readonly snapshot: () => SharedResourceByKeyWithClaimsSnapshot<K, C>;
614
+ }
615
+ /** Snapshot returned by {@link SharedResourceByKeyWithClaims.snapshot}. */
616
+ export interface SharedResourceByKeyWithClaimsSnapshot<K, C> {
617
+ /** Number of currently held ClaimLeases. */
618
+ readonly claimLeaseCount: NonNegativeInt;
619
+ /** Current retain count for every active logical claim-resource pair. */
620
+ readonly retainCountsByResourceKeyByClaim: ReadonlyMap<C, ReadonlyMap<K, number>>;
621
+ /** Current registered {@link SharedResourceSnapshot} for each key. */
622
+ readonly resourcesByKey: ReadonlyMap<K, SharedResourceSnapshot>;
623
+ }
624
+ /**
625
+ * An owned lease on the resource-retains added by one claim operation.
626
+ *
627
+ * A ClaimLease is a grouped ownership token. For example, a claim for one
628
+ * account can retain its relay, local-network, and Bluetooth transports. The
629
+ * ClaimLease releases that whole group together; it does not expose or transfer
630
+ * ownership of the resources themselves.
631
+ *
632
+ * Releasing the lease removes every retain added by that claim call. Release is
633
+ * idempotent and synchronous; resource disposal remains owned by the
634
+ * {@link SharedResourceByKeyWithClaims} that created the lease.
635
+ */
636
+ export interface ClaimLease extends Disposable {
637
+ /**
638
+ * Releases every resource retain owned by this claim lease.
639
+ *
640
+ * Returns whether this call released a still-held lease. Returns `false` if
641
+ * the lease was already released or drained by disposal of its owning
642
+ * {@link SharedResourceByKeyWithClaims} or the Run tree that owns it.
643
+ */
644
+ readonly release: () => boolean;
242
645
  }
243
646
  /** Options for {@link createSharedResourceByKeyWithClaims}. */
244
647
  export interface SharedResourceByKeyWithClaimsOptions<K, C, T extends Resource, LK = K, LC = C> extends Pick<SharedResourceOptions, "idleDisposeAfter"> {
245
- /** Derives logical identity for resource keys. Defaults to {@link identity}. */
648
+ /** Derives the identity used to compare resource keys. */
246
649
  readonly resourceLookup?: Lookup<K, LK>;
247
- /** Derives logical identity for claims. Defaults to {@link identity}. */
650
+ /** Derives the identity used to compare claims. */
248
651
  readonly claimLookup?: Lookup<C, LC>;
249
- /** Called when a key transitions from zero claims to one claim. */
652
+ /**
653
+ * Called when a claim-resource pair transitions from zero retains to one.
654
+ *
655
+ * This is a pair-retain transition, not a resource-generation transition: a
656
+ * resource can already be retained by other claims or be idling after an
657
+ * earlier release. Arguments use the first stored representatives for their
658
+ * logical claim and key.
659
+ */
250
660
  readonly onFirstClaimAdded?: (claim: C, resource: BorrowedResource<T>, resourceKey: K) => void;
251
- /** Called when a key transitions from one claim to zero claims. */
661
+ /**
662
+ * Called when a claim-resource pair transitions from one retain to zero.
663
+ *
664
+ * Runs before the ClaimLease's inner resource lease is released. Other claims
665
+ * can still retain the same resource. Arguments use the first stored
666
+ * representatives for their logical claim and key. During normal release, the
667
+ * current pair and earlier keys from the same ClaimLease are already absent
668
+ * from relation reads; later keys remain. During compensation for a failed
669
+ * first-claim callback, the failed claim remains fully visible.
670
+ */
252
671
  readonly onLastClaimRemoved?: (claim: C, resource: BorrowedResource<T>, resourceKey: K) => void;
253
672
  }
254
673
  /**
255
674
  * Creates {@link SharedResourceByKeyWithClaims}.
256
675
  *
257
- * Claim-resource pairs are reference-counted by logical identity. The
258
- * underlying resource for a key is acquired on the first active claim and
259
- * released when the last active claim for that key is removed.
676
+ * The `create` callback is called with a resource key when that resource must
677
+ * be created. The returned Task must not fail. Its dependencies are captured
678
+ * when the registry is created; later claim calls do not require or replace
679
+ * them.
680
+ *
681
+ * Transition callbacks represent semantic claim-resource pair changes and are
682
+ * not called when this registry or its Run tree drains claims during disposal.
683
+ * They must not throw. If a first-claim callback defects after earlier
684
+ * callbacks in the same claim completed, matching last-claim callbacks
685
+ * compensate those completed transitions in reverse order before cleanup.
686
+ * Relation reads during compensation still include the failed claim. Callback
687
+ * defects panic the owner Run after claim metadata and resource ownership are
688
+ * cleaned up.
689
+ *
690
+ * Transition callbacks may synchronously read relation state. They must not
691
+ * call `claim`, release a {@link ClaimLease} owned by this registry, or dispose
692
+ * this registry.
693
+ *
694
+ * Transition callbacks do not report actual resource disposal. Disposal can
695
+ * happen later when `idleDisposeAfter` is configured.
260
696
  */
261
- export declare function createSharedResourceByKeyWithClaims<T extends Resource, K = unknown, C = unknown, D = unknown>(create: (key: K) => Task<T, never, D>): Task<SharedResourceByKeyWithClaims<K, C, T, D>, never, D>;
262
- export declare function createSharedResourceByKeyWithClaims<T extends Resource, K, C, D, LK = K, LC = C>(create: (key: K) => Task<T, never, D>, options: SharedResourceByKeyWithClaimsOptions<K, C, T, LK, LC>): Task<SharedResourceByKeyWithClaims<K, C, T, D>, never, D>;
697
+ export declare const createSharedResourceByKeyWithClaims: <K = unknown, C = unknown, T extends Resource = Resource, D = unknown, LK = K, LC = C>(create: (key: K) => Task<T, never, D>, { idleDisposeAfter, resourceLookup, claimLookup, onFirstClaimAdded, onLastClaimRemoved, }?: SharedResourceByKeyWithClaimsOptions<K, C, T, LK, LC>) => Task<SharedResourceByKeyWithClaims<K, C, T>, never, D>;
263
698
  //# sourceMappingURL=Resource.d.ts.map