@mmstack/primitives 22.4.1 → 22.5.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 +105 -3
- package/fesm2022/mmstack-primitives.mjs +700 -14
- package/fesm2022/mmstack-primitives.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mmstack-primitives.d.ts +424 -10
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injector, CreateComputedOptions, Signal, EffectCleanupRegisterFn, CreateEffectOptions, EffectRef, CreateSignalOptions, WritableSignal, Provider, ValueEqualityFn,
|
|
2
|
+
import { InjectionToken, Injector, CreateComputedOptions, Signal, EffectCleanupRegisterFn, CreateEffectOptions, EffectRef, CreateSignalOptions, WritableSignal, Provider, ValueEqualityFn, ResourceStatus, DestroyRef, ElementRef } from '@angular/core';
|
|
3
3
|
import * as _mmstack_primitives from '@mmstack/primitives';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -174,6 +174,56 @@ declare function injectPaused(): Signal<boolean>;
|
|
|
174
174
|
*/
|
|
175
175
|
declare function providePaused(source: Signal<boolean>): Provider;
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* How the catch-up write is scheduled (the "lower priority" of the deferral):
|
|
179
|
+
* - `'afterRender'` (default): after the next render — the urgent update (e.g. the
|
|
180
|
+
* keystroke echo) paints first, the expensive subtree catches up right after.
|
|
181
|
+
* - `'idle'`: `requestIdleCallback` (macrotask fallback) — catch up when the frame has
|
|
182
|
+
* budget; keeps continuous input smooth at the cost of a laggier deferred view.
|
|
183
|
+
* - A function: custom scheduler — call the callback when it's time to catch up and
|
|
184
|
+
* return a canceller (also the test seam).
|
|
185
|
+
*/
|
|
186
|
+
type DeferStrategy = 'afterRender' | 'idle' | ((cb: () => void) => () => void);
|
|
187
|
+
type DeferredValueOptions<T> = {
|
|
188
|
+
readonly strategy?: DeferStrategy;
|
|
189
|
+
/** Equality for the deferred value — an equal catch-up never notifies consumers. */
|
|
190
|
+
readonly equal?: ValueEqualityFn<T>;
|
|
191
|
+
readonly injector?: Injector;
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* The deferred view of a source signal: callable as the lagging value, with `pending`
|
|
195
|
+
* reporting whether a catch-up is still owed (source has moved ahead) — the
|
|
196
|
+
* `useDeferredValue`/`isStale` pair.
|
|
197
|
+
*/
|
|
198
|
+
type DeferredSignal<T> = Signal<T> & {
|
|
199
|
+
/** True while the deferred value is behind the source (a catch-up is scheduled). */
|
|
200
|
+
readonly pending: Signal<boolean>;
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* `useDeferredValue` for signals: returns a signal that HOLDS its previous value when
|
|
204
|
+
* `source` changes and catches up at lower priority (after paint / on idle), so an
|
|
205
|
+
* expensive subtree keyed off the deferred value never blocks the urgent update that
|
|
206
|
+
* caused the change — type into a filter, the input echoes instantly, the big list
|
|
207
|
+
* re-renders a beat later.
|
|
208
|
+
*
|
|
209
|
+
* ```ts
|
|
210
|
+
* const query = signal('');
|
|
211
|
+
* const deferredQuery = deferredValue(query);
|
|
212
|
+
* const results = computed(() => expensiveFilter(items(), deferredQuery()));
|
|
213
|
+
* // template: <input [(ngModel)]="query" /> stays responsive; results lag one paint
|
|
214
|
+
* // deferredQuery.pending() → dim the stale list while it catches up
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* Rapid changes coalesce: each change reschedules the catch-up, so only the LATEST
|
|
218
|
+
* source value is ever applied (no intermediate churn in the expensive subtree).
|
|
219
|
+
* On the server this is a synchronous pass-through — SSR renders once, so deferral
|
|
220
|
+
* would just mean rendering stale content.
|
|
221
|
+
*
|
|
222
|
+
* This is a scheduling tool, not an async one — for async work compose `latest()`;
|
|
223
|
+
* for coordinated multi-resource reveals use a transition scope.
|
|
224
|
+
*/
|
|
225
|
+
declare function deferredValue<T>(source: Signal<T>, opt?: DeferredValueOptions<T>): DeferredSignal<T>;
|
|
226
|
+
|
|
177
227
|
/**
|
|
178
228
|
* Structural hold-and-swap as a signal. Given a `target` (the desired value — e.g. the
|
|
179
229
|
* subtree/def/key you want to show) and a `ready` predicate, returns a signal that keeps
|
|
@@ -188,6 +238,95 @@ declare function providePaused(source: Signal<boolean>): Provider;
|
|
|
188
238
|
*/
|
|
189
239
|
declare function holdUntilReady<T>(target: Signal<T>, ready: () => boolean): Signal<T>;
|
|
190
240
|
|
|
241
|
+
/**
|
|
242
|
+
* What `use()` accepts: any status-bearing async value — an Angular `ResourceRef`,
|
|
243
|
+
* an `@mmstack/resource` query/mutation, or another `latest()` result (so async
|
|
244
|
+
* derivations nest). Purely structural; no class or brand required.
|
|
245
|
+
*/
|
|
246
|
+
type UseSource<T> = {
|
|
247
|
+
readonly status: Signal<ResourceStatus>;
|
|
248
|
+
readonly value: Signal<T | undefined>;
|
|
249
|
+
hasValue(): boolean;
|
|
250
|
+
readonly error?: Signal<unknown>;
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* An async derivation: callable as a signal of the latest successfully-computed value
|
|
254
|
+
* (held through in-flight recomputes — the stale-while-revalidate atom), with the
|
|
255
|
+
* aggregate async state of everything it `use()`d. Satisfies both `UseSource` (so it
|
|
256
|
+
* nests inside another `latest`) and the transition scope's `ResourceLike` surface
|
|
257
|
+
* (so it registers into boundaries like any resource).
|
|
258
|
+
*/
|
|
259
|
+
type LatestSignal<T> = Signal<T | undefined> & {
|
|
260
|
+
/** The held value — same signal as the callable itself. */
|
|
261
|
+
readonly value: Signal<T | undefined>;
|
|
262
|
+
/**
|
|
263
|
+
* Aggregate status. `error` wins (any used member errored, or the computation threw);
|
|
264
|
+
* otherwise in-flight work maps to `reloading` (a value is held) / `loading` (first
|
|
265
|
+
* load); a completed computation is `resolved`; blocked-with-nothing-in-flight (e.g.
|
|
266
|
+
* a member is `idle`) is `idle`.
|
|
267
|
+
*/
|
|
268
|
+
readonly status: Signal<ResourceStatus>;
|
|
269
|
+
/** Any used member has a request in flight (`loading`/`reloading`) — the aggregate transition indicator. */
|
|
270
|
+
readonly pending: Signal<boolean>;
|
|
271
|
+
/** Alias of `pending`, for the `ResourceRef`-shaped surface. */
|
|
272
|
+
readonly isLoading: Signal<boolean>;
|
|
273
|
+
/**
|
|
274
|
+
* The computation's own thrown error, or the first used member's error (in read
|
|
275
|
+
* order). `undefined` when healthy. The held value stays readable through an error.
|
|
276
|
+
*/
|
|
277
|
+
readonly error: Signal<unknown>;
|
|
278
|
+
/** Whether a value has ever been produced (and is therefore held). */
|
|
279
|
+
hasValue(): boolean;
|
|
280
|
+
};
|
|
281
|
+
type CreateLatestOptions<T> = {
|
|
282
|
+
/** Equality for the held value: an in-flight cycle that recomputes to an equal value never notifies consumers (while `pending` still reports the flight). */
|
|
283
|
+
readonly equal?: ValueEqualityFn<T>;
|
|
284
|
+
/**
|
|
285
|
+
* Auto-registration into the nearest transition scope (same vocabulary as resource
|
|
286
|
+
* options): `'indicator'` drives `pending`/hold-stale only, `'suspend'` also gates the
|
|
287
|
+
* boundary's first-load placeholder. Requires an injection context (or `injector`).
|
|
288
|
+
*/
|
|
289
|
+
readonly register?: false | 'indicator' | 'suspend';
|
|
290
|
+
/** Injection context for `register`, when created outside one. */
|
|
291
|
+
readonly injector?: Injector;
|
|
292
|
+
readonly debugName?: string;
|
|
293
|
+
};
|
|
294
|
+
/**
|
|
295
|
+
* Reads a resource inside a `latest()` computation: returns its value and reports it to
|
|
296
|
+
* the enclosing collector, so the derivation's aggregate `pending`/`status`/`error`
|
|
297
|
+
* include it. When the resource has no value yet (first load) or is in an error state,
|
|
298
|
+
* the computation short-circuits — code after this call simply doesn't run this round —
|
|
299
|
+
* which is what lets you write the happy path with no `undefined` checks:
|
|
300
|
+
*
|
|
301
|
+
* ```ts
|
|
302
|
+
* const fullName = latest(() => {
|
|
303
|
+
* const u = use(user); // waterfalls compose:
|
|
304
|
+
* const org = use(orgFor(u)); // orgFor(u) is only read once `user` has a value
|
|
305
|
+
* return `${u.name} @ ${org.name}`;
|
|
306
|
+
* });
|
|
307
|
+
* ```
|
|
308
|
+
*
|
|
309
|
+
* Must be called synchronously within `latest()` — like `inject()`, it throws elsewhere.
|
|
310
|
+
*/
|
|
311
|
+
declare function use<T>(res: UseSource<T>): T;
|
|
312
|
+
/**
|
|
313
|
+
* An async derivation over resources: evaluates `fn` inside a collector frame so that
|
|
314
|
+
* every `use()` read registers as a member, and exposes the result with resource
|
|
315
|
+
* semantics — the value holds its previous state while anything it read is in flight
|
|
316
|
+
* (never flashing empty), `pending` aggregates the members' in-flight state, and the
|
|
317
|
+
* whole thing is itself a `UseSource`, so `latest`s nest and propagate.
|
|
318
|
+
*
|
|
319
|
+
* ```ts
|
|
320
|
+
* const fullName = latest(() => `${use(user).name} @ ${use(org).name}`);
|
|
321
|
+
* fullName(); // held value — undefined only before the first successful run
|
|
322
|
+
* fullName.pending(); // true while user OR org (re)loads
|
|
323
|
+
* ```
|
|
324
|
+
*
|
|
325
|
+
* Evaluation is a plain `computed` under the hood: lazy, pure, no effects, usable
|
|
326
|
+
* outside any injection context (`register` is the only DI-touching option).
|
|
327
|
+
*/
|
|
328
|
+
declare function latest<T>(fn: () => T, opt?: CreateLatestOptions<T>): LatestSignal<T>;
|
|
329
|
+
|
|
191
330
|
/**
|
|
192
331
|
* Handle for an in-progress transition: a `pending` signal (true while the transition's OWN
|
|
193
332
|
* resources are in flight — loads already in flight when it started are not attributed) and a
|
|
@@ -214,6 +353,22 @@ type TransitionRef = {
|
|
|
214
353
|
*/
|
|
215
354
|
declare function injectStartTransition(): (fn: () => void) => TransitionRef;
|
|
216
355
|
|
|
356
|
+
/**
|
|
357
|
+
* The structural surface a transition scope actually reads — everything a `ResourceRef`
|
|
358
|
+
* has, so any resource (query, mutation, plain Angular `resource`) passes as-is, but also
|
|
359
|
+
* satisfied by status-bearing derivations like `latest()`, so those register too.
|
|
360
|
+
*
|
|
361
|
+
* `abort` is the optional cancellation seam: a resource that knows how to tear down its
|
|
362
|
+
* in-flight work exposes it (`queryResource` does; mutations deliberately don't — a POST
|
|
363
|
+
* can't be unsent), and {@link TransitionScope.abortPending} calls it. Resources without
|
|
364
|
+
* it are simply left to settle.
|
|
365
|
+
*/
|
|
366
|
+
type ResourceLike = {
|
|
367
|
+
readonly status: Signal<ResourceStatus>;
|
|
368
|
+
readonly isLoading: Signal<boolean>;
|
|
369
|
+
hasValue(): boolean;
|
|
370
|
+
abort?(): void;
|
|
371
|
+
};
|
|
217
372
|
/**
|
|
218
373
|
* What "not ready" means for first-load suspense:
|
|
219
374
|
* - `'value'`: the resource has no value yet (`!hasValue()`). With `keepPrevious`,
|
|
@@ -238,7 +393,7 @@ type RegisterOptions = {
|
|
|
238
393
|
*/
|
|
239
394
|
type TransitionScope = {
|
|
240
395
|
/** The currently-registered resources (read-only view). */
|
|
241
|
-
readonly resources: Signal<readonly
|
|
396
|
+
readonly resources: Signal<readonly ResourceLike[]>;
|
|
242
397
|
/**
|
|
243
398
|
* Any registered resource has a request in flight (`status` is `loading`/`reloading`).
|
|
244
399
|
* This is the transition indicator — true during a reload while `keepPrevious` holds
|
|
@@ -247,8 +402,8 @@ type TransitionScope = {
|
|
|
247
402
|
readonly pending: Signal<boolean>;
|
|
248
403
|
/** Any *suspending* resource is not ready — drives the first-load placeholder. */
|
|
249
404
|
suspended(type: SuspendType): boolean;
|
|
250
|
-
add(res:
|
|
251
|
-
remove(res:
|
|
405
|
+
add(res: ResourceLike, opt?: RegisterOptions): void;
|
|
406
|
+
remove(res: ResourceLike): void;
|
|
252
407
|
/**
|
|
253
408
|
* Coordinated commit: wraps a value signal so it FREEZES at its last-settled value
|
|
254
409
|
* while the scope is `pending`, then reveals the current value once *everything*
|
|
@@ -257,6 +412,31 @@ type TransitionScope = {
|
|
|
257
412
|
* value: keepPrevious holds per-resource, `commit` gates the reveal on the aggregate.
|
|
258
413
|
*/
|
|
259
414
|
commit<T>(value: Signal<T>): Signal<T>;
|
|
415
|
+
/**
|
|
416
|
+
* THE CANCELLATION CONTRACT, and its manual lever for shared-scope cases.
|
|
417
|
+
*
|
|
418
|
+
* What holds by construction (no call needed):
|
|
419
|
+
* - **View-scoped work dies with its view.** A superseded transition (outlet or
|
|
420
|
+
* `*mmTransition`) destroys the hidden incoming view and its injector; resources
|
|
421
|
+
* created there are destroyed, which aborts their in-flight loads.
|
|
422
|
+
* - **Abort is real, all the way down.** Deduped HTTP requests are refCounted — when
|
|
423
|
+
* the last consumer lets go the request itself is torn down — and an aborted
|
|
424
|
+
* response can never settle into the query cache (cache writes happen on the
|
|
425
|
+
* subscriber side of the interceptor chain).
|
|
426
|
+
*
|
|
427
|
+
* What this method adds: resources registered in a scope that OUTLIVES the transition
|
|
428
|
+
* (a shared/root scope) aren't view-scoped, so nothing destroys them on supersede.
|
|
429
|
+
* `abortPending()` walks the registered resources and calls `abort()` on every
|
|
430
|
+
* in-flight one that exposes it ({@link ResourceLike.abort} — queries do, mutations
|
|
431
|
+
* deliberately don't, and a shared resource aborts for ALL its readers, so call this
|
|
432
|
+
* on interactions that invalidate the pending work, not as a reflex).
|
|
433
|
+
*
|
|
434
|
+
* Honest limit (true for every JS framework): only I/O is cancellable — an
|
|
435
|
+
* already-running synchronous computation cannot be preempted.
|
|
436
|
+
*
|
|
437
|
+
* @returns how many resources were actually aborted.
|
|
438
|
+
*/
|
|
439
|
+
abortPending(): number;
|
|
260
440
|
/**
|
|
261
441
|
* Whether a transaction is currently HOLDING this scope's synchronous display reads (Tier 3).
|
|
262
442
|
* A counter under the hood, so nested transactions compose. Distinct from `pending` (a resource
|
|
@@ -276,6 +456,19 @@ type TransitionScope = {
|
|
|
276
456
|
hold<T>(value: Signal<T>): Signal<T>;
|
|
277
457
|
};
|
|
278
458
|
declare function createTransitionScope(): TransitionScope;
|
|
459
|
+
/**
|
|
460
|
+
* The scope→`PendingTasks` bridge: while `scope.pending()` is true, hold an Angular
|
|
461
|
+
* pending task so SSR serialization waits for the scope's in-flight loads — HTTP loads
|
|
462
|
+
* already do this via HttpClient, but CUSTOM loaders (a `latest()` over a hand-rolled
|
|
463
|
+
* promise, a non-HTTP resource) would otherwise let the server render a boundary
|
|
464
|
+
* mid-load. Wired automatically by `provideTransitionScope` /
|
|
465
|
+
* `provideForwardingTransitionScope`; call it yourself only for scopes you construct
|
|
466
|
+
* directly with `createTransitionScope()`.
|
|
467
|
+
*
|
|
468
|
+
* Server-only by design: on the browser, tying `ApplicationRef.isStable` to every load
|
|
469
|
+
* would stall stability-gated machinery (testability, hydration timing) for no benefit.
|
|
470
|
+
*/
|
|
471
|
+
declare function bridgeScopeToPendingTasks(scope: TransitionScope, injector?: Injector): void;
|
|
279
472
|
/** Provide a fresh transition scope at a boundary so its subtree's resources are tracked independently. */
|
|
280
473
|
declare function provideTransitionScope(): Provider;
|
|
281
474
|
declare function injectTransitionScope(): TransitionScope;
|
|
@@ -307,9 +500,9 @@ declare function createAttributedPending(scope: TransitionScope): Signal<boolean
|
|
|
307
500
|
* to the scope and removes it when the caller's injection context is destroyed. Pass any
|
|
308
501
|
* `ResourceRef` (a query, mutation, or plain Angular resource) through it.
|
|
309
502
|
*/
|
|
310
|
-
declare function injectRegisterResource(): <T extends
|
|
503
|
+
declare function injectRegisterResource(): <T extends ResourceLike>(res: T, opt?: RegisterOptions) => T;
|
|
311
504
|
/** Convenience: register a resource with the nearest transition scope. Must run in an injection context. */
|
|
312
|
-
declare function registerResource<T extends
|
|
505
|
+
declare function registerResource<T extends ResourceLike>(res: T, opt?: RegisterOptions): T;
|
|
313
506
|
|
|
314
507
|
/**
|
|
315
508
|
* Shared **suspense** (readiness) boundary behaviour: reads the *nearest* transition scope and exposes
|
|
@@ -464,6 +657,37 @@ declare class MmTransition<T> {
|
|
|
464
657
|
static ɵdir: i0.ɵɵDirectiveDeclaration<MmTransition<any>, "[mmTransition]", ["mmTransition"], { "value": { "alias": "mmTransition"; "required": true; "isSignal": true; }; "immediate": { "alias": "mmTransitionImmediate"; "required": false; "isSignal": true; }; "viewTransition": { "alias": "mmTransitionViewTransition"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
465
658
|
}
|
|
466
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Per-element morphs on held swaps: assigns `view-transition-name` reactively, so when
|
|
662
|
+
* a swap wrapped in `document.startViewTransition` flips views (`*mmTransition`'s
|
|
663
|
+
* `mmTransitionViewTransition`, or the transition outlet's view-transition option), the
|
|
664
|
+
* browser pairs same-named elements across the outgoing and incoming views and MORPHS
|
|
665
|
+
* them instead of cross-fading the whole boundary.
|
|
666
|
+
*
|
|
667
|
+
* ```html
|
|
668
|
+
* <!-- outgoing view (list) and incoming view (detail) both name the hero image: -->
|
|
669
|
+
* <img [mmViewTransitionName]="'hero-' + item().id" [src]="item().img" />
|
|
670
|
+
* ```
|
|
671
|
+
*
|
|
672
|
+
* Why this works with holds: both views coexist in the DOM during a hold, but the
|
|
673
|
+
* incoming one is `display: none` — elements without boxes aren't captured, so the
|
|
674
|
+
* same name on both sides is legal at each capture point (old visible at snapshot,
|
|
675
|
+
* new visible after the swap). No arming/cleanup dance needed.
|
|
676
|
+
*
|
|
677
|
+
* The name is normalized to a valid CSS custom-ident (invalid characters → `-`, a
|
|
678
|
+
* leading digit gets a `_` prefix). An empty string / `'none'` clears the name — use
|
|
679
|
+
* that to opt an element out conditionally. One rule remains YOURS to keep: a name
|
|
680
|
+
* must be unique among elements VISIBLE at capture time (two rendered instances of the
|
|
681
|
+
* same named element make the browser skip the whole transition) — derive names from
|
|
682
|
+
* ids for anything that can repeat.
|
|
683
|
+
*/
|
|
684
|
+
declare class MmViewTransitionName {
|
|
685
|
+
readonly mmViewTransitionName: i0.InputSignal<string>;
|
|
686
|
+
constructor();
|
|
687
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<MmViewTransitionName, never>;
|
|
688
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<MmViewTransitionName, "[mmViewTransitionName]", never, { "mmViewTransitionName": { "alias": "mmViewTransitionName"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
689
|
+
}
|
|
690
|
+
|
|
467
691
|
/**
|
|
468
692
|
* Options for creating a debounced writable signal.
|
|
469
693
|
* Extends Angular's `CreateSignalOptions` with a debounce time setting.
|
|
@@ -2612,8 +2836,8 @@ type UnwrapOpaque<T> = T extends {
|
|
|
2612
2836
|
type BaseType = string | number | boolean | symbol | bigint | undefined | null | Function | Date | RegExp | {
|
|
2613
2837
|
readonly [OPAQUE]: true;
|
|
2614
2838
|
};
|
|
2615
|
-
type Key = string | number;
|
|
2616
|
-
type AnyRecord = Record<Key, any>;
|
|
2839
|
+
type Key$1 = string | number;
|
|
2840
|
+
type AnyRecord = Record<Key$1, any>;
|
|
2617
2841
|
/**
|
|
2618
2842
|
* @internal Resolves to `true` only for `any`. In a conditional type, `any` distributes across
|
|
2619
2843
|
* *both* branches (`unknown | object`), and `unknown | X` collapses to `unknown` — which would
|
|
@@ -2800,6 +3024,32 @@ declare function mutableStore<T extends AnyRecord>(value: T, opt?: CreateSignalO
|
|
|
2800
3024
|
*/
|
|
2801
3025
|
noUnionLeaves?: boolean;
|
|
2802
3026
|
}): MutableSignalStore<T>;
|
|
3027
|
+
/**
|
|
3028
|
+
* Builds a DI-less store context — the shared proxy-cache and cleanup registry that {@link toStore}
|
|
3029
|
+
* normally resolves from the injector — so a `store`/`toStore`/`opLog` graph can run with NO Angular
|
|
3030
|
+
* injection context. Spread the result into the options:
|
|
3031
|
+
*
|
|
3032
|
+
* ```ts
|
|
3033
|
+
* import { microtaskOpLogDriver } from '@mmstack/worker/host';
|
|
3034
|
+
* const ctx = createStoreContext();
|
|
3035
|
+
* const s = store({ todos: [] }, ctx);
|
|
3036
|
+
* const log = opLog(s, { driver: microtaskOpLogDriver(), origin: 'worker' }); // no injector anywhere
|
|
3037
|
+
* ```
|
|
3038
|
+
*
|
|
3039
|
+
* **This is a worker-only fallback — do NOT use it on the main thread.** DI is the default and
|
|
3040
|
+
* correct path in an app: the injector scopes the proxy-cache/cleanup singletons per app instance,
|
|
3041
|
+
* which on the SERVER keeps one request's store identity from bleeding into another's (the exact
|
|
3042
|
+
* hazard a module-scope singleton would reintroduce). A Web Worker is safe because it is a single
|
|
3043
|
+
* store graph per thread and never runs during SSR (spawn is a `PLATFORM_ID === 'server'` no-op),
|
|
3044
|
+
* so there is no cross-request scope to contaminate. Never hoist a `createStoreContext()` to module
|
|
3045
|
+
* scope on a shared/main thread.
|
|
3046
|
+
*
|
|
3047
|
+
* **Share ONE context across every store in a worker** — the same way `providedIn: 'root'` shares
|
|
3048
|
+
* one cache across all of an app's stores. `@mmstack/worker/host` memoizes this per worker
|
|
3049
|
+
* (`workerStoreContext()`); reach for `createStoreContext()` directly only in a bare
|
|
3050
|
+
* (non-worker-host) DI-less setup, and hold the single instance yourself.
|
|
3051
|
+
*/
|
|
3052
|
+
declare function createStoreContext(): toStoreOptions;
|
|
2803
3053
|
|
|
2804
3054
|
/**
|
|
2805
3055
|
* A 3-way merge of a forked value against a changed base: given the common `ancestor` (the base
|
|
@@ -2869,6 +3119,170 @@ type ForkStoreOptions<T> = toStoreOptions & {
|
|
|
2869
3119
|
};
|
|
2870
3120
|
declare function forkStore<T extends Record<string, any>>(base: WritableSignalStore<T>, opt?: ForkStoreOptions<T>): Fork<T>;
|
|
2871
3121
|
|
|
3122
|
+
type Key = string | number;
|
|
3123
|
+
/**
|
|
3124
|
+
* One structural operation. `set` on a key that did not previously exist carries NO `prev`
|
|
3125
|
+
* property (an absent key is not the same as a key holding `undefined` — the merge3 lesson),
|
|
3126
|
+
* which is what lets {@link invertBatch} invert an add into a delete.
|
|
3127
|
+
*/
|
|
3128
|
+
type StoreOp = {
|
|
3129
|
+
kind: 'set';
|
|
3130
|
+
path: readonly Key[];
|
|
3131
|
+
next: unknown;
|
|
3132
|
+
prev?: unknown;
|
|
3133
|
+
} | {
|
|
3134
|
+
kind: 'delete';
|
|
3135
|
+
path: readonly Key[];
|
|
3136
|
+
prev: unknown;
|
|
3137
|
+
};
|
|
3138
|
+
/** One emission: every op derived from one commit window (a tick), in path order. */
|
|
3139
|
+
type OpBatch = {
|
|
3140
|
+
/** Identifies the emitting log — filter your own batches on a shared transport. */
|
|
3141
|
+
readonly origin: string;
|
|
3142
|
+
/** Per-log monotonic batch counter. */
|
|
3143
|
+
readonly version: number;
|
|
3144
|
+
readonly ops: readonly StoreOp[];
|
|
3145
|
+
};
|
|
3146
|
+
/**
|
|
3147
|
+
* Drives an {@link opLog}'s emission reaction. Given the `run` closure (which reads the source in
|
|
3148
|
+
* a tracking context and flushes the delta), a driver arranges for `run` to execute now and again
|
|
3149
|
+
* on every subsequent change, returning a handle that stops it. The default driver is an Angular
|
|
3150
|
+
* `effect` (needs an injector). Supply a custom driver to run an opLog with NO injector; a
|
|
3151
|
+
* renderer-independent one built on `@angular/core/primitives/signals` `createWatch` ships as
|
|
3152
|
+
* `microtaskOpLogDriver` from `@mmstack/worker/host` (the Web Worker seam).
|
|
3153
|
+
*/
|
|
3154
|
+
type OpLogDriver = (run: () => void) => {
|
|
3155
|
+
destroy(): void;
|
|
3156
|
+
};
|
|
3157
|
+
type CreateOpLogOptions = {
|
|
3158
|
+
/** Transport identity for emitted batches. Defaults to a random id. */
|
|
3159
|
+
readonly origin?: string;
|
|
3160
|
+
/** Injection context for the default effect-based driver (required outside one). */
|
|
3161
|
+
readonly injector?: Injector;
|
|
3162
|
+
/**
|
|
3163
|
+
* Replaces the default Angular-`effect` emission driver. Supply a custom driver (e.g.
|
|
3164
|
+
* `microtaskOpLogDriver` from `@mmstack/worker/host`) to run an opLog with NO injector. When
|
|
3165
|
+
* given, `injector` is ignored and no injection context is required.
|
|
3166
|
+
*/
|
|
3167
|
+
readonly driver?: OpLogDriver;
|
|
3168
|
+
};
|
|
3169
|
+
type OpLog<T extends object> = {
|
|
3170
|
+
/**
|
|
3171
|
+
* Ordered, lossless delivery of every emitted batch. Synchronous — don't write back into
|
|
3172
|
+
* the observed source from inside a callback (route remote data through {@link OpLog.apply}).
|
|
3173
|
+
*/
|
|
3174
|
+
subscribe(cb: (batch: OpBatch) => void): () => void;
|
|
3175
|
+
/** The most recent batch — a lossy sampling view (devtools); use `subscribe` for transport. */
|
|
3176
|
+
readonly latest: Signal<OpBatch | null>;
|
|
3177
|
+
/**
|
|
3178
|
+
* Synchronously diff the source and emit any pending change NOW, rather than waiting for the
|
|
3179
|
+
* driver's scheduled run (an app tick, or a custom driver's microtask). Idempotent
|
|
3180
|
+
* and coalescing: writes since the last emission compose into one batch, and a `flush()` with
|
|
3181
|
+
* nothing pending is a no-op. Use it to make emission deterministic — the worker host calls it
|
|
3182
|
+
* to settle its mirror synchronously (tests), and it underpins the flush-before-apply honesty of
|
|
3183
|
+
* {@link OpLog.apply}. Independent of the driver: a later scheduled run simply finds no diff.
|
|
3184
|
+
*/
|
|
3185
|
+
flush(): void;
|
|
3186
|
+
/**
|
|
3187
|
+
* Applies ops (a remote batch, a persisted journal entry, an {@link invertBatch} result)
|
|
3188
|
+
* atomically: ONE `set`, one notification wave. Also advances this log's diff baseline in
|
|
3189
|
+
* the same step, so an applied batch produces NO echo emission — sync loops terminate by
|
|
3190
|
+
* construction. Local writes pending in the current tick are flushed (emitted) first, so
|
|
3191
|
+
* they are never silently folded into the applied baseline.
|
|
3192
|
+
*/
|
|
3193
|
+
apply(ops: OpBatch | readonly StoreOp[]): void;
|
|
3194
|
+
/** Stops observing and drops subscribers. Also happens when the injection context dies. */
|
|
3195
|
+
destroy(): void;
|
|
3196
|
+
};
|
|
3197
|
+
/**
|
|
3198
|
+
* Pure, store-free application of ops onto a plain root value, returning the next immutable root
|
|
3199
|
+
* (structural-sharing along op paths, missing containers vivified `'auto'`-style). This is the
|
|
3200
|
+
* same transform {@link OpLog.apply} runs, extracted so a replica can fold a received batch into
|
|
3201
|
+
* a value WITHOUT owning a diffing {@link opLog} — e.g. the worker-graph read-replica seam.
|
|
3202
|
+
* Accepts a batch or a bare op list.
|
|
3203
|
+
*/
|
|
3204
|
+
declare function applyOps<T>(root: T, ops: OpBatch | readonly StoreOp[]): T;
|
|
3205
|
+
/**
|
|
3206
|
+
* Pure reference-pruned structural diff of two roots into minimal ops (the emission core of
|
|
3207
|
+
* {@link opLog}, exported so code outside a log can produce a batch — e.g. diffing a scratch
|
|
3208
|
+
* draft against a replica's current value to route a write to its owner). Trusts the
|
|
3209
|
+
* copy-on-write contract: an untouched subtree that kept its reference is skipped.
|
|
3210
|
+
*/
|
|
3211
|
+
declare function diffOps(prev: unknown, next: unknown): StoreOp[];
|
|
3212
|
+
/**
|
|
3213
|
+
* Inverts a batch for undo: reversed order, `set`↔its own inverse (an add — a `set` with no
|
|
3214
|
+
* `prev` — inverts to a `delete`; a `delete` inverts to a `set` restoring `prev`). Feed the
|
|
3215
|
+
* result to {@link OpLog.apply}. Requires the ops' `prev`s, which in-memory batches always
|
|
3216
|
+
* carry — a wire-serialized batch that stripped them is not invertible.
|
|
3217
|
+
*/
|
|
3218
|
+
declare function invertBatch(batch: OpBatch | readonly StoreOp[]): StoreOp[];
|
|
3219
|
+
/**
|
|
3220
|
+
* Observes a copy-on-write signal (a `store`'s root, or any `WritableSignal` holding
|
|
3221
|
+
* immutably-updated objects) and emits its changes as minimal structural op batches — the
|
|
3222
|
+
* shared substrate for sync (ship batches, `apply` remote ones), persistence (journal
|
|
3223
|
+
* batches, replay on boot), undo ({@link invertBatch}), and devtools (`latest`).
|
|
3224
|
+
*
|
|
3225
|
+
* Zero store-core involvement and zero cost when unused: emission is a reference-pruned diff
|
|
3226
|
+
* of the root value per tick (structural sharing makes it O(changed paths)), driven by one
|
|
3227
|
+
* effect. A batch therefore coalesces everything written in one tick — for coarser,
|
|
3228
|
+
* intentional units, stage writes on a `forkStore` and `commit()` (one set → one batch).
|
|
3229
|
+
*
|
|
3230
|
+
* NOT supported on mutable stores/signals: in-place mutation keeps reference identity, which
|
|
3231
|
+
* defeats the diff (same reason `forkStore`'s `'fine'` strategy refuses them) — a dev-mode
|
|
3232
|
+
* warning fires and nothing emits.
|
|
3233
|
+
*
|
|
3234
|
+
* ```ts
|
|
3235
|
+
* const s = store({ todos: [{ done: false }] });
|
|
3236
|
+
* const log = opLog(s, { origin: 'tab-a' });
|
|
3237
|
+
* log.subscribe((b) => channel.postMessage(encode(b))); // ship
|
|
3238
|
+
* channel.onmessage = (m) => log.apply(decode(m.data)); // apply — echo-free
|
|
3239
|
+
* s.todos[0].done.set(true); // → { kind: 'set', path: ['todos', 0, 'done'], … }
|
|
3240
|
+
* ```
|
|
3241
|
+
*/
|
|
3242
|
+
declare function opLog<T extends object>(source: WritableSignal<T>, opt?: CreateOpLogOptions): OpLog<T>;
|
|
3243
|
+
|
|
3244
|
+
/** Identity selector for keyed array reconciliation: a property name, or a function per item. */
|
|
3245
|
+
type ReconcileKey = string | ((item: any) => unknown);
|
|
3246
|
+
/**
|
|
3247
|
+
* Produces a value equal to `next` but sharing as much of `prev`'s reference structure as possible:
|
|
3248
|
+
* an object subtree that did not change keeps its `prev` reference, and array items are matched by
|
|
3249
|
+
* `key` so a surviving item keeps its identity across a reorder/insert/remove (only added items are
|
|
3250
|
+
* new, only removed items are dropped). This is what lets a derived store recompute without tearing
|
|
3251
|
+
* down every downstream `computed` that reads an unchanged part of it.
|
|
3252
|
+
*/
|
|
3253
|
+
declare function reconcile<T>(prev: T, next: T, key?: ReconcileKey): T;
|
|
3254
|
+
type ProjectionOptions = toStoreOptions & {
|
|
3255
|
+
/** Identity key for reconciling array items (default `'id'`). */
|
|
3256
|
+
readonly key?: ReconcileKey;
|
|
3257
|
+
};
|
|
3258
|
+
/**
|
|
3259
|
+
* A derived STORE, the store-shaped counterpart to `computed`. `fn` receives a mutable draft seeded
|
|
3260
|
+
* with the current value and either mutates it in place or returns a new value; whichever it does,
|
|
3261
|
+
* the result is reconciled against the previous value (see {@link reconcile}) so unchanged subtrees
|
|
3262
|
+
* keep reference identity and keyed array items keep their proxy identity. Reading through the
|
|
3263
|
+
* returned store is fine-grained: a `computed` over one field only recomputes when that field
|
|
3264
|
+
* actually changes, even though the whole projection re-ran.
|
|
3265
|
+
*
|
|
3266
|
+
* Recompute is pull-based, exactly like `computed`: the projection is memoized and re-runs on the
|
|
3267
|
+
* first read after a signal `fn` depends on changes, so reads are always coherent (no waiting on an
|
|
3268
|
+
* effect flush) and nothing recomputes while nobody reads. `fn` must be pure, it runs inside the
|
|
3269
|
+
* reactive computation. Prefer `computed` for a plain value; reach for `projection` when you want
|
|
3270
|
+
* the per-property tracking of a store on top of a derivation.
|
|
3271
|
+
*
|
|
3272
|
+
* ```ts
|
|
3273
|
+
* const active = projection<User[]>(() => users().filter((u) => u.active), [], { key: 'id' });
|
|
3274
|
+
* // active[0].name(); — surviving users keep identity across recomputes
|
|
3275
|
+
* ```
|
|
3276
|
+
*
|
|
3277
|
+
* Needs an injection context (or an explicit `injector`) for the store layer's cleanup on the main
|
|
3278
|
+
* thread; with an explicit store context (`createStoreContext()`) it is injector-free, so it also
|
|
3279
|
+
* runs on a worker host.
|
|
3280
|
+
*
|
|
3281
|
+
* @param fn receives the current draft; mutate it, or return new data.
|
|
3282
|
+
* @param seed the initial value, held before the first run.
|
|
3283
|
+
*/
|
|
3284
|
+
declare function projection<T extends object>(fn: (draft: T) => void | T, seed: T, opt?: ProjectionOptions): SignalStore<T>;
|
|
3285
|
+
|
|
2872
3286
|
/**
|
|
2873
3287
|
* Interface for storage mechanisms compatible with the `stored` signal.
|
|
2874
3288
|
* Matches the essential parts of the `Storage` interface (`localStorage`, `sessionStorage`).
|
|
@@ -3327,5 +3741,5 @@ type CreateHistoryOptions<T> = Omit<CreateSignalOptions<T[]>, 'equal'> & {
|
|
|
3327
3741
|
*/
|
|
3328
3742
|
declare function withHistory<T>(sourceOrValue: WritableSignal<T> | T, opt?: CreateHistoryOptions<T>): SignalWithHistory<T>;
|
|
3329
3743
|
|
|
3330
|
-
export { MmActivity, MmTransition, PAUSABLE_OPTIONS, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, batteryStatus, chunked, clipboard, combineWith, createAttributedPending, createForwardingScope, createTransaction, createTransitionScope, debounce, debounced, derived, distinct, elementSize, elementVisibility, extendStore, filter, filterWith, focusWithin, forkStore, geolocation, getTransitionScope, holdUntilReady, idle, indexArray, injectPaused, injectRegisterResource, injectStartTransaction, injectStartTransition, injectTransitionScope, isDerivation, isLeaf, isMutable, isOpaque, isStore, keepPrevious, keyArray, map, mapArray, mapObject, mediaQuery, merge3, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, opaque, orientation, pageVisibility, pairwise, pausableComputed, pausableEffect, pausableSignal, pipeable, piped, pointerDrag, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, provideForwardingTransitionScope, providePausableOptions, providePaused, provideTransitionScope, registerResource, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
|
|
3331
|
-
export type { BatteryStatus, ClipboardSignal, Computation, CreateChunkedOptions, CreateDebouncedOptions, CreateHistoryOptions, CreatePooledOptions, CreateProvidedPooledOptions, CreateStoredOptions, CreateThrottledOptions, DebouncedSignal, DerivedSignal, ElementSize, ElementSizeOptions, ElementSizeSignal, ElementVisibilityOptions, ElementVisibilitySignal, ExtendStoreOptions, Fork, ForkStoreOptions, ForkStrategy, ForwardingTransitionScope, Frame, GeolocationOptions, GeolocationSignal, IdleOptions, IdleSignal, MmTransitionContext, MousePositionOptions, MousePositionSignal, MutableSignal, MutableSignalStore, NetworkStatusSignal, Opaque, PausableOptions, PauseOption, PipeableSignal, PointerDragOptions, PointerDragSignal, PointerDragState, PointerModifiers, PointerPoint, ReconcileFn, RegisterOptions, ScreenOrientation, ScreenOrientationState, ScrollPosition, ScrollPositionOptions, ScrollPositionSignal, SensorRunOptions, SignalFromEventOptions, SignalStore, SignalWithHistory, StoreOptions, StoredSignal, SuspendType, ThrottledSignal, Transaction, TransactionRef, TransitionRef, TransitionScope, UntilOptions, Vivify, WindowSize, WindowSizeOptions, WindowSizeSignal, WithVivify, WritableSignalStore, toStoreOptions };
|
|
3744
|
+
export { MmActivity, MmTransition, MmViewTransitionName, PAUSABLE_OPTIONS, SuspenseBoundary, SuspenseBoundaryBase, UnscopedSuspenseBoundary, activeTransaction, applyOps, batteryStatus, bridgeScopeToPendingTasks, chunked, clipboard, combineWith, createAttributedPending, createForwardingScope, createStoreContext, createTransaction, createTransitionScope, debounce, debounced, deferredValue, derived, diffOps, distinct, elementSize, elementVisibility, extendStore, filter, filterWith, focusWithin, forkStore, geolocation, getTransitionScope, holdUntilReady, idle, indexArray, injectPaused, injectRegisterResource, injectStartTransaction, injectStartTransition, injectTransitionScope, invertBatch, isDerivation, isLeaf, isMutable, isOpaque, isStore, keepPrevious, keyArray, latest, map, mapArray, mapObject, mediaQuery, merge3, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, opLog, opaque, orientation, pageVisibility, pairwise, pausableComputed, pausableEffect, pausableSignal, pipeable, piped, pointerDrag, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, projection, provideForwardingTransitionScope, providePausableOptions, providePaused, provideTransitionScope, reconcile, registerResource, resolvePause, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, use, windowSize, withHistory };
|
|
3745
|
+
export type { BatteryStatus, ClipboardSignal, Computation, CreateChunkedOptions, CreateDebouncedOptions, CreateHistoryOptions, CreateLatestOptions, CreateOpLogOptions, CreatePooledOptions, CreateProvidedPooledOptions, CreateStoredOptions, CreateThrottledOptions, DebouncedSignal, DeferStrategy, DeferredSignal, DeferredValueOptions, DerivedSignal, ElementSize, ElementSizeOptions, ElementSizeSignal, ElementVisibilityOptions, ElementVisibilitySignal, ExtendStoreOptions, Fork, ForkStoreOptions, ForkStrategy, ForwardingTransitionScope, Frame, GeolocationOptions, GeolocationSignal, IdleOptions, IdleSignal, LatestSignal, MmTransitionContext, MousePositionOptions, MousePositionSignal, MutableSignal, MutableSignalStore, NetworkStatusSignal, OpBatch, OpLog, OpLogDriver, Opaque, PausableOptions, PauseOption, PipeableSignal, PointerDragOptions, PointerDragSignal, PointerDragState, PointerModifiers, PointerPoint, ProjectionOptions, ReconcileFn, ReconcileKey, RegisterOptions, ResourceLike, ScreenOrientation, ScreenOrientationState, ScrollPosition, ScrollPositionOptions, ScrollPositionSignal, SensorRunOptions, SignalFromEventOptions, SignalStore, SignalWithHistory, StoreOp, StoreOptions, StoredSignal, SuspendType, ThrottledSignal, Transaction, TransactionRef, TransitionRef, TransitionScope, UntilOptions, UseSource, Vivify, WindowSize, WindowSizeOptions, WindowSizeSignal, WithVivify, WritableSignalStore, toStoreOptions };
|