march-hare 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -45
- package/dist/march-hare.js +5 -5
- package/dist/march-hare.umd.cjs +1 -1
- package/dist/src/library/index.d.ts +3 -2
- package/dist/src/library/resource/index.d.ts +50 -84
- package/dist/src/library/resource/types.d.ts +150 -0
- package/dist/src/library/resource/utils.d.ts +23 -0
- package/dist/src/library/types/index.d.ts +0 -25
- package/dist/src/library/utils/index.d.ts +73 -23
- package/dist/src/library/utils/types.d.ts +101 -0
- package/dist/src/library/utils/utils.d.ts +33 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<img src="/media/logo-v3.png" width="475" />
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
<i>❝We're all <ins>mad</ins> here.❞</i>
|
|
5
|
+
<br />
|
|
6
|
+
<sub><strong>M</strong>odel,</sub>
|
|
7
|
+
<sub><strong>A</strong>ctions,</sub>
|
|
8
|
+
<sub><strong>D</strong>ata</sub>
|
|
9
|
+
|
|
10
|
+
[](https://github.com/Wildhoney/MarchHare/actions/workflows/checks.yml)
|
|
5
11
|
|
|
6
12
|
</div>
|
|
7
13
|
|
|
8
|
-
Strongly typed React framework using generators and efficiently updated views alongside the publish-subscribe pattern.
|
|
14
|
+
> Strongly typed React framework using generators and efficiently updated views alongside the publish-subscribe pattern.
|
|
9
15
|
|
|
10
|
-
**[View Live Demo →](https://wildhoney.github.io/
|
|
16
|
+
> **[View Live Demo →](https://wildhoney.github.io/MarchHare/)**
|
|
11
17
|
|
|
12
18
|
## Contents
|
|
13
19
|
|
|
@@ -70,22 +76,33 @@ export default function Profile(): React.ReactElement {
|
|
|
70
76
|
}
|
|
71
77
|
```
|
|
72
78
|
|
|
73
|
-
When you need to do more than just assign the payload – such as making an API request – expand `useAction` to a full function. It can be synchronous, asynchronous, or even a generator
|
|
79
|
+
When you need to do more than just assign the payload – such as making an API request – expand `useAction` to a full function. It can be synchronous, asynchronous, or even a generator. Remote data goes through `Resource` rather than a bare `fetch` – declare the resource at module scope and consume it via `useResource`:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// resources.ts
|
|
83
|
+
import { Resource } from "march-hare";
|
|
84
|
+
|
|
85
|
+
export const user = Resource(() => ky.get(api.user()).json<User>());
|
|
86
|
+
```
|
|
74
87
|
|
|
75
88
|
```tsx
|
|
89
|
+
const get = {
|
|
90
|
+
user: useResource(resource.user),
|
|
91
|
+
};
|
|
92
|
+
|
|
76
93
|
actions.useAction(Actions.Name, async (context) => {
|
|
77
94
|
context.actions.produce(
|
|
78
|
-
(
|
|
79
|
-
void (
|
|
95
|
+
({ model }) =>
|
|
96
|
+
void (model.name = context.actions.annotate(model.name, Op.Update)),
|
|
80
97
|
);
|
|
81
98
|
|
|
82
|
-
const
|
|
99
|
+
const data = await get.user();
|
|
83
100
|
|
|
84
|
-
context.actions.produce((
|
|
101
|
+
context.actions.produce(({ model }) => void (model.name = data.name));
|
|
85
102
|
});
|
|
86
103
|
```
|
|
87
104
|
|
|
88
|
-
Notice we're using `annotate` which you can read more about in the [Immertation documentation](https://github.com/Wildhoney/Immertation).
|
|
105
|
+
Notice we're using `annotate` which you can read more about in the [Immertation documentation](https://github.com/Wildhoney/Immertation). Once the request is finished we update the model again with the name fetched from the response and re-render the React component. `Resource` caches the most recent successful payload and exposes typed params – the full API is covered [further down](#remote-data).
|
|
89
106
|
|
|
90
107
|
If you need to access external reactive values (like props or `useState` from parent components) that always reflect the latest value even after `await` operations, pass a data callback to `useActions`:
|
|
91
108
|
|
|
@@ -95,9 +112,13 @@ const actions = useActions<Model, typeof Actions, { query: string }>(
|
|
|
95
112
|
() => ({ query: props.query }),
|
|
96
113
|
);
|
|
97
114
|
|
|
115
|
+
const get = {
|
|
116
|
+
search: useResource(resource.search),
|
|
117
|
+
};
|
|
118
|
+
|
|
98
119
|
actions.useAction(Actions.Search, async (context) => {
|
|
99
|
-
await
|
|
100
|
-
// context.data.query is always the latest value
|
|
120
|
+
await get.search({ query: context.data.query });
|
|
121
|
+
// context.data.query is always the latest value, even after await
|
|
101
122
|
console.log(context.data.query);
|
|
102
123
|
});
|
|
103
124
|
```
|
|
@@ -156,38 +177,50 @@ class Actions {
|
|
|
156
177
|
```
|
|
157
178
|
|
|
158
179
|
```tsx
|
|
180
|
+
const get = {
|
|
181
|
+
user: useResource(resource.user),
|
|
182
|
+
};
|
|
183
|
+
|
|
159
184
|
actions.useAction(Actions.Profile, async (context) => {
|
|
160
185
|
context.actions.produce(
|
|
161
|
-
(
|
|
162
|
-
void (
|
|
186
|
+
({ model }) =>
|
|
187
|
+
void (model.name = context.actions.annotate(model.name, Op.Update)),
|
|
163
188
|
);
|
|
164
189
|
|
|
165
|
-
const
|
|
190
|
+
const data = await get.user();
|
|
166
191
|
|
|
167
|
-
context.actions.produce((
|
|
192
|
+
context.actions.produce(({ model }) => void (model.name = data.name));
|
|
168
193
|
|
|
169
|
-
context.actions.dispatch(Actions.Broadcast.Name, name);
|
|
194
|
+
context.actions.dispatch(Actions.Broadcast.Name, data.name);
|
|
170
195
|
});
|
|
171
196
|
```
|
|
172
197
|
|
|
173
198
|
Once we have the broadcast action, if we want to listen for it and perform another operation in our local component we can do that via `useAction`:
|
|
174
199
|
|
|
175
200
|
```tsx
|
|
201
|
+
const get = {
|
|
202
|
+
friends: useResource(resource.friends),
|
|
203
|
+
};
|
|
204
|
+
|
|
176
205
|
actions.useAction(Actions.Broadcast.Name, async (context, name) => {
|
|
177
|
-
const
|
|
206
|
+
const data = await get.friends({ name });
|
|
178
207
|
|
|
179
|
-
context.actions.produce((
|
|
208
|
+
context.actions.produce(({ model }) => void (model.friends = data));
|
|
180
209
|
});
|
|
181
210
|
```
|
|
182
211
|
|
|
183
212
|
Both `read` and `peek` access the latest cached broadcast value without subscribing via `useAction`. The difference is that `read` waits for any pending annotations on the corresponding model field to settle before resolving, whereas `peek` returns the value immediately:
|
|
184
213
|
|
|
185
214
|
```tsx
|
|
215
|
+
const get = {
|
|
216
|
+
friends: useResource(resource.friends),
|
|
217
|
+
};
|
|
218
|
+
|
|
186
219
|
actions.useAction(Actions.FetchFriends, async (context) => {
|
|
187
220
|
const name = await context.actions.resolution(Actions.Broadcast.Name);
|
|
188
221
|
if (!name) return;
|
|
189
|
-
const
|
|
190
|
-
context.actions.produce(({ model }) => void (model.friends =
|
|
222
|
+
const data = await get.friends({ name });
|
|
223
|
+
context.actions.produce(({ model }) => void (model.friends = data));
|
|
191
224
|
});
|
|
192
225
|
```
|
|
193
226
|
|
|
@@ -233,62 +266,90 @@ function Dashboard() {
|
|
|
233
266
|
|
|
234
267
|
Components that mount after a broadcast has already been dispatched automatically receive the cached value via their `useAction` handler. If you also fetch data in `Lifecycle.Mount()`, see the [mount deduplication recipe](./recipes/mount-broadcast-deduplication.md) to avoid duplicate requests.
|
|
235
268
|
|
|
236
|
-
|
|
269
|
+
<a id="remote-data"></a>
|
|
270
|
+
|
|
271
|
+
For remote data, declare a `Resource` at module scope and consume it via `useResource`. Every call fires its own request; the most recent successful response is cached in a module-level `WeakMap` keyed by the fetcher so `.if(...)` and `.else(...)` on the bound handle have something to read from. Convention is to keep all resources in `resources.ts` and import them as a namespace:
|
|
237
272
|
|
|
238
273
|
```ts
|
|
239
274
|
// resources.ts
|
|
240
275
|
import { Resource } from "march-hare";
|
|
241
276
|
|
|
242
|
-
export const user = Resource(
|
|
277
|
+
export const user = Resource(() => ky.get("/api/user").json<User>());
|
|
278
|
+
|
|
279
|
+
export const pay = Resource((signal, body: Body) =>
|
|
280
|
+
ky.post("/api/pay", { json: body, signal }).json<Receipt>(),
|
|
281
|
+
);
|
|
243
282
|
```
|
|
244
283
|
|
|
245
284
|
```tsx
|
|
246
285
|
// actions.ts
|
|
247
|
-
import
|
|
286
|
+
import { useActions, useResource } from "march-hare";
|
|
248
287
|
import * as resource from "./resources";
|
|
249
288
|
|
|
250
289
|
export function useActions() {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
290
|
+
// Bind the resources first so we can seed the initial model from the
|
|
291
|
+
// cache via `.else(fallback)` — if a previous mount populated the
|
|
292
|
+
// slot, the model starts with that value; otherwise the fallback.
|
|
293
|
+
// Bindings are grouped by HTTP verb so call sites advertise read vs.
|
|
294
|
+
// write at a glance.
|
|
295
|
+
const get = {
|
|
296
|
+
user: useResource(resource.user),
|
|
297
|
+
};
|
|
298
|
+
const post = {
|
|
299
|
+
pay: useResource(resource.pay),
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const actions = useActions<Model, typeof Actions>({
|
|
303
|
+
user: get.user.else(null),
|
|
304
|
+
receipt: post.pay.else(null),
|
|
305
|
+
});
|
|
254
306
|
|
|
255
307
|
actions.useAction(Actions.Mount, async (context) => {
|
|
256
|
-
const data = await user.
|
|
257
|
-
|
|
308
|
+
const data = await get.user.if(
|
|
309
|
+
{ over: { minutes: 5 } },
|
|
310
|
+
context.task.controller.signal,
|
|
311
|
+
);
|
|
258
312
|
context.actions.produce(({ model }) => void (model.user = data));
|
|
259
313
|
});
|
|
260
314
|
|
|
315
|
+
actions.useAction(Actions.Submit, async (context, body) => {
|
|
316
|
+
const receipt = await post.pay(context.task.controller.signal, body);
|
|
317
|
+
context.actions.produce(({ model }) => void (model.receipt = receipt));
|
|
318
|
+
});
|
|
319
|
+
|
|
261
320
|
return actions;
|
|
262
321
|
}
|
|
263
322
|
```
|
|
264
323
|
|
|
265
|
-
`
|
|
324
|
+
`useResource(handle)` returns the fetch callable directly. The callable has two attached methods: `.if({ over })` skips the network when the cached payload is still fresh, and `.else(fallback)` reads the cached payload synchronously with a default. `Temporal` is read from the host runtime – bring a polyfill (e.g. [`@js-temporal/polyfill`](https://github.com/js-temporal/temporal-polyfill)) if your target environment does not yet expose it natively. `.if({ over })` accepts a `Temporal.Duration`, a `DurationLike` object, or an ISO 8601 duration string.
|
|
266
325
|
|
|
267
|
-
`Resource` takes
|
|
326
|
+
`Resource` takes a single fetcher argument. The fetcher receives the call-site `params` object as its only argument and returns a `Promise<T>`. There are no callbacks – no `onSuccess`, no `onError`, no injected `dispatch`. Side-effects after a run (broadcasting, analytics, model writes) live in the `useAction` handler that awaited the call, next to the rest of the flow:
|
|
268
327
|
|
|
269
328
|
```ts
|
|
270
|
-
export const user = Resource(
|
|
329
|
+
export const user = Resource(() => ky.get("/api/user").json<User>());
|
|
271
330
|
|
|
272
331
|
actions.useAction(Actions.Mount, async (context) => {
|
|
273
|
-
const data = await user
|
|
332
|
+
const data = await get.user();
|
|
274
333
|
await context.actions.dispatch(Actions.Broadcast.UserUpdated, data);
|
|
275
334
|
context.actions.produce(({ model }) => void (model.user = data));
|
|
276
335
|
});
|
|
277
336
|
```
|
|
278
337
|
|
|
279
|
-
`params` is the second generic on `Resource` and defaults to `{}`. Declare it when the fetcher needs call-time inputs – cursors, ids, query strings. `params` is a single object (not positional args), which keeps call sites self-documenting
|
|
338
|
+
`params` is the second generic on `Resource` and defaults to `{}`. Declare it when the fetcher needs call-time inputs – cursors, ids, query strings, request bodies. `params` is a single object (not positional args), which keeps call sites self-documenting:
|
|
280
339
|
|
|
281
340
|
```ts
|
|
282
341
|
type Params = { cursor: string | null };
|
|
283
342
|
|
|
284
|
-
export const feed = Resource
|
|
343
|
+
export const feed = Resource((signal, { cursor }: Params) =>
|
|
285
344
|
http
|
|
286
|
-
.get("feed", { searchParams: { cursor: cursor ?? "" } })
|
|
345
|
+
.get("feed", { searchParams: { cursor: cursor ?? "" }, signal })
|
|
287
346
|
.json<Page<Item>>(),
|
|
288
347
|
);
|
|
289
348
|
|
|
290
|
-
const
|
|
291
|
-
|
|
349
|
+
const get = {
|
|
350
|
+
feed: useResource(resource.feed),
|
|
351
|
+
};
|
|
352
|
+
const page = await get.feed({ cursor: context.model.cursor });
|
|
292
353
|
```
|
|
293
354
|
|
|
294
355
|
A complete IntersectionObserver-driven infinite-scroll demo lives at [`src/example/transactions/`](./src/example/transactions/) – mock paginated API, scroll-triggered `LoadMore`, `pending()` guard, broadcast on success.
|
|
@@ -298,7 +359,7 @@ For typed failure routing, wrap the call in `try/catch` and use `instanceof` &nd
|
|
|
298
359
|
```ts
|
|
299
360
|
actions.useAction(Actions.Mount, async (context) => {
|
|
300
361
|
try {
|
|
301
|
-
const data = await user
|
|
362
|
+
const data = await get.user();
|
|
302
363
|
context.actions.produce(({ model }) => void (model.user = data));
|
|
303
364
|
} catch (error) {
|
|
304
365
|
if (error instanceof RateLimitedError) {
|
|
@@ -314,6 +375,42 @@ actions.useAction(Actions.Mount, async (context) => {
|
|
|
314
375
|
|
|
315
376
|
See the [Resource recipe](./recipes/use-resource.md) for the three-tier error handling model, parameterised resources, and limitations.
|
|
316
377
|
|
|
378
|
+
### Persisting resources across reloads
|
|
379
|
+
|
|
380
|
+
`useResource`'s cache lives in a `WeakMap` that's reset on every page load. To keep the most recent successful payload around between sessions, pair it with `utils.store(...)` – a synchronous key/value wrapper around any backing store (`localStorage` on web, `MMKV` on React Native, etc.) that traffics in the same `Stored<T>` shape as the Resource cache.
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import { utils } from "march-hare";
|
|
384
|
+
|
|
385
|
+
export const store = utils.store({
|
|
386
|
+
get: (key) => localStorage.getItem(key),
|
|
387
|
+
set: (key, value) => localStorage.setItem(key, value),
|
|
388
|
+
remove: (key) => localStorage.removeItem(key),
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
`get.cat.else(...)` is overloaded: pass a `Stored<T>` (from `store.get(key)`) and the bound handle seeds its own cache from it when empty – then chain `.else(null)` for the leaf fallback. `get.cat.snapshot()` produces the symmetric `Stored<T>` for writing back. After this single chain, `.if({ over })` short-circuits on the persisted timestamp on the _first_ mount after a reload, with no second method to call.
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
const get = { cat: useResource(resource.cat) };
|
|
396
|
+
const actions = useActions<Model, typeof Actions>({
|
|
397
|
+
// First render reads cache → storage → null.
|
|
398
|
+
cat: get.cat.else(store.get(Snapshots.Cat)).else(null),
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
actions.useAction(Actions.Mount, async (context) => {
|
|
402
|
+
// Short-circuits when storage held a payload < 5 minutes old.
|
|
403
|
+
const fresh = await get.cat.if(
|
|
404
|
+
{ over: { minutes: 5 } },
|
|
405
|
+
context.task.controller.signal,
|
|
406
|
+
);
|
|
407
|
+
store.set(Snapshots.Cat, get.cat.snapshot());
|
|
408
|
+
context.actions.produce(({ model }) => void (model.cat = fresh));
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
See the [storage recipe](./recipes/storage.md) for backend adapters (React Native MMKV, browser extension `chrome.storage`), sign-out purge, and the `unset` sentinel that keeps "nothing stored" distinct from "a legitimately stored null".
|
|
413
|
+
|
|
317
414
|
For targeted event delivery, use channeled actions. Define a channel type as the second generic argument and call the action with a channel object – handlers fire when the dispatch channel matches:
|
|
318
415
|
|
|
319
416
|
```tsx
|
|
@@ -387,7 +484,7 @@ See the [multicast recipe](./recipes/multicast-actions.md) for more details.
|
|
|
387
484
|
For coordinating between async handlers without re-rendering the JSX tree, use the per-`<Boundary>` mode handle returned by `useMode()`. Thread it through the `useActions` data callback so it shows up as `context.data.mode` inside handlers, fully typed. Mode is **not** reactive — drive view state through the model, not mode.
|
|
388
485
|
|
|
389
486
|
```ts
|
|
390
|
-
import
|
|
487
|
+
import { useActions, useMode } from "march-hare";
|
|
391
488
|
|
|
392
489
|
enum Mode {
|
|
393
490
|
Idle,
|
|
@@ -395,14 +492,13 @@ enum Mode {
|
|
|
395
492
|
}
|
|
396
493
|
|
|
397
494
|
export function useActions() {
|
|
398
|
-
const mode =
|
|
495
|
+
const mode = useMode<Mode>();
|
|
399
496
|
// Spell the data shape as the third generic so `context.data.mode` keeps
|
|
400
497
|
// its concrete type inside handlers.
|
|
401
|
-
const actions =
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
>(model, () => ({ mode }));
|
|
498
|
+
const actions = useActions<Model, typeof Actions, { mode: typeof mode }>(
|
|
499
|
+
model,
|
|
500
|
+
() => ({ mode }),
|
|
501
|
+
);
|
|
406
502
|
|
|
407
503
|
actions.useAction(Actions.SignOut, async (context) => {
|
|
408
504
|
context.data.mode.update(Mode.SigningOut);
|
package/dist/march-hare.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{G as e,A as t}from"@mobily/ts-belt";import{immerable as n,enablePatches as r,Immer as o}from"immer";import{jsx as s}from"react/jsx-runtime";import*as c from"react";const i=(e="")=>`march-hare.action/${e}`,a=(e="")=>`march-hare.action/broadcast/${e}`,u=(e="")=>`march-hare.action/multicast/${e}`,l=(e="")=>`march-hare.action.lifecycle/${e}`;class f{static Payload=/* @__PURE__ */Symbol("march-hare.brand/Payload");static Broadcast=/* @__PURE__ */Symbol("march-hare.brand/Broadcast");static Multicast=/* @__PURE__ */Symbol("march-hare.brand/Multicast");static Action=/* @__PURE__ */Symbol("march-hare.brand/Action");static Channel=/* @__PURE__ */Symbol("march-hare.brand/Channel")}function d(e){const t=/* @__PURE__ */Symbol(`march-hare.action.lifecycle/${e}`),n=function(e){return{[f.Action]:t,[f.Payload]:void 0,[f.Channel]:e,channel:e}};return Object.defineProperty(n,f.Action,{value:t,enumerable:!1}),Object.defineProperty(n,f.Payload,{value:void 0,enumerable:!1}),n}const p=Symbol(a("Fault"));class h{static Mount(){return d("Mount")}static Unmount(){return d("Unmount")}static Error(){return d("Error")}static Update(){return d("Update")}static Fault=(()=>{const e={};return Object.defineProperty(e,f.Action,{value:p,enumerable:!1}),Object.defineProperty(e,f.Payload,{value:void 0,enumerable:!1}),Object.defineProperty(e,f.Broadcast,{value:!0,enumerable:!1}),e})()}var m=/* @__PURE__ */(e=>(e.Unicast="unicast",e.Broadcast="broadcast",e.Multicast="multicast",e))(m||{}),y=/* @__PURE__ */(e=>(e.Mounting="mounting",e.Mounted="mounted",e.Unmounting="unmounting",e.Unmounted="unmounted",e))(y||{});const b=e=>"symbol"==typeof e;function v(t){return e.isString(t)||b(t)?t:(e.isObject(t)||e.isFunction(t))&&f.Action in t?t[f.Action]:t}function g(t){if(e.isString(t))return t.startsWith(a());if(b(t))return t.description?.startsWith(a())??!1;if(e.isObject(t)||e.isFunction(t)){if(f.Broadcast in t&&t[f.Broadcast])return!0;if(f.Action in t){const e=t[f.Action];return e.description?.startsWith(a())??!1}}return!1}function w(t){const n=v(t),r=e.isString(n)?n:n.description??"";return r.startsWith(i())&&r.slice(r.lastIndexOf("/")+1)||"unknown"}function O(t){return e.isObject(t)&&f.Channel in t&&"channel"in t}function j(e){const t=v(e),n=b(t)?t.description??"":t;return n.startsWith(l())&&n.slice(l().length)||null}function S(t){if(e.isString(t))return t.startsWith(u());if(b(t))return t.description?.startsWith(u())??!1;if(e.isObject(t)||e.isFunction(t)){if(f.Multicast in t&&t[f.Multicast])return!0;if(f.Action in t){const e=t[f.Action];return e.description?.startsWith(u())??!1}}return!1}const E=(e,t=m.Unicast)=>{const n=t===m.Broadcast?Symbol(a(e)):t===m.Multicast?Symbol(u(e)):Symbol(i(e)),r=function(e){return{[f.Action]:n,[f.Payload]:void 0,[f.Channel]:e,channel:e}};return Object.defineProperty(r,f.Action,{value:n,enumerable:!1}),Object.defineProperty(r,f.Payload,{value:void 0,enumerable:!1}),t===m.Broadcast&&Object.defineProperty(r,f.Broadcast,{value:!0,enumerable:!1}),t===m.Multicast&&Object.defineProperty(r,f.Multicast,{value:!0,enumerable:!1}),r};var P=/* @__PURE__ */(e=>(e[e.Timedout=0]="Timedout",e[e.Supplanted=1]="Supplanted",e[e.Errored=2]="Errored",e))(P||{});class M extends Error{name="AbortError";constructor(e="Aborted"){super(e)}}class C extends Error{name="TimeoutError";constructor(e="Timeout"){super(e)}}let x=(e=21)=>{let t="",n=crypto.getRandomValues(new Uint8Array(e|=0));for(;e--;)t+="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"[63&n[e]];return t};var k=/* @__PURE__ */(e=>(e[e.Add=1]="Add",e[e.Remove=2]="Remove",e[e.Update=4]="Update",e[e.Move=8]="Move",e[e.Replace=16]="Replace",e[e.Sort=32]="Sort",e[e.Create=64]="Create",e[e.Fetch=128]="Fetch",e[e.Clone=256]="Clone",e[e.Archive=512]="Archive",e[e.Restore=1024]="Restore",e[e.Merge=2048]="Merge",e[e.Reorder=4096]="Reorder",e[e.Sync=8192]="Sync",e[e.Publish=16384]="Publish",e[e.Link=32768]="Link",e[e.Unlink=65536]="Unlink",e[e.Lock=131072]="Lock",e[e.Unlock=262144]="Unlock",e[e.Import=524288]="Import",e[e.Export=1048576]="Export",e[e.Transfer=2097152]="Transfer",e))(k||{}),A=/* @__PURE__ */(e=>(e[e.Produce=0]="Produce",e[e.Hydrate=1]="Hydrate",e))(A||{}),_=/* @__PURE__ */(e=>(e.Property="property",e.Process="process",e.Value="value",e.Operation="operation",e))(_||{});class R{[n]=!0;static keys=new Set(Object.values(_));property=null;process=null;value;operation;constructor(e,t){this.value=e,this.operation=t}assign(e,t){const n=new R(this.value,this.operation);return n.property=e,n.process=t,n}}class N{static immer=(()=>{r();const e=new o;return e.setAutoFreeze(!1),e})();static tag="κ";static id=x}function U(e,t){const n="string"==typeof t?""===t?[]:t.split("."):t;let r=e;for(const o of n){if(null==r)return;r=r[o]}return r}function L(t){if(e.isNullable(t)||F(t))return t;if(e.isArray(t))return t.map(e=>L(e));if(e.isObject(t)&&B(t)){const e=Object.entries(t).map(([e,t])=>[e,L(t)]);return{...Object.fromEntries(e),[N.tag]:t[N.tag]??N.id()}}return t}function T(e){if(Array.isArray(e))return e.filter(e=>N.tag in e).map(e=>e[N.tag]??"").join(",");const t=e[N.tag];if(t)return t;try{return JSON.stringify(e)}catch{return`[unserializable:${typeof e}]`}}function B(e){const t=Object.getPrototypeOf(e);return t===Object.prototype||null===t}function F(t){return e.isNullable(t)||e.isString(t)||e.isNumber(t)||e.isBoolean(t)||"symbol"==typeof t||"bigint"==typeof t}function W(t,n,r,o,s,c){return function i(a,u=n.path){if(a instanceof R){const n=U(r,u.join("."));if(Object.entries(a).filter(([e,t])=>!R.keys.has(e)&&t instanceof R).forEach(([e,t])=>i(t,u.concat(e))),F(a.value)){if(t===A.Hydrate)return a.value;const i=u.slice(0,-1),l=i.length>0?U(r,i.join(".")):r;return e.isNullable(l)||$(l,a,u.at(-1),o,s,c),n??a.value}if(t===A.Hydrate){const e=L(i(a.value,u));return $(e,a,null,o,s,c),e}const l=n??L(a.value);return $(l,a,null,o,s,c),e.isNullable(n)?l:(i(a.value,u),n)}if(e.isArray(a))return a.map((e,t)=>i(e,u.concat(t)));if(e.isObject(a)&&!B(a))return a;if(e.isObject(a)){const e=Object.entries(a).map(([e,t])=>[e,i(t,u.concat(e))]),n=Object.fromEntries(e);if(t===A.Hydrate){const e=L(n);return Object.entries(a).forEach(([t,n])=>{n instanceof R&&F(n.value)&&$(e,n,t,o,s,c)}),e}return n}return a}(n.value)}function $(e,t,n,r,o,s){const c=s(e),i=o.get(c)??[];o.set(c,[t.assign(n,r),...i])}class z{#e={};#t;#n=/* @__PURE__ */new Map;#r=/* @__PURE__ */new Set;#o=!1;constructor(e=T){this.#t=e}static pk(){return x()}static"κ"=z.pk;annotate(e,t){return new R(t,e)}"δ"=this.annotate;get model(){return this.#e}get inspect(){return function(n,r,o,s,c){function i(s){const c=s.at(-1),i=U(n(),s),a=s.slice(0,-1),u=t.isNotEmpty(a)?U(n(),a):n();return[...e.isObject(i)||e.isArray(i)?r.get(o(i))?.filter(t=>e.isNullable(t.property))??[]:[],...e.isObject(u)?r.get(o(u))?.filter(e=>e.property===c)??[]:[]]}return function e(r){return new Proxy(()=>{},{get:(o,a)=>"pending"===a?()=>!t.isEmpty(i(r)):"remaining"===a?()=>t.length(i(r)):"box"===a?()=>({value:U(n(),r),inspect:e(r)}):"is"===a?e=>i(r).some(t=>0!==(t.operation&e)):"draft"===a?()=>t.head(i(r))?.value??U(n(),r):"settled"===a?()=>new Promise(e=>{if(t.isEmpty(i(r)))return e(U(n(),r));const o=()=>{t.isEmpty(i(r))&&(c(o),e(U(n(),r)))};s(o)}):e([...r,String(a)])})}([])}(()=>this.#e,this.#n,this.#t,e=>this.#r.add(e),e=>this.#r.delete(e))}hydrate(e){return this.#o=!0,this.#s(A.Hydrate,()=>e)}produce(e){if(!this.#o)throw new Error("State must be hydrated using hydrate() before calling produce()");return this.#s(A.Produce,e)}#s(e,t){const n=/* @__PURE__ */Symbol("process"),[,r]=N.immer.produceWithPatches(this.#e,t);return this.#e=r.reduce((t,r)=>N.immer.applyPatches(t,[{...r,value:W(e,r,t,n,this.#n,this.#t)}]),this.#e),this.#e=L(this.#e),this.#c(),n}prune(e){this.#n.forEach((n,r)=>{const o=n.filter(t=>t.process!==e);t.isEmpty(o)?this.#n.delete(r):this.#n.set(r,o)}),this.#c()}#c(){this.#r.forEach(e=>e())}observe(e){const t=()=>e(this.#e);return this.#r.add(t),()=>this.#r.delete(t)}}const H=new z;function I(e,t=k.Update){return H.annotate(t,e)}function D(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var G,J={exports:{}};const V=/* @__PURE__ */D((G||(G=1,function(e){var t=Object.prototype.hasOwnProperty,n="~";function r(){}function o(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function s(e,t,r,s,c){if("function"!=typeof r)throw new TypeError("The listener must be a function");var i=new o(r,s||e,c),a=n?n+t:t;return e._events[a]?e._events[a].fn?e._events[a]=[e._events[a],i]:e._events[a].push(i):(e._events[a]=i,e._eventsCount++),e}function c(e,t){0===--e._eventsCount?e._events=new r:delete e._events[t]}function i(){this._events=new r,this._eventsCount=0}Object.create&&(r.prototype=/* @__PURE__ */Object.create(null),(new r).__proto__||(n=!1)),i.prototype.eventNames=function(){var e,r,o=[];if(0===this._eventsCount)return o;for(r in e=this._events)t.call(e,r)&&o.push(n?r.slice(1):r);return Object.getOwnPropertySymbols?o.concat(Object.getOwnPropertySymbols(e)):o},i.prototype.listeners=function(e){var t=this._events[n?n+e:e];if(!t)return[];if(t.fn)return[t.fn];for(var r=0,o=t.length,s=new Array(o);r<o;r++)s[r]=t[r].fn;return s},i.prototype.listenerCount=function(e){var t=this._events[n?n+e:e];return t?t.fn?1:t.length:0},i.prototype.emit=function(e,t,r,o,s,c){var i=n?n+e:e;if(!this._events[i])return!1;var a,u,l=this._events[i],f=arguments.length;if(l.fn){switch(l.once&&this.removeListener(e,l.fn,void 0,!0),f){case 1:return l.fn.call(l.context),!0;case 2:return l.fn.call(l.context,t),!0;case 3:return l.fn.call(l.context,t,r),!0;case 4:return l.fn.call(l.context,t,r,o),!0;case 5:return l.fn.call(l.context,t,r,o,s),!0;case 6:return l.fn.call(l.context,t,r,o,s,c),!0}for(u=1,a=new Array(f-1);u<f;u++)a[u-1]=arguments[u];l.fn.apply(l.context,a)}else{var d,p=l.length;for(u=0;u<p;u++)switch(l[u].once&&this.removeListener(e,l[u].fn,void 0,!0),f){case 1:l[u].fn.call(l[u].context);break;case 2:l[u].fn.call(l[u].context,t);break;case 3:l[u].fn.call(l[u].context,t,r);break;case 4:l[u].fn.call(l[u].context,t,r,o);break;default:if(!a)for(d=1,a=new Array(f-1);d<f;d++)a[d-1]=arguments[d];l[u].fn.apply(l[u].context,a)}}return!0},i.prototype.on=function(e,t,n){return s(this,e,t,n,!1)},i.prototype.once=function(e,t,n){return s(this,e,t,n,!0)},i.prototype.removeListener=function(e,t,r,o){var s=n?n+e:e;if(!this._events[s])return this;if(!t)return c(this,s),this;var i=this._events[s];if(i.fn)i.fn!==t||o&&!i.once||r&&i.context!==r||c(this,s);else{for(var a=0,u=[],l=i.length;a<l;a++)(i[a].fn!==t||o&&!i[a].once||r&&i[a].context!==r)&&u.push(i[a]);u.length?this._events[s]=1===u.length?u[0]:u:c(this,s)}return this},i.prototype.removeAllListeners=function(e){var t;return e?this._events[t=n?n+e:e]&&c(this,t):(this._events=new r,this._eventsCount=0),this},i.prototype.off=i.prototype.removeListener,i.prototype.addListener=i.prototype.on,i.prefixed=n,i.EventEmitter=i,e.exports=i}(J)),J.exports));class q extends V{cache=/* @__PURE__ */new Map;emit(e,...t){return this.cache.set(e,t[0]),super.emit(e,...t)}setCache(e,t){this.cache.set(e,t)}getCached(e){return this.cache.get(e)}fire(e,...t){return super.emit(e,...t)}}const K=c.createContext(new q);function Q(){return c.useContext(K)}function X({children:e}){const t=c.useMemo(()=>new q,[]);/* @__PURE__ */
|
|
2
|
-
return s(K.Provider,{value:t,children:e})}const Y=
|
|
3
|
-
return s(Y.Provider,{value:t,children:e})}const ee=
|
|
1
|
+
import{G as e,A as t}from"@mobily/ts-belt";import{immerable as n,enablePatches as r,Immer as o}from"immer";import{jsx as s}from"react/jsx-runtime";import*as a from"react";const c=(e="")=>`march-hare.action/${e}`,i=(e="")=>`march-hare.action/broadcast/${e}`,u=(e="")=>`march-hare.action/multicast/${e}`,l=(e="")=>`march-hare.action.lifecycle/${e}`;class f{static Payload=/* @__PURE__ */Symbol("march-hare.brand/Payload");static Broadcast=/* @__PURE__ */Symbol("march-hare.brand/Broadcast");static Multicast=/* @__PURE__ */Symbol("march-hare.brand/Multicast");static Action=/* @__PURE__ */Symbol("march-hare.brand/Action");static Channel=/* @__PURE__ */Symbol("march-hare.brand/Channel")}function d(e){const t=/* @__PURE__ */Symbol(`march-hare.action.lifecycle/${e}`),n=function(e){return{[f.Action]:t,[f.Payload]:void 0,[f.Channel]:e,channel:e}};return Object.defineProperty(n,f.Action,{value:t,enumerable:!1}),Object.defineProperty(n,f.Payload,{value:void 0,enumerable:!1}),n}const p=Symbol(i("Fault"));class h{static Mount(){return d("Mount")}static Unmount(){return d("Unmount")}static Error(){return d("Error")}static Update(){return d("Update")}static Fault=(()=>{const e={};return Object.defineProperty(e,f.Action,{value:p,enumerable:!1}),Object.defineProperty(e,f.Payload,{value:void 0,enumerable:!1}),Object.defineProperty(e,f.Broadcast,{value:!0,enumerable:!1}),e})()}var m=/* @__PURE__ */(e=>(e.Unicast="unicast",e.Broadcast="broadcast",e.Multicast="multicast",e))(m||{}),y=/* @__PURE__ */(e=>(e.Mounting="mounting",e.Mounted="mounted",e.Unmounting="unmounting",e.Unmounted="unmounted",e))(y||{});const b=e=>"symbol"==typeof e;function v(t){return e.isString(t)||b(t)?t:(e.isObject(t)||e.isFunction(t))&&f.Action in t?t[f.Action]:t}function g(t){if(e.isString(t))return t.startsWith(i());if(b(t))return t.description?.startsWith(i())??!1;if(e.isObject(t)||e.isFunction(t)){if(f.Broadcast in t&&t[f.Broadcast])return!0;if(f.Action in t){const e=t[f.Action];return e.description?.startsWith(i())??!1}}return!1}function w(t){const n=v(t),r=e.isString(n)?n:n.description??"";return r.startsWith(c())&&r.slice(r.lastIndexOf("/")+1)||"unknown"}function O(t){return e.isObject(t)&&f.Channel in t&&"channel"in t}function S(e){const t=v(e),n=b(t)?t.description??"":t;return n.startsWith(l())&&n.slice(l().length)||null}function j(t){if(e.isString(t))return t.startsWith(u());if(b(t))return t.description?.startsWith(u())??!1;if(e.isObject(t)||e.isFunction(t)){if(f.Multicast in t&&t[f.Multicast])return!0;if(f.Action in t){const e=t[f.Action];return e.description?.startsWith(u())??!1}}return!1}const P=(e,t=m.Unicast)=>{const n=t===m.Broadcast?Symbol(i(e)):t===m.Multicast?Symbol(u(e)):Symbol(c(e)),r=function(e){return{[f.Action]:n,[f.Payload]:void 0,[f.Channel]:e,channel:e}};return Object.defineProperty(r,f.Action,{value:n,enumerable:!1}),Object.defineProperty(r,f.Payload,{value:void 0,enumerable:!1}),t===m.Broadcast&&Object.defineProperty(r,f.Broadcast,{value:!0,enumerable:!1}),t===m.Multicast&&Object.defineProperty(r,f.Multicast,{value:!0,enumerable:!1}),r};var E=/* @__PURE__ */(e=>(e[e.Timedout=0]="Timedout",e[e.Supplanted=1]="Supplanted",e[e.Errored=2]="Errored",e))(E||{});class M extends Error{name="AbortError";constructor(e="Aborted"){super(e)}}class C extends Error{name="TimeoutError";constructor(e="Timeout"){super(e)}}let x=(e=21)=>{let t="",n=crypto.getRandomValues(new Uint8Array(e|=0));for(;e--;)t+="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"[63&n[e]];return t};var k=/* @__PURE__ */(e=>(e[e.Add=1]="Add",e[e.Remove=2]="Remove",e[e.Update=4]="Update",e[e.Move=8]="Move",e[e.Replace=16]="Replace",e[e.Sort=32]="Sort",e[e.Create=64]="Create",e[e.Fetch=128]="Fetch",e[e.Clone=256]="Clone",e[e.Archive=512]="Archive",e[e.Restore=1024]="Restore",e[e.Merge=2048]="Merge",e[e.Reorder=4096]="Reorder",e[e.Sync=8192]="Sync",e[e.Publish=16384]="Publish",e[e.Link=32768]="Link",e[e.Unlink=65536]="Unlink",e[e.Lock=131072]="Lock",e[e.Unlock=262144]="Unlock",e[e.Import=524288]="Import",e[e.Export=1048576]="Export",e[e.Transfer=2097152]="Transfer",e))(k||{}),A=/* @__PURE__ */(e=>(e[e.Produce=0]="Produce",e[e.Hydrate=1]="Hydrate",e))(A||{}),_=/* @__PURE__ */(e=>(e.Property="property",e.Process="process",e.Value="value",e.Operation="operation",e))(_||{});class N{[n]=!0;static keys=new Set(Object.values(_));property=null;process=null;value;operation;constructor(e,t){this.value=e,this.operation=t}assign(e,t){const n=new N(this.value,this.operation);return n.property=e,n.process=t,n}}class R{static immer=(()=>{r();const e=new o;return e.setAutoFreeze(!1),e})();static tag="κ";static id=x}function U(e,t){const n="string"==typeof t?""===t?[]:t.split("."):t;let r=e;for(const o of n){if(null==r)return;r=r[o]}return r}function L(t){if(e.isNullable(t)||F(t))return t;if(e.isArray(t))return t.map(e=>L(e));if(e.isObject(t)&&B(t)){const e=Object.entries(t).map(([e,t])=>[e,L(t)]);return{...Object.fromEntries(e),[R.tag]:t[R.tag]??R.id()}}return t}function T(e){if(Array.isArray(e))return e.filter(e=>R.tag in e).map(e=>e[R.tag]??"").join(",");const t=e[R.tag];if(t)return t;try{return JSON.stringify(e)}catch{return`[unserializable:${typeof e}]`}}function B(e){const t=Object.getPrototypeOf(e);return t===Object.prototype||null===t}function F(t){return e.isNullable(t)||e.isString(t)||e.isNumber(t)||e.isBoolean(t)||"symbol"==typeof t||"bigint"==typeof t}function W(t,n,r,o,s,a){return function c(i,u=n.path){if(i instanceof N){const n=U(r,u.join("."));if(Object.entries(i).filter(([e,t])=>!N.keys.has(e)&&t instanceof N).forEach(([e,t])=>c(t,u.concat(e))),F(i.value)){if(t===A.Hydrate)return i.value;const c=u.slice(0,-1),l=c.length>0?U(r,c.join(".")):r;return e.isNullable(l)||$(l,i,u.at(-1),o,s,a),n??i.value}if(t===A.Hydrate){const e=L(c(i.value,u));return $(e,i,null,o,s,a),e}const l=n??L(i.value);return $(l,i,null,o,s,a),e.isNullable(n)?l:(c(i.value,u),n)}if(e.isArray(i))return i.map((e,t)=>c(e,u.concat(t)));if(e.isObject(i)&&!B(i))return i;if(e.isObject(i)){const e=Object.entries(i).map(([e,t])=>[e,c(t,u.concat(e))]),n=Object.fromEntries(e);if(t===A.Hydrate){const e=L(n);return Object.entries(i).forEach(([t,n])=>{n instanceof N&&F(n.value)&&$(e,n,t,o,s,a)}),e}return n}return i}(n.value)}function $(e,t,n,r,o,s){const a=s(e),c=o.get(a)??[];o.set(a,[t.assign(n,r),...c])}class H{#e={};#t;#n=/* @__PURE__ */new Map;#r=/* @__PURE__ */new Set;#o=!1;constructor(e=T){this.#t=e}static pk(){return x()}static"κ"=H.pk;annotate(e,t){return new N(t,e)}"δ"=this.annotate;get model(){return this.#e}get inspect(){return function(n,r,o,s,a){function c(s){const a=s.at(-1),c=U(n(),s),i=s.slice(0,-1),u=t.isNotEmpty(i)?U(n(),i):n();return[...e.isObject(c)||e.isArray(c)?r.get(o(c))?.filter(t=>e.isNullable(t.property))??[]:[],...e.isObject(u)?r.get(o(u))?.filter(e=>e.property===a)??[]:[]]}return function e(r){return new Proxy(()=>{},{get:(o,i)=>"pending"===i?()=>!t.isEmpty(c(r)):"remaining"===i?()=>t.length(c(r)):"box"===i?()=>({value:U(n(),r),inspect:e(r)}):"is"===i?e=>c(r).some(t=>0!==(t.operation&e)):"draft"===i?()=>t.head(c(r))?.value??U(n(),r):"settled"===i?()=>new Promise(e=>{if(t.isEmpty(c(r)))return e(U(n(),r));const o=()=>{t.isEmpty(c(r))&&(a(o),e(U(n(),r)))};s(o)}):e([...r,String(i)])})}([])}(()=>this.#e,this.#n,this.#t,e=>this.#r.add(e),e=>this.#r.delete(e))}hydrate(e){return this.#o=!0,this.#s(A.Hydrate,()=>e)}produce(e){if(!this.#o)throw new Error("State must be hydrated using hydrate() before calling produce()");return this.#s(A.Produce,e)}#s(e,t){const n=/* @__PURE__ */Symbol("process"),[,r]=R.immer.produceWithPatches(this.#e,t);return this.#e=r.reduce((t,r)=>R.immer.applyPatches(t,[{...r,value:W(e,r,t,n,this.#n,this.#t)}]),this.#e),this.#e=L(this.#e),this.#a(),n}prune(e){this.#n.forEach((n,r)=>{const o=n.filter(t=>t.process!==e);t.isEmpty(o)?this.#n.delete(r):this.#n.set(r,o)}),this.#a()}#a(){this.#r.forEach(e=>e())}observe(e){const t=()=>e(this.#e);return this.#r.add(t),()=>this.#r.delete(t)}}const I=new H;function z(e,t=k.Update){return I.annotate(t,e)}function D(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var J,G={exports:{}};const V=/* @__PURE__ */D((J||(J=1,function(e){var t=Object.prototype.hasOwnProperty,n="~";function r(){}function o(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function s(e,t,r,s,a){if("function"!=typeof r)throw new TypeError("The listener must be a function");var c=new o(r,s||e,a),i=n?n+t:t;return e._events[i]?e._events[i].fn?e._events[i]=[e._events[i],c]:e._events[i].push(c):(e._events[i]=c,e._eventsCount++),e}function a(e,t){0===--e._eventsCount?e._events=new r:delete e._events[t]}function c(){this._events=new r,this._eventsCount=0}Object.create&&(r.prototype=/* @__PURE__ */Object.create(null),(new r).__proto__||(n=!1)),c.prototype.eventNames=function(){var e,r,o=[];if(0===this._eventsCount)return o;for(r in e=this._events)t.call(e,r)&&o.push(n?r.slice(1):r);return Object.getOwnPropertySymbols?o.concat(Object.getOwnPropertySymbols(e)):o},c.prototype.listeners=function(e){var t=this._events[n?n+e:e];if(!t)return[];if(t.fn)return[t.fn];for(var r=0,o=t.length,s=new Array(o);r<o;r++)s[r]=t[r].fn;return s},c.prototype.listenerCount=function(e){var t=this._events[n?n+e:e];return t?t.fn?1:t.length:0},c.prototype.emit=function(e,t,r,o,s,a){var c=n?n+e:e;if(!this._events[c])return!1;var i,u,l=this._events[c],f=arguments.length;if(l.fn){switch(l.once&&this.removeListener(e,l.fn,void 0,!0),f){case 1:return l.fn.call(l.context),!0;case 2:return l.fn.call(l.context,t),!0;case 3:return l.fn.call(l.context,t,r),!0;case 4:return l.fn.call(l.context,t,r,o),!0;case 5:return l.fn.call(l.context,t,r,o,s),!0;case 6:return l.fn.call(l.context,t,r,o,s,a),!0}for(u=1,i=new Array(f-1);u<f;u++)i[u-1]=arguments[u];l.fn.apply(l.context,i)}else{var d,p=l.length;for(u=0;u<p;u++)switch(l[u].once&&this.removeListener(e,l[u].fn,void 0,!0),f){case 1:l[u].fn.call(l[u].context);break;case 2:l[u].fn.call(l[u].context,t);break;case 3:l[u].fn.call(l[u].context,t,r);break;case 4:l[u].fn.call(l[u].context,t,r,o);break;default:if(!i)for(d=1,i=new Array(f-1);d<f;d++)i[d-1]=arguments[d];l[u].fn.apply(l[u].context,i)}}return!0},c.prototype.on=function(e,t,n){return s(this,e,t,n,!1)},c.prototype.once=function(e,t,n){return s(this,e,t,n,!0)},c.prototype.removeListener=function(e,t,r,o){var s=n?n+e:e;if(!this._events[s])return this;if(!t)return a(this,s),this;var c=this._events[s];if(c.fn)c.fn!==t||o&&!c.once||r&&c.context!==r||a(this,s);else{for(var i=0,u=[],l=c.length;i<l;i++)(c[i].fn!==t||o&&!c[i].once||r&&c[i].context!==r)&&u.push(c[i]);u.length?this._events[s]=1===u.length?u[0]:u:a(this,s)}return this},c.prototype.removeAllListeners=function(e){var t;return e?this._events[t=n?n+e:e]&&a(this,t):(this._events=new r,this._eventsCount=0),this},c.prototype.off=c.prototype.removeListener,c.prototype.addListener=c.prototype.on,c.prefixed=n,c.EventEmitter=c,e.exports=c}(G)),G.exports));class q extends V{cache=/* @__PURE__ */new Map;emit(e,...t){return this.cache.set(e,t[0]),super.emit(e,...t)}setCache(e,t){this.cache.set(e,t)}getCached(e){return this.cache.get(e)}fire(e,...t){return super.emit(e,...t)}}const K=a.createContext(new q);function Q(){return a.useContext(K)}function X({children:e}){const t=a.useMemo(()=>new q,[]);/* @__PURE__ */
|
|
2
|
+
return s(K.Provider,{value:t,children:e})}const Y=a.createContext(/* @__PURE__ */new Set);function Z({children:e}){const t=a.useMemo(()=>/* @__PURE__ */new Set,[]);/* @__PURE__ */
|
|
3
|
+
return s(Y.Provider,{value:t,children:e})}const ee=a.createContext({current:null});function te(){const e=a.useContext(ee);return a.useMemo(()=>({read:()=>e.current,update(t){e.current=t}}),[e])}function ne({children:e}){const t=a.useRef(null);/* @__PURE__ */
|
|
4
4
|
return s(ee.Provider,{value:t,children:e})}function re({children:e}){/* @__PURE__ */
|
|
5
|
-
return s(X,{children:/* @__PURE__ */s(ne,{children:/* @__PURE__ */s(Z,{children:e})})})}const oe=
|
|
6
|
-
return s(oe.Provider,{value:
|
|
5
|
+
return s(X,{children:/* @__PURE__ */s(ne,{children:/* @__PURE__ */s(Z,{children:e})})})}const oe=a.createContext(null);function se(){return a.useContext(oe)}function ae(e,t){return e?.get(t)??null}function ce(e,t){const n=`Scoped${t.displayName||t.name||"Component"}`,r=v(e);return{[n](e){const n=se(),o=a.useMemo(()=>({action:r,emitter:new q}),[]),c=a.useMemo(()=>{const e=new Map(n??[]);return e.set(r,o),e},[n,o]);/* @__PURE__ */
|
|
6
|
+
return s(oe.Provider,{value:c,children:/* @__PURE__ */s(t,{...e})})}}[n]}const ie=Symbol(((e="")=>`march-hare/replay${e}`)());function ue(e,t,...n){e instanceof q&&e.setCache(t,n[0]);const r=e.listeners(t);return 0===r.length?Promise.resolve():Promise.all(r.map(e=>Promise.resolve(e(...n)))).then(()=>{})}const le={Update:e=>(t,n)=>{t.actions.produce(t=>{t.model[e]=n})},Invert:e=>t=>{t.actions.produce(t=>{t.model[e]=!t.model[e]})}};function fe(e,t){for(const n of e.keys())if(S(n)===t)return n;return null}const de=/* @__PURE__ */Symbol("march-hare.unset");function pe(){const[,e]=a.useReducer(e=>e+1,0);return e}function he(){return{data:de,at:null,else:e=>e}}function me(e,t){return{data:e,at:t,else:t=>e}}function ye(e){if(e instanceof Error){if("TimeoutError"===e.name)return E.Timedout;if("AbortError"===e.name)return E.Supplanted}return E.Errored}const be=a.createContext(/* @__PURE__ */new Map);function ve({action:t,renderer:n}){const r=Q(),o=a.useContext(be),s=pe(),c=a.useMemo(()=>{const e=o.get(t);if(e)return e;const n={state:new H,listeners:/* @__PURE__ */new Set};return o.set(t,n),n},[t,o]);a.useLayoutEffect(()=>{function e(e){c.state.hydrate({value:e}),c.listeners.forEach(e=>e())}return c.listeners.add(s),r.on(t,e),()=>{c.listeners.delete(s),r.off(t,e)}},[t,r,c]);const i=c.state.model?.value;return e.isNullable(i)?null:n(i,c.state.inspect.value)}function ge(...n){const r=e.isUndefined(n[0])||e.isFunction(n[0])?{}:n[0],o=e.isFunction(n[0])?n[0]:n[1]??(()=>({})),s=Q(),c=se(),i=a.useContext(Y),u=pe(),l=a.useRef(!1),f=a.useRef(null),d=a.useRef(new H);l.current||(l.current=!0,f.current=d.current.hydrate(r));const[h,m]=a.useState(()=>d.current.model),b=function(e){const t=a.useRef(e);return a.useLayoutEffect(()=>{t.current=e},[e]),a.useMemo(()=>{return n=t,Object.keys(e).reduce((e,t)=>(Object.defineProperty(e,t,{get:()=>n.current[t],enumerable:!0}),e),{});var n},[e])}(o()),S=a.useMemo(()=>new V,[]),P=a.useRef({handlers:/* @__PURE__ */new Map});P.current.handlers=/* @__PURE__ */new Map;const E=function(){const e=a.useRef(/* @__PURE__ */new Set),t=a.useRef(/* @__PURE__ */new Set);return a.useMemo(()=>({broadcast:e.current,multicast:t.current}),[])}(),M=a.useRef(y.Mounting),C=a.useRef(/* @__PURE__ */new Set),x=a.useRef(0),A=a.useCallback((e,t,n)=>{const r=new AbortController,o={controller:r,action:e,payload:t};return i.add(o),C.current.add(o),{model:d.current.model,get phase(){return M.current},task:o,data:b,tasks:i,actions:{produce(e){if(r.signal.aborted)return;const t=d.current.produce(t=>{e({model:t,inspect:d.current.inspect})});m(d.current.model),n.processes.add(t),f.current&&(n.processes.add(f.current),f.current=null)},dispatch(e,t){if(r.signal.aborted)return Promise.resolve();const n=v(e),o=O(e)?e.channel:void 0;if(j(e)){const e=ae(c,n);return e?ue(e.emitter,n,t,o):Promise.resolve()}return ue(g(e)?s:S,n,t,o)},annotate:(e,t=k.Update)=>d.current.annotate(t,e),async resolution(e){if(r.signal.aborted)return null;const t=v(e),n=j(e)?ae(c,t)?.emitter??null:s;if(!n)return null;if(void 0===n.getCached(t))return null;const o=w(e),a="unknown"!==o?o[0].toLowerCase()+o.slice(1):null;if(a){const e=d.current.inspect[a];e?.pending?.()&&await new Promise((t,n)=>{if(r.signal.aborted)return void n(r.signal.reason);const o=()=>n(r.signal.reason);r.signal.addEventListener("abort",o,{once:!0}),e.settled().then(()=>{r.signal.removeEventListener("abort",o),t()})})}return n.getCached(t)??null},peek(e){if(r.signal.aborted)return null;const t=v(e),n=j(e)?ae(c,t)?.emitter??null:s;return n?n.getCached(t)??null:null}}}},[h]);a.useLayoutEffect(()=>{function t(t,n,r){return function(o,a){const c=r();if(a===ie&&e.isNotNullable(c))return;if(e.isNotNullable(a)&&a!==ie&&e.isNotNullable(c)&&!function(e,t){for(const n of Object.keys(e))if(t[n]!==e[n])return!1;return!0}(a,c))return;const l={processes:/* @__PURE__ */new Set},f=Promise.withResolvers(),h=A(t,o,l);function m(e){const n=fe(P.current.handlers,"Error"),r=null!==n,o={reason:ye(e),error:(a=e,a instanceof Error?a:new Error(String(a))),action:w(t),handled:r,tasks:i};var a;s.fire(p,o),r&&n&&S.emit(n,o)}function y(){for(const e of i)if(e===h.task){i.delete(e),C.current.delete(e);break}l.processes.forEach(e=>d.current.prune(e)),l.processes.size>0&&u(),f.resolve()}let b;try{b=n(h,o)}catch(v){return m(v),void y()}if(!function(e){if(!e||"object"!=typeof e)return!1;const t=Object.prototype.toString.call(e);return"[object Generator]"===t||"[object AsyncGenerator]"===t}(b))return Promise.resolve(b).catch(m).finally(y),f.promise;(async()=>{for await(const e of b);})().catch(m).finally(y)}}x.current++;const n=/* @__PURE__ */new Set;return P.current.handlers.forEach((e,r)=>{for(const{getChannel:o,handler:a}of e){const e=t(r,a,o);if(j(r)){if(c)for(const t of c.values()){const o=t.emitter;o.on(r,e),n.add(()=>o.off(r,e))}S.on(r,e),E.multicast.add(r),n.add(()=>S.off(r,e))}else g(r)?(s.on(r,e),S.on(r,e),E.broadcast.add(r),n.add(()=>{s.off(r,e),S.off(r,e)})):(S.on(r,e),n.add(()=>S.off(r,e)))}}),()=>{const e=++x.current,t=new Set(n);queueMicrotask(()=>{if(x.current!==e){for(const e of t)e();return}for(const e of C.current)e.controller.abort(),i.delete(e);C.current.clear(),M.current=y.Unmounting;const n=fe(P.current.handlers,"Unmount");n&&S.emit(n),M.current=y.Unmounted;for(const e of t)e()})}},[S]),function({unicast:n,broadcast:r,dispatchers:o,scope:s,phase:c,data:i,handlers:u}){const l=a.useRef(null);a.useLayoutEffect(()=>{if(c.current===y.Mounted)return;c.current=y.Mounting;const t=fe(u,"Mount");t&&n.emit(t),o.broadcast.forEach(t=>{const o=r.getCached(t);e.isNullable(o)||n.emit(t,o,ie)}),s&&o.multicast.forEach(t=>{for(const r of s.values()){const o=r.emitter.getCached(t);e.isNullable(o)||n.emit(t,o,ie)}}),c.current=y.Mounted},[]),a.useLayoutEffect(()=>{if(e.isNotNullable(l.current)){const e=function(e,t){return Object.keys(t).reduce((n,r)=>e[r]!==t[r]?{...n,[r]:t[r]}:n,{})}(l.current,i);if(t.isNotEmpty(Object.keys(e))){const t=fe(u,"Update");t&&n.emit(t,e)}}l.current=i},[i,n])}({unicast:S,broadcast:s,dispatchers:E,scope:c,phase:M,data:o(),handlers:P.current.handlers});const _=a.useMemo(()=>[h,{dispatch(e,t){const n=v(e),r=O(e)?e.channel:void 0;if(j(e)){const e=ae(c,n);return e?ue(e.emitter,n,t,r):Promise.resolve()}return ue(g(e)?s:S,n,t,r)},get inspect(){return d.current.inspect},stream:(e,t)=>a.createElement(ve,{action:v(e),renderer:t})}],[h,S]);return _.useAction=(e,t)=>{!function(e,t,n){const r=a.useRef(n);a.useLayoutEffect(()=>{r.current=n});const o=a.useRef(t);a.useLayoutEffect(()=>{o.current=t});const s=a.useCallback((e,t)=>r.current(e,t),[]),c=a.useCallback(()=>O(o.current)?o.current.channel:void 0,[]),i=v(t),u=e.current.handlers.get(i)??/* @__PURE__ */new Set;0===u.size&&e.current.handlers.set(i,u),u.add({getChannel:c,handler:s})}(P,e,t)},_}function we(e,t){return new Promise((n,r)=>{if(t?.aborted)return void r(new M);const o=setTimeout(n,e);t?.addEventListener("abort",()=>{clearTimeout(o),r(new M)},{once:!0})})}async function Oe(e,t,n){if(t?.aborted)throw new M;for(;;){if(await n())return;await we(e,t)}}function Se(e){return e?Boolean(e&&"symbol"!=typeof e):/* @__PURE__ */Symbol(`pk.${Date.now()}.${crypto.randomUUID()}`)}function je(e){return{get(t){try{const n=e.get(t);if(null===n)return he();const r=JSON.parse(n);return me(r.data,Temporal.Instant.from(r.at))}catch{return he()}},set(t,n){if(n.data===de||null===n.at)return!1;try{return e.set(t,JSON.stringify({data:n.data,at:n.at.toString()})),!0}catch{return!1}},remove(t){e.remove(t)},clear(){e.clear()}}}const Pe=/* @__PURE__ */Object.freeze(/* @__PURE__ */Object.defineProperty({__proto__:null,pk:Se,poll:Oe,sleep:we,store:je,unset:de,"ζ":we,"κ":Se,"π":Oe,"σ":je},Symbol.toStringTag,{value:"Module"})),Ee=/* @__PURE__ */new WeakMap;function Me(e){return{run:(t,n)=>e(t,n).then(t=>(Ee.set(e,{data:t,at:Temporal.Now.instant()}),t)),get data(){const t=Ee.get(e);return void 0===t?de:t.data},get at(){return Ee.get(e)?.at??null},seed(t,n){Ee.set(e,{data:t,at:n})}}}function Ce(e){return a.useMemo(()=>{const t=(t,n)=>e.run(t??void 0,n??{});return Object.defineProperties(t,{if:{value:(t,n,r)=>{const o=e.data,s=e.at;if(o!==de&&null!==s){const e=Temporal.Now.instant().since(s),n=Temporal.Duration.from(t.over);if(Temporal.Duration.compare(e,n)<=0)return Promise.resolve(o)}return e.run(n??void 0,r??{})},enumerable:!0},else:{value:function(n){if(null!==(r=n)&&"object"==typeof r&&"data"in r&&"at"in r&&"else"in r)return e.data===de&&n.data!==de&&null!==n.at&&e.seed(n.data,n.at),t;var r;const o=e.data;return o===de?n:o},enumerable:!0},snapshot:{value:()=>{const t=e.data,n=e.at;return t===de||null===n?he():me(t,n)},enumerable:!0},data:{get:()=>e.data,enumerable:!0},at:{get:()=>e.at,enumerable:!0}}),t},[e])}export{M as AbortError,P as Action,re as Boundary,m as Distribution,h as Lifecycle,k as Op,k as Operation,E as Reason,Me as Resource,H as State,C as TimeoutError,le as With,z as annotate,ge as useActions,te as useMode,Ce as useResource,Pe as utils,ce as withScope};
|
package/dist/march-hare.umd.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var global,factory;global=this,factory=function(e,t,n,r,o){"use strict";function s(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e)for(const n in e)if("default"!==n){const r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:()=>e[n]})}return t.default=e,Object.freeze(t)}const c=s(o),i=(e="")=>`march-hare.action/${e}`,a=(e="")=>`march-hare.action/broadcast/${e}`,u=(e="")=>`march-hare.action/multicast/${e}`,l=(e="")=>`march-hare.action.lifecycle/${e}`;class f{static Payload=Symbol("march-hare.brand/Payload");static Broadcast=Symbol("march-hare.brand/Broadcast");static Multicast=Symbol("march-hare.brand/Multicast");static Action=Symbol("march-hare.brand/Action");static Channel=Symbol("march-hare.brand/Channel")}function d(e){const t=Symbol(`march-hare.action.lifecycle/${e}`),n=function(e){return{[f.Action]:t,[f.Payload]:void 0,[f.Channel]:e,channel:e}};return Object.defineProperty(n,f.Action,{value:t,enumerable:!1}),Object.defineProperty(n,f.Payload,{value:void 0,enumerable:!1}),n}const p=Symbol(a("Fault"));class h{static Mount(){return d("Mount")}static Unmount(){return d("Unmount")}static Error(){return d("Error")}static Update(){return d("Update")}static Fault=(()=>{const e={};return Object.defineProperty(e,f.Action,{value:p,enumerable:!1}),Object.defineProperty(e,f.Payload,{value:void 0,enumerable:!1}),Object.defineProperty(e,f.Broadcast,{value:!0,enumerable:!1}),e})()}var m=(e=>(e.Unicast="unicast",e.Broadcast="broadcast",e.Multicast="multicast",e))(m||{}),b=(e=>(e.Mounting="mounting",e.Mounted="mounted",e.Unmounting="unmounting",e.Unmounted="unmounted",e))(b||{});const y=e=>"symbol"==typeof e;function v(e){return t.G.isString(e)||y(e)?e:(t.G.isObject(e)||t.G.isFunction(e))&&f.Action in e?e[f.Action]:e}function g(e){if(t.G.isString(e))return e.startsWith(a());if(y(e))return e.description?.startsWith(a())??!1;if(t.G.isObject(e)||t.G.isFunction(e)){if(f.Broadcast in e&&e[f.Broadcast])return!0;if(f.Action in e){const t=e[f.Action];return t.description?.startsWith(a())??!1}}return!1}function w(e){const n=v(e),r=t.G.isString(n)?n:n.description??"";return r.startsWith(i())&&r.slice(r.lastIndexOf("/")+1)||"unknown"}function j(e){return t.G.isObject(e)&&f.Channel in e&&"channel"in e}function O(e){const t=v(e),n=y(t)?t.description??"":t;return n.startsWith(l())&&n.slice(l().length)||null}function S(e){if(t.G.isString(e))return e.startsWith(u());if(y(e))return e.description?.startsWith(u())??!1;if(t.G.isObject(e)||t.G.isFunction(e)){if(f.Multicast in e&&e[f.Multicast])return!0;if(f.Action in e){const t=e[f.Action];return t.description?.startsWith(u())??!1}}return!1}var P=(e=>(e[e.Timedout=0]="Timedout",e[e.Supplanted=1]="Supplanted",e[e.Errored=2]="Errored",e))(P||{});class x extends Error{name="AbortError";constructor(e="Aborted"){super(e)}}class E extends Error{name="TimeoutError";constructor(e="Timeout"){super(e)}}let M=(e=21)=>{let t="",n=crypto.getRandomValues(new Uint8Array(e|=0));for(;e--;)t+="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"[63&n[e]];return t};var A=(e=>(e[e.Add=1]="Add",e[e.Remove=2]="Remove",e[e.Update=4]="Update",e[e.Move=8]="Move",e[e.Replace=16]="Replace",e[e.Sort=32]="Sort",e[e.Create=64]="Create",e[e.Fetch=128]="Fetch",e[e.Clone=256]="Clone",e[e.Archive=512]="Archive",e[e.Restore=1024]="Restore",e[e.Merge=2048]="Merge",e[e.Reorder=4096]="Reorder",e[e.Sync=8192]="Sync",e[e.Publish=16384]="Publish",e[e.Link=32768]="Link",e[e.Unlink=65536]="Unlink",e[e.Lock=131072]="Lock",e[e.Unlock=262144]="Unlock",e[e.Import=524288]="Import",e[e.Export=1048576]="Export",e[e.Transfer=2097152]="Transfer",e))(A||{}),C=(e=>(e[e.Produce=0]="Produce",e[e.Hydrate=1]="Hydrate",e))(C||{}),G=(e=>(e.Property="property",e.Process="process",e.Value="value",e.Operation="operation",e))(G||{});class k{[n.immerable]=!0;static keys=new Set(Object.values(G));property=null;process=null;value;operation;constructor(e,t){this.value=e,this.operation=t}assign(e,t){const n=new k(this.value,this.operation);return n.property=e,n.process=t,n}}class _{static immer=(()=>{n.enablePatches();const e=new n.Immer;return e.setAutoFreeze(!1),e})();static tag="κ";static id=M}function R(e,t){const n="string"==typeof t?""===t?[]:t.split("."):t;let r=e;for(const o of n){if(null==r)return;r=r[o]}return r}function N(e){if(t.G.isNullable(e)||L(e))return e;if(t.G.isArray(e))return e.map(e=>N(e));if(t.G.isObject(e)&&T(e)){const t=Object.entries(e).map(([e,t])=>[e,N(t)]);return{...Object.fromEntries(t),[_.tag]:e[_.tag]??_.id()}}return e}function U(e){if(Array.isArray(e))return e.filter(e=>_.tag in e).map(e=>e[_.tag]??"").join(",");const t=e[_.tag];if(t)return t;try{return JSON.stringify(e)}catch{return`[unserializable:${typeof e}]`}}function T(e){const t=Object.getPrototypeOf(e);return t===Object.prototype||null===t}function L(e){return t.G.isNullable(e)||t.G.isString(e)||t.G.isNumber(e)||t.G.isBoolean(e)||"symbol"==typeof e||"bigint"==typeof e}function B(e,n,r,o,s,c){return function i(a,u=n.path){if(a instanceof k){const n=R(r,u.join("."));if(Object.entries(a).filter(([e,t])=>!k.keys.has(e)&&t instanceof k).forEach(([e,t])=>i(t,u.concat(e))),L(a.value)){if(e===C.Hydrate)return a.value;const i=u.slice(0,-1),l=i.length>0?R(r,i.join(".")):r;return t.G.isNullable(l)||F(l,a,u.at(-1),o,s,c),n??a.value}if(e===C.Hydrate){const e=N(i(a.value,u));return F(e,a,null,o,s,c),e}const l=n??N(a.value);return F(l,a,null,o,s,c),t.G.isNullable(n)?l:(i(a.value,u),n)}if(t.G.isArray(a))return a.map((e,t)=>i(e,u.concat(t)));if(t.G.isObject(a)&&!T(a))return a;if(t.G.isObject(a)){const t=Object.entries(a).map(([e,t])=>[e,i(t,u.concat(e))]),n=Object.fromEntries(t);if(e===C.Hydrate){const e=N(n);return Object.entries(a).forEach(([t,n])=>{n instanceof k&&L(n.value)&&F(e,n,t,o,s,c)}),e}return n}return a}(n.value)}function F(e,t,n,r,o,s){const c=s(e),i=o.get(c)??[];o.set(c,[t.assign(n,r),...i])}class W{#e={};#t;#n=new Map;#r=new Set;#o=!1;constructor(e=U){this.#t=e}static pk(){return M()}static"κ"=W.pk;annotate(e,t){return new k(t,e)}"δ"=this.annotate;get model(){return this.#e}get inspect(){return function(e,n,r,o,s){function c(o){const s=o.at(-1),c=R(e(),o),i=o.slice(0,-1),a=t.A.isNotEmpty(i)?R(e(),i):e();return[...t.G.isObject(c)||t.G.isArray(c)?n.get(r(c))?.filter(e=>t.G.isNullable(e.property))??[]:[],...t.G.isObject(a)?n.get(r(a))?.filter(e=>e.property===s)??[]:[]]}return function n(r){return new Proxy(()=>{},{get:(i,a)=>"pending"===a?()=>!t.A.isEmpty(c(r)):"remaining"===a?()=>t.A.length(c(r)):"box"===a?()=>({value:R(e(),r),inspect:n(r)}):"is"===a?e=>c(r).some(t=>0!==(t.operation&e)):"draft"===a?()=>t.A.head(c(r))?.value??R(e(),r):"settled"===a?()=>new Promise(n=>{if(t.A.isEmpty(c(r)))return n(R(e(),r));const i=()=>{t.A.isEmpty(c(r))&&(s(i),n(R(e(),r)))};o(i)}):n([...r,String(a)])})}([])}(()=>this.#e,this.#n,this.#t,e=>this.#r.add(e),e=>this.#r.delete(e))}hydrate(e){return this.#o=!0,this.#s(C.Hydrate,()=>e)}produce(e){if(!this.#o)throw new Error("State must be hydrated using hydrate() before calling produce()");return this.#s(C.Produce,e)}#s(e,t){const n=Symbol("process"),[,r]=_.immer.produceWithPatches(this.#e,t);return this.#e=r.reduce((t,r)=>_.immer.applyPatches(t,[{...r,value:B(e,r,t,n,this.#n,this.#t)}]),this.#e),this.#e=N(this.#e),this.#c(),n}prune(e){this.#n.forEach((n,r)=>{const o=n.filter(t=>t.process!==e);t.A.isEmpty(o)?this.#n.delete(r):this.#n.set(r,o)}),this.#c()}#c(){this.#r.forEach(e=>e())}observe(e){const t=()=>e(this.#e);return this.#r.add(t),()=>this.#r.delete(t)}}const $=new W;function z(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var H,I={exports:{}},D=(H||(H=1,function(e){var t=Object.prototype.hasOwnProperty,n="~";function r(){}function o(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function s(e,t,r,s,c){if("function"!=typeof r)throw new TypeError("The listener must be a function");var i=new o(r,s||e,c),a=n?n+t:t;return e._events[a]?e._events[a].fn?e._events[a]=[e._events[a],i]:e._events[a].push(i):(e._events[a]=i,e._eventsCount++),e}function c(e,t){0===--e._eventsCount?e._events=new r:delete e._events[t]}function i(){this._events=new r,this._eventsCount=0}Object.create&&(r.prototype=Object.create(null),(new r).__proto__||(n=!1)),i.prototype.eventNames=function(){var e,r,o=[];if(0===this._eventsCount)return o;for(r in e=this._events)t.call(e,r)&&o.push(n?r.slice(1):r);return Object.getOwnPropertySymbols?o.concat(Object.getOwnPropertySymbols(e)):o},i.prototype.listeners=function(e){var t=this._events[n?n+e:e];if(!t)return[];if(t.fn)return[t.fn];for(var r=0,o=t.length,s=new Array(o);r<o;r++)s[r]=t[r].fn;return s},i.prototype.listenerCount=function(e){var t=this._events[n?n+e:e];return t?t.fn?1:t.length:0},i.prototype.emit=function(e,t,r,o,s,c){var i=n?n+e:e;if(!this._events[i])return!1;var a,u,l=this._events[i],f=arguments.length;if(l.fn){switch(l.once&&this.removeListener(e,l.fn,void 0,!0),f){case 1:return l.fn.call(l.context),!0;case 2:return l.fn.call(l.context,t),!0;case 3:return l.fn.call(l.context,t,r),!0;case 4:return l.fn.call(l.context,t,r,o),!0;case 5:return l.fn.call(l.context,t,r,o,s),!0;case 6:return l.fn.call(l.context,t,r,o,s,c),!0}for(u=1,a=new Array(f-1);u<f;u++)a[u-1]=arguments[u];l.fn.apply(l.context,a)}else{var d,p=l.length;for(u=0;u<p;u++)switch(l[u].once&&this.removeListener(e,l[u].fn,void 0,!0),f){case 1:l[u].fn.call(l[u].context);break;case 2:l[u].fn.call(l[u].context,t);break;case 3:l[u].fn.call(l[u].context,t,r);break;case 4:l[u].fn.call(l[u].context,t,r,o);break;default:if(!a)for(d=1,a=new Array(f-1);d<f;d++)a[d-1]=arguments[d];l[u].fn.apply(l[u].context,a)}}return!0},i.prototype.on=function(e,t,n){return s(this,e,t,n,!1)},i.prototype.once=function(e,t,n){return s(this,e,t,n,!0)},i.prototype.removeListener=function(e,t,r,o){var s=n?n+e:e;if(!this._events[s])return this;if(!t)return c(this,s),this;var i=this._events[s];if(i.fn)i.fn!==t||o&&!i.once||r&&i.context!==r||c(this,s);else{for(var a=0,u=[],l=i.length;a<l;a++)(i[a].fn!==t||o&&!i[a].once||r&&i[a].context!==r)&&u.push(i[a]);u.length?this._events[s]=1===u.length?u[0]:u:c(this,s)}return this},i.prototype.removeAllListeners=function(e){var t;return e?this._events[t=n?n+e:e]&&c(this,t):(this._events=new r,this._eventsCount=0),this},i.prototype.off=i.prototype.removeListener,i.prototype.addListener=i.prototype.on,i.prefixed=n,i.EventEmitter=i,e.exports=i}(I)),I.exports);const q=z(D);class J extends q{cache=new Map;emit(e,...t){return this.cache.set(e,t[0]),super.emit(e,...t)}setCache(e,t){this.cache.set(e,t)}getCached(e){return this.cache.get(e)}fire(e,...t){return super.emit(e,...t)}}const V=c.createContext(new J);function K(){return c.useContext(V)}function Q({children:e}){const t=c.useMemo(()=>new J,[]);return r.jsx(V.Provider,{value:t,children:e})}const X=c.createContext(new Set);function Y({children:e}){const t=c.useMemo(()=>new Set,[]);return r.jsx(X.Provider,{value:t,children:e})}const Z=c.createContext({current:null});function ee({children:e}){const t=c.useRef(null);return r.jsx(Z.Provider,{value:t,children:e})}const te=c.createContext(null);function ne(){return c.useContext(te)}function re(e,t){return e?.get(t)??null}const oe=Symbol(((e="")=>`march-hare/replay${e}`)());function se(e,t,...n){e instanceof J&&e.setCache(t,n[0]);const r=e.listeners(t);return 0===r.length?Promise.resolve():Promise.all(r.map(e=>Promise.resolve(e(...n)))).then(()=>{})}function ce(e,t){for(const n of e.keys())if(O(n)===t)return n;return null}function ie(){const[,e]=c.useReducer(e=>e+1,0);return e}function ae(e){if(e instanceof Error){if("TimeoutError"===e.name)return P.Timedout;if("AbortError"===e.name)return P.Supplanted}return P.Errored}const ue=c.createContext(new Map);function le({action:e,renderer:n}){const r=K(),o=c.useContext(ue),s=ie(),i=c.useMemo(()=>{const t=o.get(e);if(t)return t;const n={state:new W,listeners:new Set};return o.set(e,n),n},[e,o]);c.useLayoutEffect(()=>{function t(e){i.state.hydrate({value:e}),i.listeners.forEach(e=>e())}return i.listeners.add(s),r.on(e,t),()=>{i.listeners.delete(s),r.off(e,t)}},[e,r,i]);const a=i.state.model?.value;return t.G.isNullable(a)?null:n(a,i.state.inspect.value)}function fe(e,t){return new Promise((n,r)=>{if(t?.aborted)return void r(new x);const o=setTimeout(n,e);t?.addEventListener("abort",()=>{clearTimeout(o),r(new x)},{once:!0})})}async function de(e,t,n){if(t?.aborted)throw new x;for(;;){if(await n())return;await fe(e,t)}}function pe(e){return e?Boolean(e&&"symbol"!=typeof e):Symbol(`pk.${Date.now()}.${crypto.randomUUID()}`)}const he=Object.freeze(Object.defineProperty({__proto__:null,pk:pe,poll:de,sleep:fe,"ζ":fe,"κ":pe,"π":de},Symbol.toStringTag,{value:"Module"}));e.AbortError=x,e.Action=(e,t=m.Unicast)=>{const n=t===m.Broadcast?Symbol(a(e)):t===m.Multicast?Symbol(u(e)):Symbol(i(e)),r=function(e){return{[f.Action]:n,[f.Payload]:void 0,[f.Channel]:e,channel:e}};return Object.defineProperty(r,f.Action,{value:n,enumerable:!1}),Object.defineProperty(r,f.Payload,{value:void 0,enumerable:!1}),t===m.Broadcast&&Object.defineProperty(r,f.Broadcast,{value:!0,enumerable:!1}),t===m.Multicast&&Object.defineProperty(r,f.Multicast,{value:!0,enumerable:!1}),r},e.Boundary=function({children:e}){return r.jsx(Q,{children:r.jsx(ee,{children:r.jsx(Y,{children:e})})})},e.Distribution=m,e.Lifecycle=h,e.Op=A,e.Operation=A,e.Reason=P,e.Resource=function(e,t){const n=new Map;let r=null,o=null;return Object.freeze({key:e,run:e=>{const s=JSON.stringify(e),c=n.get(s);if(c)return c;const i=t(e).then(e=>(n.get(s)===i&&n.delete(s),r=e,o=Temporal.Now.instant(),e),e=>{throw n.get(s)===i&&n.delete(s),e});return n.set(s,i),i},get data(){return r},get at(){return o}})},e.State=W,e.TimeoutError=E,e.With={Update:e=>(t,n)=>{t.actions.produce(t=>{t.model[e]=n})},Invert:e=>t=>{t.actions.produce(t=>{t.model[e]=!t.model[e]})}},e.annotate=function(e,t=A.Update){return $.annotate(t,e)},e.useActions=function(...e){const n=t.G.isUndefined(e[0])||t.G.isFunction(e[0])?{}:e[0],r=t.G.isFunction(e[0])?e[0]:e[1]??(()=>({})),o=K(),s=ne(),i=c.useContext(X),a=ie(),u=c.useRef(!1),l=c.useRef(null),f=c.useRef(new W);u.current||(u.current=!0,l.current=f.current.hydrate(n));const[d,h]=c.useState(()=>f.current.model),m=function(e){const t=c.useRef(e);return c.useLayoutEffect(()=>{t.current=e},[e]),c.useMemo(()=>{return n=t,Object.keys(e).reduce((e,t)=>(Object.defineProperty(e,t,{get:()=>n.current[t],enumerable:!0}),e),{});var n},[e])}(r()),y=c.useMemo(()=>new q,[]),O=c.useRef({handlers:new Map});O.current.handlers=new Map;const P=function(){const e=c.useRef(new Set),t=c.useRef(new Set);return c.useMemo(()=>({broadcast:e.current,multicast:t.current}),[])}(),x=c.useRef(b.Mounting),E=c.useRef(new Set),M=c.useRef(0),C=c.useCallback((e,t,n)=>{const r=new AbortController,c={controller:r,action:e,payload:t};return i.add(c),E.current.add(c),{model:f.current.model,get phase(){return x.current},task:c,data:m,tasks:i,actions:{produce(e){if(r.signal.aborted)return;const t=f.current.produce(t=>{e({model:t,inspect:f.current.inspect})});h(f.current.model),n.processes.add(t),l.current&&(n.processes.add(l.current),l.current=null)},dispatch(e,t){if(r.signal.aborted)return Promise.resolve();const n=v(e),c=j(e)?e.channel:void 0;if(S(e)){const e=re(s,n);return e?se(e.emitter,n,t,c):Promise.resolve()}return se(g(e)?o:y,n,t,c)},annotate:(e,t=A.Update)=>f.current.annotate(t,e),async resolution(e){if(r.signal.aborted)return null;const t=v(e),n=S(e)?re(s,t)?.emitter??null:o;if(!n)return null;if(void 0===n.getCached(t))return null;const c=w(e),i="unknown"!==c?c[0].toLowerCase()+c.slice(1):null;if(i){const e=f.current.inspect[i];e?.pending?.()&&await new Promise((t,n)=>{if(r.signal.aborted)return void n(r.signal.reason);const o=()=>n(r.signal.reason);r.signal.addEventListener("abort",o,{once:!0}),e.settled().then(()=>{r.signal.removeEventListener("abort",o),t()})})}return n.getCached(t)??null},peek(e){if(r.signal.aborted)return null;const t=v(e),n=S(e)?re(s,t)?.emitter??null:o;return n?n.getCached(t)??null:null}}}},[d]);c.useLayoutEffect(()=>{function e(e,n,r){return function(s,c){const u=r();if(c===oe&&t.G.isNotNullable(u))return;if(t.G.isNotNullable(c)&&c!==oe&&t.G.isNotNullable(u)&&!function(e,t){for(const n of Object.keys(e))if(t[n]!==e[n])return!1;return!0}(c,u))return;const l={processes:new Set},d=Promise.withResolvers(),h=C(e,s,l);function m(t){const n=ce(O.current.handlers,"Error"),r=null!==n,s={reason:ae(t),error:(c=t,c instanceof Error?c:new Error(String(c))),action:w(e),handled:r,tasks:i};var c;o.fire(p,s),r&&n&&y.emit(n,s)}function b(){for(const e of i)if(e===h.task){i.delete(e),E.current.delete(e);break}l.processes.forEach(e=>f.current.prune(e)),l.processes.size>0&&a(),d.resolve()}let v;try{v=n(h,s)}catch(g){return m(g),void b()}if(!function(e){if(!e||"object"!=typeof e)return!1;const t=Object.prototype.toString.call(e);return"[object Generator]"===t||"[object AsyncGenerator]"===t}(v))return Promise.resolve(v).catch(m).finally(b),d.promise;(async()=>{for await(const e of v);})().catch(m).finally(b)}}M.current++;const n=new Set;return O.current.handlers.forEach((t,r)=>{for(const{getChannel:c,handler:i}of t){const t=e(r,i,c);if(S(r)){if(s)for(const e of s.values()){const o=e.emitter;o.on(r,t),n.add(()=>o.off(r,t))}y.on(r,t),P.multicast.add(r),n.add(()=>y.off(r,t))}else g(r)?(o.on(r,t),y.on(r,t),P.broadcast.add(r),n.add(()=>{o.off(r,t),y.off(r,t)})):(y.on(r,t),n.add(()=>y.off(r,t)))}}),()=>{const e=++M.current,t=new Set(n);queueMicrotask(()=>{if(M.current!==e){for(const e of t)e();return}for(const e of E.current)e.controller.abort(),i.delete(e);E.current.clear(),x.current=b.Unmounting;const n=ce(O.current.handlers,"Unmount");n&&y.emit(n),x.current=b.Unmounted;for(const e of t)e()})}},[y]),function({unicast:e,broadcast:n,dispatchers:r,scope:o,phase:s,data:i,handlers:a}){const u=c.useRef(null);c.useLayoutEffect(()=>{if(s.current===b.Mounted)return;s.current=b.Mounting;const c=ce(a,"Mount");c&&e.emit(c),r.broadcast.forEach(r=>{const o=n.getCached(r);t.G.isNullable(o)||e.emit(r,o,oe)}),o&&r.multicast.forEach(n=>{for(const r of o.values()){const o=r.emitter.getCached(n);t.G.isNullable(o)||e.emit(n,o,oe)}}),s.current=b.Mounted},[]),c.useLayoutEffect(()=>{if(t.G.isNotNullable(u.current)){const n=function(e,t){return Object.keys(t).reduce((n,r)=>e[r]!==t[r]?{...n,[r]:t[r]}:n,{})}(u.current,i);if(t.A.isNotEmpty(Object.keys(n))){const t=ce(a,"Update");t&&e.emit(t,n)}}u.current=i},[i,e])}({unicast:y,broadcast:o,dispatchers:P,scope:s,phase:x,data:r(),handlers:O.current.handlers});const G=c.useMemo(()=>[d,{dispatch(e,t){const n=v(e),r=j(e)?e.channel:void 0;if(S(e)){const e=re(s,n);return e?se(e.emitter,n,t,r):Promise.resolve()}return se(g(e)?o:y,n,t,r)},get inspect(){return f.current.inspect},stream:(e,t)=>c.createElement(le,{action:v(e),renderer:t})}],[d,y]);return G.useAction=(e,t)=>{!function(e,t,n){const r=c.useRef(n);c.useLayoutEffect(()=>{r.current=n});const o=c.useRef(t);c.useLayoutEffect(()=>{o.current=t});const s=c.useCallback((e,t)=>r.current(e,t),[]),i=c.useCallback(()=>j(o.current)?o.current.channel:void 0,[]),a=v(t),u=e.current.handlers.get(a)??new Set;0===u.size&&e.current.handlers.set(a,u),u.add({getChannel:i,handler:s})}(O,e,t)},G.useResource=e=>{const t=c.useMemo(()=>{const t=t=>e.run(t??{});return Object.assign(t,{if:(n,r)=>{const{data:o,at:s}=e;if(null!==s&&null!==o){const e=Temporal.Now.instant().since(s),t=Temporal.Duration.from(n.over);if(Temporal.Duration.compare(e,t)<=0)return Promise.resolve(o)}return t(r)}})},[e]);return c.useMemo(()=>Object.freeze({run:t,get data(){return e.data},get at(){return e.at}}),[e,t])},G},e.useMode=function(){const e=c.useContext(Z);return c.useMemo(()=>({read:()=>e.current,update(t){e.current=t}}),[e])},e.utils=he,e.withScope=function(e,t){const n=`Scoped${t.displayName||t.name||"Component"}`,o=v(e);return{[n](e){const n=ne(),s=c.useMemo(()=>({action:o,emitter:new J}),[]),i=c.useMemo(()=>{const e=new Map(n??[]);return e.set(o,s),e},[n,s]);return r.jsx(te.Provider,{value:i,children:r.jsx(t,{...e})})}}[n]},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})},"object"==typeof exports&&"undefined"!=typeof module?factory(exports,require("@mobily/ts-belt"),require("immer"),require("react/jsx-runtime"),require("react")):"function"==typeof define&&define.amd?define(["exports","@mobily/ts-belt","immer","react/jsx-runtime","react"],factory):factory((global="undefined"!=typeof globalThis?globalThis:global||self).MarchHare={},global.TsBelt,global.Immer,global.jsxRuntime,global.React);
|
|
1
|
+
var global,factory;global=this,factory=function(e,t,n,r,o){"use strict";function s(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e)for(const n in e)if("default"!==n){const r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:()=>e[n]})}return t.default=e,Object.freeze(t)}const a=s(o),c=(e="")=>`march-hare.action/${e}`,i=(e="")=>`march-hare.action/broadcast/${e}`,u=(e="")=>`march-hare.action/multicast/${e}`,l=(e="")=>`march-hare.action.lifecycle/${e}`;class f{static Payload=Symbol("march-hare.brand/Payload");static Broadcast=Symbol("march-hare.brand/Broadcast");static Multicast=Symbol("march-hare.brand/Multicast");static Action=Symbol("march-hare.brand/Action");static Channel=Symbol("march-hare.brand/Channel")}function d(e){const t=Symbol(`march-hare.action.lifecycle/${e}`),n=function(e){return{[f.Action]:t,[f.Payload]:void 0,[f.Channel]:e,channel:e}};return Object.defineProperty(n,f.Action,{value:t,enumerable:!1}),Object.defineProperty(n,f.Payload,{value:void 0,enumerable:!1}),n}const p=Symbol(i("Fault"));class h{static Mount(){return d("Mount")}static Unmount(){return d("Unmount")}static Error(){return d("Error")}static Update(){return d("Update")}static Fault=(()=>{const e={};return Object.defineProperty(e,f.Action,{value:p,enumerable:!1}),Object.defineProperty(e,f.Payload,{value:void 0,enumerable:!1}),Object.defineProperty(e,f.Broadcast,{value:!0,enumerable:!1}),e})()}var m=(e=>(e.Unicast="unicast",e.Broadcast="broadcast",e.Multicast="multicast",e))(m||{}),b=(e=>(e.Mounting="mounting",e.Mounted="mounted",e.Unmounting="unmounting",e.Unmounted="unmounted",e))(b||{});const y=e=>"symbol"==typeof e;function v(e){return t.G.isString(e)||y(e)?e:(t.G.isObject(e)||t.G.isFunction(e))&&f.Action in e?e[f.Action]:e}function g(e){if(t.G.isString(e))return e.startsWith(i());if(y(e))return e.description?.startsWith(i())??!1;if(t.G.isObject(e)||t.G.isFunction(e)){if(f.Broadcast in e&&e[f.Broadcast])return!0;if(f.Action in e){const t=e[f.Action];return t.description?.startsWith(i())??!1}}return!1}function w(e){const n=v(e),r=t.G.isString(n)?n:n.description??"";return r.startsWith(c())&&r.slice(r.lastIndexOf("/")+1)||"unknown"}function j(e){return t.G.isObject(e)&&f.Channel in e&&"channel"in e}function O(e){const t=v(e),n=y(t)?t.description??"":t;return n.startsWith(l())&&n.slice(l().length)||null}function S(e){if(t.G.isString(e))return e.startsWith(u());if(y(e))return e.description?.startsWith(u())??!1;if(t.G.isObject(e)||t.G.isFunction(e)){if(f.Multicast in e&&e[f.Multicast])return!0;if(f.Action in e){const t=e[f.Action];return t.description?.startsWith(u())??!1}}return!1}var P=(e=>(e[e.Timedout=0]="Timedout",e[e.Supplanted=1]="Supplanted",e[e.Errored=2]="Errored",e))(P||{});class x extends Error{name="AbortError";constructor(e="Aborted"){super(e)}}class E extends Error{name="TimeoutError";constructor(e="Timeout"){super(e)}}let M=(e=21)=>{let t="",n=crypto.getRandomValues(new Uint8Array(e|=0));for(;e--;)t+="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"[63&n[e]];return t};var A=(e=>(e[e.Add=1]="Add",e[e.Remove=2]="Remove",e[e.Update=4]="Update",e[e.Move=8]="Move",e[e.Replace=16]="Replace",e[e.Sort=32]="Sort",e[e.Create=64]="Create",e[e.Fetch=128]="Fetch",e[e.Clone=256]="Clone",e[e.Archive=512]="Archive",e[e.Restore=1024]="Restore",e[e.Merge=2048]="Merge",e[e.Reorder=4096]="Reorder",e[e.Sync=8192]="Sync",e[e.Publish=16384]="Publish",e[e.Link=32768]="Link",e[e.Unlink=65536]="Unlink",e[e.Lock=131072]="Lock",e[e.Unlock=262144]="Unlock",e[e.Import=524288]="Import",e[e.Export=1048576]="Export",e[e.Transfer=2097152]="Transfer",e))(A||{}),C=(e=>(e[e.Produce=0]="Produce",e[e.Hydrate=1]="Hydrate",e))(C||{}),G=(e=>(e.Property="property",e.Process="process",e.Value="value",e.Operation="operation",e))(G||{});class k{[n.immerable]=!0;static keys=new Set(Object.values(G));property=null;process=null;value;operation;constructor(e,t){this.value=e,this.operation=t}assign(e,t){const n=new k(this.value,this.operation);return n.property=e,n.process=t,n}}class _{static immer=(()=>{n.enablePatches();const e=new n.Immer;return e.setAutoFreeze(!1),e})();static tag="κ";static id=M}function R(e,t){const n="string"==typeof t?""===t?[]:t.split("."):t;let r=e;for(const o of n){if(null==r)return;r=r[o]}return r}function N(e){if(t.G.isNullable(e)||L(e))return e;if(t.G.isArray(e))return e.map(e=>N(e));if(t.G.isObject(e)&&T(e)){const t=Object.entries(e).map(([e,t])=>[e,N(t)]);return{...Object.fromEntries(t),[_.tag]:e[_.tag]??_.id()}}return e}function U(e){if(Array.isArray(e))return e.filter(e=>_.tag in e).map(e=>e[_.tag]??"").join(",");const t=e[_.tag];if(t)return t;try{return JSON.stringify(e)}catch{return`[unserializable:${typeof e}]`}}function T(e){const t=Object.getPrototypeOf(e);return t===Object.prototype||null===t}function L(e){return t.G.isNullable(e)||t.G.isString(e)||t.G.isNumber(e)||t.G.isBoolean(e)||"symbol"==typeof e||"bigint"==typeof e}function B(e,n,r,o,s,a){return function c(i,u=n.path){if(i instanceof k){const n=R(r,u.join("."));if(Object.entries(i).filter(([e,t])=>!k.keys.has(e)&&t instanceof k).forEach(([e,t])=>c(t,u.concat(e))),L(i.value)){if(e===C.Hydrate)return i.value;const c=u.slice(0,-1),l=c.length>0?R(r,c.join(".")):r;return t.G.isNullable(l)||W(l,i,u.at(-1),o,s,a),n??i.value}if(e===C.Hydrate){const e=N(c(i.value,u));return W(e,i,null,o,s,a),e}const l=n??N(i.value);return W(l,i,null,o,s,a),t.G.isNullable(n)?l:(c(i.value,u),n)}if(t.G.isArray(i))return i.map((e,t)=>c(e,u.concat(t)));if(t.G.isObject(i)&&!T(i))return i;if(t.G.isObject(i)){const t=Object.entries(i).map(([e,t])=>[e,c(t,u.concat(e))]),n=Object.fromEntries(t);if(e===C.Hydrate){const e=N(n);return Object.entries(i).forEach(([t,n])=>{n instanceof k&&L(n.value)&&W(e,n,t,o,s,a)}),e}return n}return i}(n.value)}function W(e,t,n,r,o,s){const a=s(e),c=o.get(a)??[];o.set(a,[t.assign(n,r),...c])}class F{#e={};#t;#n=new Map;#r=new Set;#o=!1;constructor(e=U){this.#t=e}static pk(){return M()}static"κ"=F.pk;annotate(e,t){return new k(t,e)}"δ"=this.annotate;get model(){return this.#e}get inspect(){return function(e,n,r,o,s){function a(o){const s=o.at(-1),a=R(e(),o),c=o.slice(0,-1),i=t.A.isNotEmpty(c)?R(e(),c):e();return[...t.G.isObject(a)||t.G.isArray(a)?n.get(r(a))?.filter(e=>t.G.isNullable(e.property))??[]:[],...t.G.isObject(i)?n.get(r(i))?.filter(e=>e.property===s)??[]:[]]}return function n(r){return new Proxy(()=>{},{get:(c,i)=>"pending"===i?()=>!t.A.isEmpty(a(r)):"remaining"===i?()=>t.A.length(a(r)):"box"===i?()=>({value:R(e(),r),inspect:n(r)}):"is"===i?e=>a(r).some(t=>0!==(t.operation&e)):"draft"===i?()=>t.A.head(a(r))?.value??R(e(),r):"settled"===i?()=>new Promise(n=>{if(t.A.isEmpty(a(r)))return n(R(e(),r));const c=()=>{t.A.isEmpty(a(r))&&(s(c),n(R(e(),r)))};o(c)}):n([...r,String(i)])})}([])}(()=>this.#e,this.#n,this.#t,e=>this.#r.add(e),e=>this.#r.delete(e))}hydrate(e){return this.#o=!0,this.#s(C.Hydrate,()=>e)}produce(e){if(!this.#o)throw new Error("State must be hydrated using hydrate() before calling produce()");return this.#s(C.Produce,e)}#s(e,t){const n=Symbol("process"),[,r]=_.immer.produceWithPatches(this.#e,t);return this.#e=r.reduce((t,r)=>_.immer.applyPatches(t,[{...r,value:B(e,r,t,n,this.#n,this.#t)}]),this.#e),this.#e=N(this.#e),this.#a(),n}prune(e){this.#n.forEach((n,r)=>{const o=n.filter(t=>t.process!==e);t.A.isEmpty(o)?this.#n.delete(r):this.#n.set(r,o)}),this.#a()}#a(){this.#r.forEach(e=>e())}observe(e){const t=()=>e(this.#e);return this.#r.add(t),()=>this.#r.delete(t)}}const $=new F;function I(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var H,z={exports:{}},D=(H||(H=1,function(e){var t=Object.prototype.hasOwnProperty,n="~";function r(){}function o(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function s(e,t,r,s,a){if("function"!=typeof r)throw new TypeError("The listener must be a function");var c=new o(r,s||e,a),i=n?n+t:t;return e._events[i]?e._events[i].fn?e._events[i]=[e._events[i],c]:e._events[i].push(c):(e._events[i]=c,e._eventsCount++),e}function a(e,t){0===--e._eventsCount?e._events=new r:delete e._events[t]}function c(){this._events=new r,this._eventsCount=0}Object.create&&(r.prototype=Object.create(null),(new r).__proto__||(n=!1)),c.prototype.eventNames=function(){var e,r,o=[];if(0===this._eventsCount)return o;for(r in e=this._events)t.call(e,r)&&o.push(n?r.slice(1):r);return Object.getOwnPropertySymbols?o.concat(Object.getOwnPropertySymbols(e)):o},c.prototype.listeners=function(e){var t=this._events[n?n+e:e];if(!t)return[];if(t.fn)return[t.fn];for(var r=0,o=t.length,s=new Array(o);r<o;r++)s[r]=t[r].fn;return s},c.prototype.listenerCount=function(e){var t=this._events[n?n+e:e];return t?t.fn?1:t.length:0},c.prototype.emit=function(e,t,r,o,s,a){var c=n?n+e:e;if(!this._events[c])return!1;var i,u,l=this._events[c],f=arguments.length;if(l.fn){switch(l.once&&this.removeListener(e,l.fn,void 0,!0),f){case 1:return l.fn.call(l.context),!0;case 2:return l.fn.call(l.context,t),!0;case 3:return l.fn.call(l.context,t,r),!0;case 4:return l.fn.call(l.context,t,r,o),!0;case 5:return l.fn.call(l.context,t,r,o,s),!0;case 6:return l.fn.call(l.context,t,r,o,s,a),!0}for(u=1,i=new Array(f-1);u<f;u++)i[u-1]=arguments[u];l.fn.apply(l.context,i)}else{var d,p=l.length;for(u=0;u<p;u++)switch(l[u].once&&this.removeListener(e,l[u].fn,void 0,!0),f){case 1:l[u].fn.call(l[u].context);break;case 2:l[u].fn.call(l[u].context,t);break;case 3:l[u].fn.call(l[u].context,t,r);break;case 4:l[u].fn.call(l[u].context,t,r,o);break;default:if(!i)for(d=1,i=new Array(f-1);d<f;d++)i[d-1]=arguments[d];l[u].fn.apply(l[u].context,i)}}return!0},c.prototype.on=function(e,t,n){return s(this,e,t,n,!1)},c.prototype.once=function(e,t,n){return s(this,e,t,n,!0)},c.prototype.removeListener=function(e,t,r,o){var s=n?n+e:e;if(!this._events[s])return this;if(!t)return a(this,s),this;var c=this._events[s];if(c.fn)c.fn!==t||o&&!c.once||r&&c.context!==r||a(this,s);else{for(var i=0,u=[],l=c.length;i<l;i++)(c[i].fn!==t||o&&!c[i].once||r&&c[i].context!==r)&&u.push(c[i]);u.length?this._events[s]=1===u.length?u[0]:u:a(this,s)}return this},c.prototype.removeAllListeners=function(e){var t;return e?this._events[t=n?n+e:e]&&a(this,t):(this._events=new r,this._eventsCount=0),this},c.prototype.off=c.prototype.removeListener,c.prototype.addListener=c.prototype.on,c.prefixed=n,c.EventEmitter=c,e.exports=c}(z)),z.exports);const q=I(D);class J extends q{cache=new Map;emit(e,...t){return this.cache.set(e,t[0]),super.emit(e,...t)}setCache(e,t){this.cache.set(e,t)}getCached(e){return this.cache.get(e)}fire(e,...t){return super.emit(e,...t)}}const V=a.createContext(new J);function K(){return a.useContext(V)}function Q({children:e}){const t=a.useMemo(()=>new J,[]);return r.jsx(V.Provider,{value:t,children:e})}const X=a.createContext(new Set);function Y({children:e}){const t=a.useMemo(()=>new Set,[]);return r.jsx(X.Provider,{value:t,children:e})}const Z=a.createContext({current:null});function ee({children:e}){const t=a.useRef(null);return r.jsx(Z.Provider,{value:t,children:e})}const te=a.createContext(null);function ne(){return a.useContext(te)}function re(e,t){return e?.get(t)??null}const oe=Symbol(((e="")=>`march-hare/replay${e}`)());function se(e,t,...n){e instanceof J&&e.setCache(t,n[0]);const r=e.listeners(t);return 0===r.length?Promise.resolve():Promise.all(r.map(e=>Promise.resolve(e(...n)))).then(()=>{})}function ae(e,t){for(const n of e.keys())if(O(n)===t)return n;return null}const ce=Symbol("march-hare.unset");function ie(){const[,e]=a.useReducer(e=>e+1,0);return e}function ue(){return{data:ce,at:null,else:e=>e}}function le(e,t){return{data:e,at:t,else:t=>e}}function fe(e){if(e instanceof Error){if("TimeoutError"===e.name)return P.Timedout;if("AbortError"===e.name)return P.Supplanted}return P.Errored}const de=a.createContext(new Map);function pe({action:e,renderer:n}){const r=K(),o=a.useContext(de),s=ie(),c=a.useMemo(()=>{const t=o.get(e);if(t)return t;const n={state:new F,listeners:new Set};return o.set(e,n),n},[e,o]);a.useLayoutEffect(()=>{function t(e){c.state.hydrate({value:e}),c.listeners.forEach(e=>e())}return c.listeners.add(s),r.on(e,t),()=>{c.listeners.delete(s),r.off(e,t)}},[e,r,c]);const i=c.state.model?.value;return t.G.isNullable(i)?null:n(i,c.state.inspect.value)}function he(e,t){return new Promise((n,r)=>{if(t?.aborted)return void r(new x);const o=setTimeout(n,e);t?.addEventListener("abort",()=>{clearTimeout(o),r(new x)},{once:!0})})}async function me(e,t,n){if(t?.aborted)throw new x;for(;;){if(await n())return;await he(e,t)}}function be(e){return e?Boolean(e&&"symbol"!=typeof e):Symbol(`pk.${Date.now()}.${crypto.randomUUID()}`)}function ye(e){return{get(t){try{const n=e.get(t);if(null===n)return ue();const r=JSON.parse(n);return le(r.data,Temporal.Instant.from(r.at))}catch{return ue()}},set(t,n){if(n.data===ce||null===n.at)return!1;try{return e.set(t,JSON.stringify({data:n.data,at:n.at.toString()})),!0}catch{return!1}},remove(t){e.remove(t)},clear(){e.clear()}}}const ve=Object.freeze(Object.defineProperty({__proto__:null,pk:be,poll:me,sleep:he,store:ye,unset:ce,"ζ":he,"κ":be,"π":me,"σ":ye},Symbol.toStringTag,{value:"Module"})),ge=new WeakMap;e.AbortError=x,e.Action=(e,t=m.Unicast)=>{const n=t===m.Broadcast?Symbol(i(e)):t===m.Multicast?Symbol(u(e)):Symbol(c(e)),r=function(e){return{[f.Action]:n,[f.Payload]:void 0,[f.Channel]:e,channel:e}};return Object.defineProperty(r,f.Action,{value:n,enumerable:!1}),Object.defineProperty(r,f.Payload,{value:void 0,enumerable:!1}),t===m.Broadcast&&Object.defineProperty(r,f.Broadcast,{value:!0,enumerable:!1}),t===m.Multicast&&Object.defineProperty(r,f.Multicast,{value:!0,enumerable:!1}),r},e.Boundary=function({children:e}){return r.jsx(Q,{children:r.jsx(ee,{children:r.jsx(Y,{children:e})})})},e.Distribution=m,e.Lifecycle=h,e.Op=A,e.Operation=A,e.Reason=P,e.Resource=function(e){return{run:(t,n)=>e(t,n).then(t=>(ge.set(e,{data:t,at:Temporal.Now.instant()}),t)),get data(){const t=ge.get(e);return void 0===t?ce:t.data},get at(){return ge.get(e)?.at??null},seed(t,n){ge.set(e,{data:t,at:n})}}},e.State=F,e.TimeoutError=E,e.With={Update:e=>(t,n)=>{t.actions.produce(t=>{t.model[e]=n})},Invert:e=>t=>{t.actions.produce(t=>{t.model[e]=!t.model[e]})}},e.annotate=function(e,t=A.Update){return $.annotate(t,e)},e.useActions=function(...e){const n=t.G.isUndefined(e[0])||t.G.isFunction(e[0])?{}:e[0],r=t.G.isFunction(e[0])?e[0]:e[1]??(()=>({})),o=K(),s=ne(),c=a.useContext(X),i=ie(),u=a.useRef(!1),l=a.useRef(null),f=a.useRef(new F);u.current||(u.current=!0,l.current=f.current.hydrate(n));const[d,h]=a.useState(()=>f.current.model),m=function(e){const t=a.useRef(e);return a.useLayoutEffect(()=>{t.current=e},[e]),a.useMemo(()=>{return n=t,Object.keys(e).reduce((e,t)=>(Object.defineProperty(e,t,{get:()=>n.current[t],enumerable:!0}),e),{});var n},[e])}(r()),y=a.useMemo(()=>new q,[]),O=a.useRef({handlers:new Map});O.current.handlers=new Map;const P=function(){const e=a.useRef(new Set),t=a.useRef(new Set);return a.useMemo(()=>({broadcast:e.current,multicast:t.current}),[])}(),x=a.useRef(b.Mounting),E=a.useRef(new Set),M=a.useRef(0),C=a.useCallback((e,t,n)=>{const r=new AbortController,a={controller:r,action:e,payload:t};return c.add(a),E.current.add(a),{model:f.current.model,get phase(){return x.current},task:a,data:m,tasks:c,actions:{produce(e){if(r.signal.aborted)return;const t=f.current.produce(t=>{e({model:t,inspect:f.current.inspect})});h(f.current.model),n.processes.add(t),l.current&&(n.processes.add(l.current),l.current=null)},dispatch(e,t){if(r.signal.aborted)return Promise.resolve();const n=v(e),a=j(e)?e.channel:void 0;if(S(e)){const e=re(s,n);return e?se(e.emitter,n,t,a):Promise.resolve()}return se(g(e)?o:y,n,t,a)},annotate:(e,t=A.Update)=>f.current.annotate(t,e),async resolution(e){if(r.signal.aborted)return null;const t=v(e),n=S(e)?re(s,t)?.emitter??null:o;if(!n)return null;if(void 0===n.getCached(t))return null;const a=w(e),c="unknown"!==a?a[0].toLowerCase()+a.slice(1):null;if(c){const e=f.current.inspect[c];e?.pending?.()&&await new Promise((t,n)=>{if(r.signal.aborted)return void n(r.signal.reason);const o=()=>n(r.signal.reason);r.signal.addEventListener("abort",o,{once:!0}),e.settled().then(()=>{r.signal.removeEventListener("abort",o),t()})})}return n.getCached(t)??null},peek(e){if(r.signal.aborted)return null;const t=v(e),n=S(e)?re(s,t)?.emitter??null:o;return n?n.getCached(t)??null:null}}}},[d]);a.useLayoutEffect(()=>{function e(e,n,r){return function(s,a){const u=r();if(a===oe&&t.G.isNotNullable(u))return;if(t.G.isNotNullable(a)&&a!==oe&&t.G.isNotNullable(u)&&!function(e,t){for(const n of Object.keys(e))if(t[n]!==e[n])return!1;return!0}(a,u))return;const l={processes:new Set},d=Promise.withResolvers(),h=C(e,s,l);function m(t){const n=ae(O.current.handlers,"Error"),r=null!==n,s={reason:fe(t),error:(a=t,a instanceof Error?a:new Error(String(a))),action:w(e),handled:r,tasks:c};var a;o.fire(p,s),r&&n&&y.emit(n,s)}function b(){for(const e of c)if(e===h.task){c.delete(e),E.current.delete(e);break}l.processes.forEach(e=>f.current.prune(e)),l.processes.size>0&&i(),d.resolve()}let v;try{v=n(h,s)}catch(g){return m(g),void b()}if(!function(e){if(!e||"object"!=typeof e)return!1;const t=Object.prototype.toString.call(e);return"[object Generator]"===t||"[object AsyncGenerator]"===t}(v))return Promise.resolve(v).catch(m).finally(b),d.promise;(async()=>{for await(const e of v);})().catch(m).finally(b)}}M.current++;const n=new Set;return O.current.handlers.forEach((t,r)=>{for(const{getChannel:a,handler:c}of t){const t=e(r,c,a);if(S(r)){if(s)for(const e of s.values()){const o=e.emitter;o.on(r,t),n.add(()=>o.off(r,t))}y.on(r,t),P.multicast.add(r),n.add(()=>y.off(r,t))}else g(r)?(o.on(r,t),y.on(r,t),P.broadcast.add(r),n.add(()=>{o.off(r,t),y.off(r,t)})):(y.on(r,t),n.add(()=>y.off(r,t)))}}),()=>{const e=++M.current,t=new Set(n);queueMicrotask(()=>{if(M.current!==e){for(const e of t)e();return}for(const e of E.current)e.controller.abort(),c.delete(e);E.current.clear(),x.current=b.Unmounting;const n=ae(O.current.handlers,"Unmount");n&&y.emit(n),x.current=b.Unmounted;for(const e of t)e()})}},[y]),function({unicast:e,broadcast:n,dispatchers:r,scope:o,phase:s,data:c,handlers:i}){const u=a.useRef(null);a.useLayoutEffect(()=>{if(s.current===b.Mounted)return;s.current=b.Mounting;const a=ae(i,"Mount");a&&e.emit(a),r.broadcast.forEach(r=>{const o=n.getCached(r);t.G.isNullable(o)||e.emit(r,o,oe)}),o&&r.multicast.forEach(n=>{for(const r of o.values()){const o=r.emitter.getCached(n);t.G.isNullable(o)||e.emit(n,o,oe)}}),s.current=b.Mounted},[]),a.useLayoutEffect(()=>{if(t.G.isNotNullable(u.current)){const n=function(e,t){return Object.keys(t).reduce((n,r)=>e[r]!==t[r]?{...n,[r]:t[r]}:n,{})}(u.current,c);if(t.A.isNotEmpty(Object.keys(n))){const t=ae(i,"Update");t&&e.emit(t,n)}}u.current=c},[c,e])}({unicast:y,broadcast:o,dispatchers:P,scope:s,phase:x,data:r(),handlers:O.current.handlers});const G=a.useMemo(()=>[d,{dispatch(e,t){const n=v(e),r=j(e)?e.channel:void 0;if(S(e)){const e=re(s,n);return e?se(e.emitter,n,t,r):Promise.resolve()}return se(g(e)?o:y,n,t,r)},get inspect(){return f.current.inspect},stream:(e,t)=>a.createElement(pe,{action:v(e),renderer:t})}],[d,y]);return G.useAction=(e,t)=>{!function(e,t,n){const r=a.useRef(n);a.useLayoutEffect(()=>{r.current=n});const o=a.useRef(t);a.useLayoutEffect(()=>{o.current=t});const s=a.useCallback((e,t)=>r.current(e,t),[]),c=a.useCallback(()=>j(o.current)?o.current.channel:void 0,[]),i=v(t),u=e.current.handlers.get(i)??new Set;0===u.size&&e.current.handlers.set(i,u),u.add({getChannel:c,handler:s})}(O,e,t)},G},e.useMode=function(){const e=a.useContext(Z);return a.useMemo(()=>({read:()=>e.current,update(t){e.current=t}}),[e])},e.useResource=function(e){return a.useMemo(()=>{const t=(t,n)=>e.run(t??void 0,n??{});return Object.defineProperties(t,{if:{value:(t,n,r)=>{const o=e.data,s=e.at;if(o!==ce&&null!==s){const e=Temporal.Now.instant().since(s),n=Temporal.Duration.from(t.over);if(Temporal.Duration.compare(e,n)<=0)return Promise.resolve(o)}return e.run(n??void 0,r??{})},enumerable:!0},else:{value:function(n){if(null!==(r=n)&&"object"==typeof r&&"data"in r&&"at"in r&&"else"in r)return e.data===ce&&n.data!==ce&&null!==n.at&&e.seed(n.data,n.at),t;var r;const o=e.data;return o===ce?n:o},enumerable:!0},snapshot:{value:()=>{const t=e.data,n=e.at;return t===ce||null===n?ue():le(t,n)},enumerable:!0},data:{get:()=>e.data,enumerable:!0},at:{get:()=>e.at,enumerable:!0}}),t},[e])},e.utils=ve,e.withScope=function(e,t){const n=`Scoped${t.displayName||t.name||"Component"}`,o=v(e);return{[n](e){const n=ne(),s=a.useMemo(()=>({action:o,emitter:new J}),[]),c=a.useMemo(()=>{const e=new Map(n??[]);return e.set(o,s),e},[n,s]);return r.jsx(te.Provider,{value:c,children:r.jsx(t,{...e})})}}[n]},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})},"object"==typeof exports&&"undefined"!=typeof module?factory(exports,require("@mobily/ts-belt"),require("immer"),require("react/jsx-runtime"),require("react")):"function"==typeof define&&define.amd?define(["exports","@mobily/ts-belt","immer","react/jsx-runtime","react"],factory):factory((global="undefined"!=typeof globalThis?globalThis:global||self).MarchHare={},global.TsBelt,global.Immer,global.jsxRuntime,global.React);
|
|
@@ -8,9 +8,10 @@ export { withScope } from './boundary/components/scope/index.tsx';
|
|
|
8
8
|
export { useMode } from './boundary/components/mode/index.tsx';
|
|
9
9
|
export type { ModeHandle } from './boundary/components/mode/index.tsx';
|
|
10
10
|
export { useActions, With } from './hooks/index.ts';
|
|
11
|
-
export { Resource } from './resource/index.ts';
|
|
12
|
-
export type { ResourceHandle, ResourceFetcher,
|
|
11
|
+
export { Resource, useResource } from './resource/index.ts';
|
|
12
|
+
export type { ResourceHandle, ResourceFetcher, BoundResourceHandle, IfOptions, } from './resource/index.ts';
|
|
13
13
|
export * as utils from './utils/index.ts';
|
|
14
|
+
export type { Stored, Unset, Adapter, Store } from './utils/index.ts';
|
|
14
15
|
export type { Box } from 'immertation';
|
|
15
16
|
export type { Fault } from './error/index.ts';
|
|
16
17
|
export type { Pk, Task, Tasks, Handlers } from './types/index.ts';
|
|
@@ -1,99 +1,65 @@
|
|
|
1
|
+
import { ResourceFetcher, ResourceHandle, BoundResourceHandle } from './types.ts';
|
|
2
|
+
export type { IfOptions, ResourceFetcher, ResourceHandle, BoundResourceHandle, } from './types.ts';
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
4
|
+
* Defines a remote resource — declared at module scope and
|
|
5
|
+
* consumed via {@link useResource}.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The fetcher receives the optional `AbortSignal` first and the
|
|
8
|
+
* `params` object second (defaults to `{}`). Resources do **not**
|
|
9
|
+
* carry any callbacks – side-effects (broadcasting, logging,
|
|
10
|
+
* model updates) belong in the `useAction` handler that called
|
|
11
|
+
* `await handle(...)`.
|
|
12
|
+
*
|
|
13
|
+
* Every call fires its own request. The most recent successful
|
|
14
|
+
* payload is cached in a module-level `WeakMap` keyed by the fetcher,
|
|
15
|
+
* so `.if(...)` and `.else(...)` on the bound handle behave
|
|
16
|
+
* consistently across all components that share the same Resource.
|
|
9
17
|
*
|
|
10
18
|
* @example
|
|
11
19
|
* ```ts
|
|
12
|
-
*
|
|
13
|
-
* await user.run.if({ over: "PT5M" });
|
|
14
|
-
* await user.run.if({ over: Temporal.Duration.from({ minutes: 5 }) });
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
export type IfOptions = {
|
|
18
|
-
readonly over: Temporal.DurationLike;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Fetcher signature accepted by {@link Resource}. Receives the
|
|
22
|
-
* call-site `params` object and returns a `Promise` of the response.
|
|
23
|
-
* Side-effects (dispatching broadcasts, analytics, etc.) belong in
|
|
24
|
-
* the calling `useAction` handler, not inside the fetcher.
|
|
25
|
-
*/
|
|
26
|
-
export type ResourceFetcher<T, P extends object = Record<never, never>> = (params: P) => Promise<T>;
|
|
27
|
-
/**
|
|
28
|
-
* Component-bound `run` callable returned by `actions.useResource`. It
|
|
29
|
-
* is invokable like the underlying fetcher (`run(params)`) and also
|
|
30
|
-
* carries an `if` method that triggers the network call only when the
|
|
31
|
-
* cached data is older than the supplied freshness window.
|
|
20
|
+
* import { Resource } from "march-hare";
|
|
32
21
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
(params: P): Promise<T>;
|
|
45
|
-
/**
|
|
46
|
-
* Calls `run(params)` if the most recent successful run resolved
|
|
47
|
-
* longer ago than `options.over`. Otherwise returns the cached data.
|
|
48
|
-
*/
|
|
49
|
-
readonly if: (options: IfOptions, params: P) => Promise<T>;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Module-scope handle returned by {@link Resource}. Pass to
|
|
53
|
-
* `actions.useResource(handle)` inside a component to obtain a
|
|
54
|
-
* `{ run, data, at }` object.
|
|
22
|
+
* // `T` is inferred from the fetcher's return type.
|
|
23
|
+
* export const user = Resource((signal) =>
|
|
24
|
+
* ky.get("user", { signal }).json<User>(),
|
|
25
|
+
* );
|
|
26
|
+
*
|
|
27
|
+
* // Annotate `params` when destructuring so `P` is inferred.
|
|
28
|
+
* export const updateUser = Resource(
|
|
29
|
+
* (signal, { id, body }: { id: number; body: { name: string } }) =>
|
|
30
|
+
* ky.patch(`users/${id}`, { json: body, signal }).json<User>(),
|
|
31
|
+
* );
|
|
32
|
+
* ```
|
|
55
33
|
*/
|
|
56
|
-
export
|
|
57
|
-
readonly key: string;
|
|
58
|
-
/** @internal */
|
|
59
|
-
readonly run: (params: P) => Promise<T>;
|
|
60
|
-
/** Most recent successful data across all param-sets, or `null`. */
|
|
61
|
-
readonly data: T | null;
|
|
62
|
-
/** Instant of the most recent successful run, or `null`. */
|
|
63
|
-
readonly at: Temporal.Instant | null;
|
|
64
|
-
};
|
|
34
|
+
export declare function Resource<T, P extends object = Record<never, never>>(fetcher: ResourceFetcher<T, P>): ResourceHandle<T, P>;
|
|
65
35
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* returns a `Promise<T>`. Resources do **not** carry any callbacks
|
|
72
|
-
* – any side-effects the caller wants on success or failure
|
|
73
|
-
* (broadcasting, logging, model updates) belong in the `useAction`
|
|
74
|
-
* handler that called `await user.run(...)`.
|
|
36
|
+
* Binds a module-scope {@link ResourceHandle} to the component, returning
|
|
37
|
+
* the fetch callable with `.if`, `.else`, `.snapshot`, `.data`, and `.at`
|
|
38
|
+
* attached. The hook is standalone – call it *before* `useActions`
|
|
39
|
+
* when you want to seed the initial model from the cache via
|
|
40
|
+
* `.else(fallback)`.
|
|
75
41
|
*
|
|
76
|
-
* `
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* share one network request.
|
|
81
|
-
*
|
|
82
|
-
* Each call to `run()` always hits the network; `data` and `at`
|
|
83
|
-
* are read-only snapshots of the most recent successful payload and
|
|
84
|
-
* the instant it resolved – not a memoised result.
|
|
42
|
+
* Pass `context.task.controller.signal` as the first argument to thread
|
|
43
|
+
* cancellation from the surrounding action handler through to the
|
|
44
|
+
* fetcher. For parameterised resources, pass `null` as the first arg
|
|
45
|
+
* when you have params but no signal.
|
|
85
46
|
*
|
|
86
47
|
* @example
|
|
87
48
|
* ```ts
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* ({ cursor }) =>
|
|
93
|
-
* http
|
|
94
|
-
* .get("feed", { searchParams: { cursor: cursor ?? "" } })
|
|
95
|
-
* .json<Page<Item>>(),
|
|
49
|
+
* const cat = useResource(resources.cat);
|
|
50
|
+
* const actions = useActions<Model, typeof Actions, Data>(
|
|
51
|
+
* { cat: cat.else(store.get(Snapshots.Cat)).else(null) },
|
|
52
|
+
* () => ({ index, router }),
|
|
96
53
|
* );
|
|
54
|
+
*
|
|
55
|
+
* actions.useAction(Actions.Mount, async (context) => {
|
|
56
|
+
* const fresh = await cat.if(
|
|
57
|
+
* { over: { minutes: 5 } },
|
|
58
|
+
* context.task.controller.signal,
|
|
59
|
+
* );
|
|
60
|
+
* store.set(Snapshots.Cat, cat.snapshot());
|
|
61
|
+
* context.actions.produce(({ model }) => void (model.cat = fresh));
|
|
62
|
+
* });
|
|
97
63
|
* ```
|
|
98
64
|
*/
|
|
99
|
-
export declare function
|
|
65
|
+
export declare function useResource<T, P extends object>(resource: ResourceHandle<T, P>): BoundResourceHandle<T, P>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Stored, Unset } from '../utils/index.ts';
|
|
2
|
+
export type { Unset } from '../utils/index.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Options accepted by `.if(...)` on a bound resource handle.
|
|
5
|
+
*
|
|
6
|
+
* - `over` – a `Temporal.Duration`, a `DurationLike` object
|
|
7
|
+
* (e.g. `{ minutes: 5 }`), or an ISO 8601 duration string (`"PT5M"`).
|
|
8
|
+
* If the most recent successful run resolved longer ago than this
|
|
9
|
+
* window, the underlying fetcher is called. Otherwise the cached
|
|
10
|
+
* data is returned without hitting the network.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* await user.if({ over: { minutes: 5 } });
|
|
15
|
+
* await user.if({ over: "PT5M" });
|
|
16
|
+
* await user.if({ over: Temporal.Duration.from({ minutes: 5 }) });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export type IfOptions = {
|
|
20
|
+
readonly over: Temporal.DurationLike;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Fetcher signature accepted by `Resource`. Receives the optional
|
|
24
|
+
* `AbortSignal` first (threaded from the action handler's
|
|
25
|
+
* `context.task.controller.signal`) and the call-site `params` second.
|
|
26
|
+
* Side-effects (dispatching broadcasts, analytics, etc.) belong in the
|
|
27
|
+
* calling `useAction` handler, not inside the fetcher.
|
|
28
|
+
*/
|
|
29
|
+
export type ResourceFetcher<T, P extends object = Record<never, never>> = (signal: AbortSignal | undefined, params: P) => Promise<T>;
|
|
30
|
+
/**
|
|
31
|
+
* Module-scope handle returned by `Resource`. Pass to `useResource` to
|
|
32
|
+
* obtain the bound, component-scoped callable.
|
|
33
|
+
*
|
|
34
|
+
* Every call to the underlying fetcher fires its own request. The most
|
|
35
|
+
* recent successful response is cached in a module-level `WeakMap`
|
|
36
|
+
* keyed by the fetcher itself, so `.if(...)` and `.else(...)` on the
|
|
37
|
+
* bound handle have something to read from.
|
|
38
|
+
*/
|
|
39
|
+
export type ResourceHandle<T, P extends object = Record<never, never>> = {
|
|
40
|
+
/** @internal */
|
|
41
|
+
readonly run: (signal: AbortSignal | undefined, params: P) => Promise<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Most recent successfully-resolved payload, or the shared `unset`
|
|
44
|
+
* sentinel if no successful run has happened yet.
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
readonly data: T | Unset;
|
|
48
|
+
/** @internal */
|
|
49
|
+
readonly at: Temporal.Instant | null;
|
|
50
|
+
/**
|
|
51
|
+
* Populates the cache slot with `data` and `at` without invoking the
|
|
52
|
+
* fetcher. Used by the bound handle's `.else(stored)` overload to
|
|
53
|
+
* hydrate the cache from a {@link Stored} fallback (typically the
|
|
54
|
+
* return value of `Store.get(key)` after a page reload).
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
readonly seed: (data: T, at: Temporal.Instant) => void;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Component-bound handle returned by `useResource`. The handle is itself
|
|
61
|
+
* the fetch callable — `await user(signal?, params?)` triggers a
|
|
62
|
+
* request — with attached read accessors and methods:
|
|
63
|
+
*
|
|
64
|
+
* - `.if({ over }, signal?, params?)` — fetch only if the cached
|
|
65
|
+
* payload is older than the supplied freshness window; otherwise
|
|
66
|
+
* return the cached payload synchronously.
|
|
67
|
+
* - `.else(fallback)` — synchronous read of the cached payload,
|
|
68
|
+
* falling back to the supplied default when nothing has resolved
|
|
69
|
+
* successfully yet. Accepts either a value (terminal, returns
|
|
70
|
+
* `T | U`) or a {@link Stored} (chainable, seeds the cache from the
|
|
71
|
+
* Stored's data/at when the cache is empty and returns the same bound
|
|
72
|
+
* handle for further chaining).
|
|
73
|
+
* - `.snapshot()` — returns a {@link Stored} wrapping the current
|
|
74
|
+
* cache state, symmetric with `Store.get(key)`. Pass straight to
|
|
75
|
+
* `Store.set(key, ...)` to persist the latest successful payload.
|
|
76
|
+
* - `.data` and `.at` — the underlying cache fields. Reading
|
|
77
|
+
* `.snapshot()` is usually clearer.
|
|
78
|
+
*
|
|
79
|
+
* Call signature: `(signal?)` for resources with no params, or
|
|
80
|
+
* `(signal: AbortSignal | null, params: P)` for parameterised
|
|
81
|
+
* resources — pass `null` as the first argument when you have
|
|
82
|
+
* params but no signal to thread.
|
|
83
|
+
*/
|
|
84
|
+
export type BoundResourceHandle<T, P extends object> = [keyof P] extends [never] ? {
|
|
85
|
+
(signal?: AbortSignal): Promise<T>;
|
|
86
|
+
/**
|
|
87
|
+
* Calls the underlying fetcher if the most recent successful run
|
|
88
|
+
* resolved longer ago than `options.over`. Otherwise returns the
|
|
89
|
+
* cached data without hitting the network.
|
|
90
|
+
*/
|
|
91
|
+
readonly if: (options: IfOptions, signal?: AbortSignal) => Promise<T>;
|
|
92
|
+
/**
|
|
93
|
+
* Overloaded fallback accessor.
|
|
94
|
+
*
|
|
95
|
+
* - `(fallback: U)` terminal — returns the cached payload, or
|
|
96
|
+
* `fallback` when nothing has resolved yet. Cached `null` values
|
|
97
|
+
* are returned verbatim.
|
|
98
|
+
* - `(stored: Stored<T>)` chainable — if the cache is empty
|
|
99
|
+
* and the Stored carries data and a timestamp, seeds the cache
|
|
100
|
+
* from it before returning the same bound handle so further
|
|
101
|
+
* `.else(...)` calls compose. Used to hydrate the cache on first
|
|
102
|
+
* render after a page reload, allowing `.if({ over })` to
|
|
103
|
+
* short-circuit on the persisted timestamp.
|
|
104
|
+
*/
|
|
105
|
+
readonly else: {
|
|
106
|
+
(stored: Stored<T>): BoundResourceHandle<T, P>;
|
|
107
|
+
<U>(fallback: U): T | U;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Snapshot of the current cache state in the shared {@link Stored}
|
|
111
|
+
* shape. Empty (`data === unset`, `at === null`) until a fetcher
|
|
112
|
+
* resolves; otherwise carries the most recent payload and the
|
|
113
|
+
* instant it resolved. Pass directly to `Store.set(key, ...)`.
|
|
114
|
+
*/
|
|
115
|
+
readonly snapshot: () => Stored<T>;
|
|
116
|
+
/** Direct read of the cache payload. Prefer `.snapshot()`. */
|
|
117
|
+
readonly data: T | Unset;
|
|
118
|
+
/** Instant the cache was last populated, or `null` if empty. */
|
|
119
|
+
readonly at: Temporal.Instant | null;
|
|
120
|
+
} : {
|
|
121
|
+
(signal: AbortSignal | null, params: P): Promise<T>;
|
|
122
|
+
/**
|
|
123
|
+
* Calls the underlying fetcher if the most recent successful run
|
|
124
|
+
* resolved longer ago than `options.over`. Otherwise returns the
|
|
125
|
+
* cached data without hitting the network.
|
|
126
|
+
*/
|
|
127
|
+
readonly if: (options: IfOptions, signal: AbortSignal | null, params: P) => Promise<T>;
|
|
128
|
+
/**
|
|
129
|
+
* Overloaded fallback accessor.
|
|
130
|
+
*
|
|
131
|
+
* - `(fallback: U)` terminal — returns the cached payload, or
|
|
132
|
+
* `fallback` when nothing has resolved yet.
|
|
133
|
+
* - `(stored: Stored<T>)` chainable — if the cache is empty
|
|
134
|
+
* and the Stored carries data and a timestamp, seeds the cache
|
|
135
|
+
* from it before returning the same bound handle.
|
|
136
|
+
*/
|
|
137
|
+
readonly else: {
|
|
138
|
+
(stored: Stored<T>): BoundResourceHandle<T, P>;
|
|
139
|
+
<U>(fallback: U): T | U;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Snapshot of the current cache state in the shared {@link Stored}
|
|
143
|
+
* shape. Pass directly to `Store.set(key, ...)`.
|
|
144
|
+
*/
|
|
145
|
+
readonly snapshot: () => Stored<T>;
|
|
146
|
+
/** Direct read of the cache payload. Prefer `.snapshot()`. */
|
|
147
|
+
readonly data: T | Unset;
|
|
148
|
+
/** Instant the cache was last populated, or `null` if empty. */
|
|
149
|
+
readonly at: Temporal.Instant | null;
|
|
150
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { unset } from '../utils/index.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Module-level cache shared by every `Resource` declaration. Keyed by
|
|
4
|
+
* the fetcher function itself so each module-scope declaration gets a
|
|
5
|
+
* distinct slot — entries are garbage-collected when the fetcher
|
|
6
|
+
* reference is dropped.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare const cache: WeakMap<object, {
|
|
11
|
+
data: unknown;
|
|
12
|
+
at: Temporal.Instant;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Re-export of the shared `unset` sentinel from {@link "../utils/index.ts"}.
|
|
16
|
+
* Kept under `config` for back-compatibility with existing imports in this
|
|
17
|
+
* module's siblings; new code should import `unset` directly from `utils`.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export declare const config: {
|
|
22
|
+
readonly unset: typeof unset;
|
|
23
|
+
};
|
|
@@ -690,29 +690,4 @@ export type UseActions<M extends Model | void, AC extends Actions | void, D exte
|
|
|
690
690
|
* ```
|
|
691
691
|
*/
|
|
692
692
|
useAction<A extends ActionId | HandlerPayload | ChanneledAction>(action: A, handler: (context: HandlerContext<M, AC, D>, ...args: [Payload<A>] extends [never] ? [] : [payload: Payload<A>]) => void | Promise<void> | AsyncGenerator | Generator): void;
|
|
693
|
-
/**
|
|
694
|
-
* Connects a {@link Resource} declared at module scope to this component.
|
|
695
|
-
* Returns a frozen `{ run, data, at }` object – `run` triggers a
|
|
696
|
-
* fresh network call (concurrent calls share the in-flight promise),
|
|
697
|
-
* while `data` and `at` are read-only snapshots of the most recent
|
|
698
|
-
* successful payload and the instant it resolved.
|
|
699
|
-
*
|
|
700
|
-
* `data` and `at` are non-reactive — reading them does not
|
|
701
|
-
* subscribe the component to updates. Drive UI from the model.
|
|
702
|
-
*
|
|
703
|
-
* @example
|
|
704
|
-
* ```ts
|
|
705
|
-
* const user = actions.useResource(resources.user);
|
|
706
|
-
*
|
|
707
|
-
* actions.useAction(Actions.Mount, async (context) => {
|
|
708
|
-
* const data = await user.run();
|
|
709
|
-
* context.actions.produce(({ model }) => { model.user = data; });
|
|
710
|
-
* });
|
|
711
|
-
* ```
|
|
712
|
-
*/
|
|
713
|
-
useResource<T, P extends object>(resource: import('../resource/index.ts').ResourceHandle<T, P>): Readonly<{
|
|
714
|
-
run: import('../resource/index.ts').BoundRun<T, P>;
|
|
715
|
-
data: T | null;
|
|
716
|
-
at: Temporal.Instant | null;
|
|
717
|
-
}>;
|
|
718
693
|
};
|
|
@@ -1,42 +1,92 @@
|
|
|
1
1
|
import { Pk } from '../types/index.ts';
|
|
2
|
+
import { Adapter, Store } from './types.ts';
|
|
3
|
+
export { unset } from './utils.ts';
|
|
4
|
+
export type { Adapter, Encoded, Store, Stored, Unset } from './types.ts';
|
|
2
5
|
/**
|
|
3
|
-
* Returns a promise that resolves after the specified number of
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
+
* Returns a promise that resolves after the specified number of
|
|
7
|
+
* milliseconds, or rejects with an {@link AbortError} when the signal is
|
|
8
|
+
* aborted. Use to inject a cancellable delay into an action handler.
|
|
6
9
|
*
|
|
7
|
-
* @param ms
|
|
8
|
-
* @param signal AbortSignal
|
|
9
|
-
*
|
|
10
|
+
* @param ms How long to wait before resolving.
|
|
11
|
+
* @param signal Optional {@link AbortSignal} that cancels the sleep early.
|
|
12
|
+
* Pass `context.task.controller.signal` to tie the wait to
|
|
13
|
+
* the lifetime of the current action.
|
|
14
|
+
* @returns A promise that resolves after `ms` milliseconds or rejects with
|
|
15
|
+
* an {@link AbortError} if `signal` aborts first.
|
|
10
16
|
*/
|
|
11
17
|
export declare function sleep(ms: number, signal: AbortSignal | undefined): Promise<void>;
|
|
12
|
-
/** Shorthand alias for {@link sleep}. */
|
|
13
|
-
export declare const ζ: typeof sleep;
|
|
14
18
|
/**
|
|
15
|
-
* Repeatedly calls a function at a fixed interval until it returns `true`
|
|
16
|
-
* the signal is aborted. The function is invoked immediately on the
|
|
17
|
-
* iteration, then after each interval.
|
|
18
|
-
*
|
|
19
|
-
* @param ms
|
|
20
|
-
* @param signal Optional AbortSignal
|
|
21
|
-
*
|
|
22
|
-
* @
|
|
23
|
-
*
|
|
19
|
+
* Repeatedly calls a function at a fixed interval until it returns `true`
|
|
20
|
+
* or the signal is aborted. The function is invoked immediately on the
|
|
21
|
+
* first iteration, then after each interval.
|
|
22
|
+
*
|
|
23
|
+
* @param ms Interval in milliseconds between invocations of `fn`.
|
|
24
|
+
* @param signal Optional {@link AbortSignal} that cancels polling early.
|
|
25
|
+
* Aborts propagate as an {@link AbortError} rejection.
|
|
26
|
+
* @param fn Predicate invoked each iteration. Return `true` to stop
|
|
27
|
+
* polling, `false` to schedule another invocation after `ms`.
|
|
28
|
+
* May be sync or async.
|
|
29
|
+
* @returns A promise that resolves when `fn` returns `true`, or rejects
|
|
30
|
+
* with an {@link AbortError} if `signal` aborts first.
|
|
24
31
|
*/
|
|
25
32
|
export declare function poll(ms: number, signal: AbortSignal | undefined, fn: () => boolean | Promise<boolean>): Promise<void>;
|
|
26
|
-
/** Shorthand alias for {@link poll}. */
|
|
27
|
-
export declare const π: typeof poll;
|
|
28
33
|
/**
|
|
29
34
|
* Generates a unique primary key.
|
|
35
|
+
*
|
|
30
36
|
* @returns A new unique symbol representing the primary key.
|
|
31
37
|
*/
|
|
32
38
|
export declare function pk(): symbol;
|
|
33
39
|
/**
|
|
34
|
-
* Checks if the provided ID is a valid primary key.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
40
|
+
* Checks if the provided ID is a valid primary key. A valid primary key
|
|
41
|
+
* is any value that is not a symbol.
|
|
42
|
+
*
|
|
43
|
+
* @template T The model type the key identifies.
|
|
37
44
|
* @param id The primary key to validate.
|
|
38
|
-
* @returns `true` if
|
|
45
|
+
* @returns `true` if `id` is a non-symbol value, `false` otherwise.
|
|
39
46
|
*/
|
|
40
47
|
export declare function pk<T>(id: Pk<T>): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Wraps a synchronous {@link Adapter} into a {@link Store} that traffics
|
|
50
|
+
* in {@link Stored} values. Storage entries serialise as
|
|
51
|
+
* {@link Encoded}`<T>` so the `Temporal.Instant` timestamp survives the
|
|
52
|
+
* string round-trip and `BoundResourceHandle.if({ over })` can
|
|
53
|
+
* short-circuit on the persisted timestamp after a reload.
|
|
54
|
+
*
|
|
55
|
+
* @param adapter Backend implementation providing raw string `get`/`set`/
|
|
56
|
+
* `remove`/`clear`. The Store layers JSON encoding and
|
|
57
|
+
* timestamp serialisation on top.
|
|
58
|
+
* @returns A {@link Store} bound to `adapter`. Reads return {@link Stored}
|
|
59
|
+
* envelopes; writes accept Stored envelopes and return `true`
|
|
60
|
+
* when the entry landed in the adapter.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* const store = utils.store({
|
|
65
|
+
* get: (key) => localStorage.getItem(key),
|
|
66
|
+
* set: (key, value) => localStorage.setItem(key, value),
|
|
67
|
+
* remove: (key) => localStorage.removeItem(key),
|
|
68
|
+
* clear: () => localStorage.clear(),
|
|
69
|
+
* });
|
|
70
|
+
*
|
|
71
|
+
* // Read into a Resource fallback chain.
|
|
72
|
+
* { cat: get.cat.else(store.get(Snapshots.Cat)).else(null) }
|
|
73
|
+
*
|
|
74
|
+
* // Write the latest cached value back to storage.
|
|
75
|
+
* store.set(Snapshots.Cat, get.cat.snapshot());
|
|
76
|
+
*
|
|
77
|
+
* // Drop a snapshot on sign-out, cache invalidation, etc.
|
|
78
|
+
* store.remove(Snapshots.Cat);
|
|
79
|
+
*
|
|
80
|
+
* // Or wipe the whole backing store (scope is the adapter's call).
|
|
81
|
+
* store.clear();
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function store(adapter: Adapter): Store;
|
|
85
|
+
/** Shorthand alias for {@link sleep}. */
|
|
86
|
+
export declare const ζ: typeof sleep;
|
|
87
|
+
/** Shorthand alias for {@link poll}. */
|
|
88
|
+
export declare const π: typeof poll;
|
|
41
89
|
/** Shorthand alias for {@link pk}. */
|
|
42
90
|
export declare const κ: typeof pk;
|
|
91
|
+
/** Shorthand alias for {@link store}. */
|
|
92
|
+
export declare const σ: typeof store;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { unset } from './utils.ts';
|
|
2
|
+
/** Nominal type of the {@link unset} sentinel. */
|
|
3
|
+
export type Unset = typeof unset;
|
|
4
|
+
/**
|
|
5
|
+
* On-disk JSON shape of a {@link Stored} envelope. The Store wrapper
|
|
6
|
+
* encodes a populated Stored as `{ data, at: at.toString() }` so the
|
|
7
|
+
* `Temporal.Instant` survives the string round-trip, and decodes via
|
|
8
|
+
* `Temporal.Instant.from(...)` on read. Adapters never see this shape
|
|
9
|
+
* directly — they shuttle the already-stringified JSON.
|
|
10
|
+
*
|
|
11
|
+
* @template T The payload type carried by the matching {@link Stored}.
|
|
12
|
+
*/
|
|
13
|
+
export type Encoded<T> = {
|
|
14
|
+
readonly data: T;
|
|
15
|
+
readonly at: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Common shape for a possibly-present value with a timestamp. Produced by
|
|
19
|
+
* `BoundResourceHandle.snapshot()` (from the in-memory cache) and by
|
|
20
|
+
* `Store.get(key)` (from persistent storage). Both feed into the bound
|
|
21
|
+
* handle's overloaded `.else(...)`, which seeds the cache when given a
|
|
22
|
+
* Stored that carries data and a timestamp.
|
|
23
|
+
*
|
|
24
|
+
* @template T The payload type when present.
|
|
25
|
+
*/
|
|
26
|
+
export type Stored<T> = {
|
|
27
|
+
/** The payload, or {@link unset} when nothing is recorded. */
|
|
28
|
+
readonly data: T | Unset;
|
|
29
|
+
/** When the payload was recorded, or `null` when nothing is recorded. */
|
|
30
|
+
readonly at: Temporal.Instant | null;
|
|
31
|
+
/**
|
|
32
|
+
* Returns {@link data} when present, otherwise the supplied fallback.
|
|
33
|
+
* Symmetric with `BoundResourceHandle.else(...)`'s terminal form.
|
|
34
|
+
*/
|
|
35
|
+
readonly else: <U>(fallback: U) => T | U;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Adapter contract for synchronous key/value storage. Implement once per
|
|
39
|
+
* backend (localStorage, MMKV on React Native, chrome.storage with a sync
|
|
40
|
+
* facade, etc.) and pass to `store`. The adapter shuttles raw strings;
|
|
41
|
+
* JSON encoding and `Temporal.Instant` round-tripping happen inside the
|
|
42
|
+
* Store wrapper, so adapters stay trivial.
|
|
43
|
+
*/
|
|
44
|
+
export type Adapter = {
|
|
45
|
+
/**
|
|
46
|
+
* Return the raw string stored under `key`, or `null` when no entry
|
|
47
|
+
* exists. The Store wrapper handles JSON parsing and `Temporal.Instant`
|
|
48
|
+
* round-tripping, so this stays a plain string getter. Treat any
|
|
49
|
+
* read-time error (decryption, IPC, etc.) as "not found" and return
|
|
50
|
+
* `null` — the Store falls through to its next fallback rather than
|
|
51
|
+
* crashing the render.
|
|
52
|
+
*/
|
|
53
|
+
readonly get: (key: string) => string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Persist the raw string `value` under `key`. The Store guarantees
|
|
56
|
+
* `value` is a JSON-encoded `{ data, at }` envelope produced by a
|
|
57
|
+
* resolved snapshot — never a placeholder. Throwing is fine on quota,
|
|
58
|
+
* private mode, sandboxed iframes, etc.; the Store catches and
|
|
59
|
+
* swallows so a write failure can't poison an already-resolved fetch.
|
|
60
|
+
*/
|
|
61
|
+
readonly set: (key: string, value: string) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Drop the entry at `key`. Idempotent — calling `remove` for a key
|
|
64
|
+
* that isn't present must not throw.
|
|
65
|
+
*/
|
|
66
|
+
readonly remove: (key: string) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Wipe every entry this adapter can see. On a shared backend such as
|
|
69
|
+
* `localStorage` this means the whole origin — third-party SDK state,
|
|
70
|
+
* dismissed banners, route hints, etc. all go with it. Adapter authors
|
|
71
|
+
* should either delegate to the backend's native clear (accepting that
|
|
72
|
+
* scope) or namespace by key prefix and remove only their own.
|
|
73
|
+
*/
|
|
74
|
+
readonly clear: () => void;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Bound storage instance returned by `store`. Reads return a
|
|
78
|
+
* {@link Stored} handle so the result composes with the Resource bound
|
|
79
|
+
* handle's `.else(...)`; writes accept a Stored and short-circuit on the
|
|
80
|
+
* empty case to avoid persisting placeholder snapshots.
|
|
81
|
+
*/
|
|
82
|
+
export type Store = Pick<Adapter, "remove" | "clear"> & {
|
|
83
|
+
/**
|
|
84
|
+
* Read the entry at `key` as a {@link Stored} envelope. Returns an
|
|
85
|
+
* empty Stored (`data: unset`, `at: null`) when nothing is recorded
|
|
86
|
+
* or when the persisted payload fails to parse — corrupted entries
|
|
87
|
+
* never reach the caller. The result composes directly with a
|
|
88
|
+
* Resource bound handle's `.else(...)` for seeding the cache after
|
|
89
|
+
* a reload.
|
|
90
|
+
*/
|
|
91
|
+
readonly get: <T>(key: string) => Stored<T>;
|
|
92
|
+
/**
|
|
93
|
+
* Persist `value` under `key`. Returns `true` when the entry landed
|
|
94
|
+
* in the adapter, `false` otherwise. A `false` covers two distinct
|
|
95
|
+
* cases: the Stored had no payload yet (`data === unset` or
|
|
96
|
+
* `at === null`), or the adapter threw (quota, private mode, etc.).
|
|
97
|
+
* Callers that care about quota failures should branch on the
|
|
98
|
+
* return; callers writing on every dispatch can safely ignore it.
|
|
99
|
+
*/
|
|
100
|
+
readonly set: <T>(key: string, value: Stored<T>) => boolean;
|
|
101
|
+
};
|
|
@@ -1,5 +1,36 @@
|
|
|
1
|
+
import { Stored } from './types.ts';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
3
|
+
* Sentinel symbol marking "no value present yet". Shared by the Resource
|
|
4
|
+
* cache and by storage handles so callers can distinguish "nothing has been
|
|
5
|
+
* recorded" from "a legitimately stored null".
|
|
6
|
+
*/
|
|
7
|
+
export declare const unset: unique symbol;
|
|
8
|
+
/**
|
|
9
|
+
* Returns a function to force a component re-render. Useful when state is
|
|
10
|
+
* managed externally (e.g., refs) but the UI needs updating.
|
|
11
|
+
*
|
|
12
|
+
* @returns A zero-arg callback that schedules a re-render of the host
|
|
13
|
+
* component when invoked.
|
|
4
14
|
*/
|
|
5
15
|
export declare function useRerender(): () => void;
|
|
16
|
+
/**
|
|
17
|
+
* Constructs a {@link Stored} in the empty state. Internal helper shared
|
|
18
|
+
* by the storage layer and the Resource snapshot accessor.
|
|
19
|
+
*
|
|
20
|
+
* @template T The payload type the resulting Stored would carry if populated.
|
|
21
|
+
* @returns A Stored with `data` set to {@link unset} and `at` set to `null`.
|
|
22
|
+
* Its `.else(fallback)` returns the fallback unchanged.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export declare function empty<T>(): Stored<T>;
|
|
26
|
+
/**
|
|
27
|
+
* Constructs a {@link Stored} wrapping a present payload and timestamp.
|
|
28
|
+
*
|
|
29
|
+
* @template T The payload type carried by the Stored.
|
|
30
|
+
* @param data The payload value to wrap.
|
|
31
|
+
* @param at The instant the payload was recorded — flows through to the
|
|
32
|
+
* Resource cache as the entry's `at` timestamp.
|
|
33
|
+
* @returns A Stored whose `.else(fallback)` returns `data` unchanged.
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export declare function present<T>(data: T, at: Temporal.Instant): Stored<T>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "march-hare",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"packageManager": "yarn@1.22.22",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "vite build",
|
|
33
|
-
"build:example": "vite build --mode example --outDir dist-example --base /
|
|
33
|
+
"build:example": "vite build --mode example --outDir dist-example --base /MarchHare/ && mv dist-example/src/example/index.html dist-example/index.html && rm -rf dist-example/src",
|
|
34
34
|
"dev": "vite",
|
|
35
35
|
"preview": "vite preview",
|
|
36
36
|
"release": "make checks",
|