@yoltra/core 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.es.md +443 -148
- package/README.md +207 -84
- package/dist/types/index.d.ts +2 -1
- package/dist/types/persistence/persist.d.ts +2 -2
- package/dist/types/store/Store.d.ts +373 -66
- package/dist/types/store/fingerprint.d.ts +62 -0
- package/dist/types/store/matching.d.ts +49 -0
- package/dist/types/store/paths.d.ts +39 -0
- package/dist/types/store/performCall.d.ts +15 -0
- package/dist/types/store/rejection.d.ts +2 -2
- package/dist/types/types.d.ts +528 -18
- package/dist/yoltra.cjs +3 -8
- package/dist/yoltra.cjs.map +1 -1
- package/dist/yoltra.mjs +2133 -1385
- package/dist/yoltra.mjs.map +1 -1
- package/dist/yoltra.umd.js +3 -8
- package/dist/yoltra.umd.js.map +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content fingerprints for event deduplication.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Extracted from `Store.ts` following the `matching.ts` / `paths.ts` precedent: nothing here
|
|
6
|
+
* touches an instance field.
|
|
7
|
+
*
|
|
8
|
+
* The old fingerprint was `channel::type::JSON.stringify(payload)`, which produced two
|
|
9
|
+
* inconsistent failures on the one path whose entire job is deciding whether two payloads are
|
|
10
|
+
* the same. A `Map`, `Set` or typed array stringifies to `{}`, so **distinct payloads
|
|
11
|
+
* collided and the second event was silently swallowed** - precisely the behaviour the README
|
|
12
|
+
* says Yoltra refuses to do by default. A `BigInt` or a cycle threw, hit a timestamp fallback,
|
|
13
|
+
* and was never deduped at all.
|
|
14
|
+
*
|
|
15
|
+
* `encodeState` already produces a faithful, JSON-stringifiable representation of all of
|
|
16
|
+
* those, so fingerprinting through it makes content dedup mean what it says.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* JSON with plain-object keys sorted, for a stable content fingerprint.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* Insertion order is not content: `{a:1,b:2}` and `{b:2,a:1}` are the same payload and must
|
|
25
|
+
* fingerprint alike, which `JSON.stringify` alone does not deliver.
|
|
26
|
+
*
|
|
27
|
+
* **Arrays and `Map` entries are never sorted.** Their order is semantic - `[1,2]` is not
|
|
28
|
+
* `[2,1]`, and a `Map` preserves insertion order by specification. Sorting them would make
|
|
29
|
+
* genuinely different payloads share a fingerprint, which is worse than the bug this module
|
|
30
|
+
* exists to fix: it would silently drop real events rather than merely failing to dedup.
|
|
31
|
+
*
|
|
32
|
+
* Runs over the **already-encoded** value, so `Map`, `Set`, `Date` and binary have already
|
|
33
|
+
* become plain JSON shapes and there is nothing exotic left to handle.
|
|
34
|
+
*
|
|
35
|
+
* `JSON.stringify(v, keyArray)` cannot do this: the replacer-array form applies one global
|
|
36
|
+
* key list at every depth.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export declare function stableStringify(value: unknown): string;
|
|
41
|
+
/**
|
|
42
|
+
* A content fingerprint for one event.
|
|
43
|
+
*
|
|
44
|
+
* @param channel - Event channel.
|
|
45
|
+
* @param type - Event type.
|
|
46
|
+
* @param payload - Event payload.
|
|
47
|
+
* @returns A string that is equal for two events with equal content.
|
|
48
|
+
*
|
|
49
|
+
* @remarks
|
|
50
|
+
* Primitives keep a fast path, but a typed one: `String(payload)` alone made the number `1`
|
|
51
|
+
* and the string `"1"` the same event, as it did `true` and `"true"`. `null` and `undefined`
|
|
52
|
+
* are likewise distinguished, having previously shared `::null`.
|
|
53
|
+
*
|
|
54
|
+
* When the payload exceeds the node budget the fingerprint degrades to **never dedupe**
|
|
55
|
+
* rather than maybe-wrongly-dedupe. Two large payloads differing only past the cutoff would
|
|
56
|
+
* otherwise collide and the second would be dropped; refusing to dedup merely costs a
|
|
57
|
+
* duplicate, which is the safe direction and matches what already happened to payloads the
|
|
58
|
+
* old implementation could not serialize.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
export declare function fingerprint(channel: string, type: string, payload: unknown): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { EventKey, EventMapBase, EventUnion, MiddlewareFunction, MiddlewareInput, When } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if an event matches a `When` matcher.
|
|
4
|
+
*
|
|
5
|
+
* @param when - The When matcher (or undefined for "all events").
|
|
6
|
+
* @param event - The event to check.
|
|
7
|
+
* @returns `true` if the event matches, `false` otherwise.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* - `undefined` or missing `when` matches ALL events.
|
|
11
|
+
* - `{ any: true }` matches ALL events.
|
|
12
|
+
* - `{ keys: [...] }` matches if event's `[channel, type]` is in the array.
|
|
13
|
+
* - `{ channel: 'x' }` matches if event's channel equals 'x'.
|
|
14
|
+
* - `{ channels: ['x', 'y'] }` matches if event's channel is in the array.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export declare function matchesWhen<EM extends EventMapBase>(when: When<EM> | undefined, event: EventUnion<EM>): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Extracts the middleware function from a MiddlewareInput.
|
|
21
|
+
* Handles both raw functions (legacy) and MiddlewareSpec objects.
|
|
22
|
+
*
|
|
23
|
+
* @param input - MiddlewareInput (function or spec).
|
|
24
|
+
* @returns The middleware function.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export declare function getMiddlewareFunction<St, EM extends EventMapBase>(input: MiddlewareInput<St, EM>): MiddlewareFunction<St, EM>;
|
|
29
|
+
/**
|
|
30
|
+
* Gets the `when` matcher from a MiddlewareInput.
|
|
31
|
+
*
|
|
32
|
+
* @param input - MiddlewareInput (function or spec).
|
|
33
|
+
* @returns The `when` matcher, or `undefined` for raw functions (match all).
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export declare function getMiddlewareWhen<St, EM extends EventMapBase>(input: MiddlewareInput<St, EM>): When<EM> | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Normalizes event targeting from `when` to an array of EventKeys.
|
|
40
|
+
*
|
|
41
|
+
* @param spec - Object with an optional `when` matcher.
|
|
42
|
+
* @returns Array of `[channel, type]` pairs.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export declare function normalizeEventKeys<EM extends EventMapBase>(spec: {
|
|
47
|
+
when?: When<EM>;
|
|
48
|
+
events?: ReadonlyArray<EventKey<EM>>;
|
|
49
|
+
}): ReadonlyArray<EventKey<EM>>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading and expanding dotted state paths.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Moved out of `Store.ts` unchanged. Neither function touched an instance field.
|
|
6
|
+
*
|
|
7
|
+
* `Store` still exposes both as members, and deliberately so. `Store.buildAncestorPaths` is
|
|
8
|
+
* public API that appears in the committed reference, and `getAtPath` is replaced on the
|
|
9
|
+
* instance by a test that counts the walks a change description costs, so the internal callers
|
|
10
|
+
* have to keep reaching it through `this`.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Reads a dotted path from an object (supports numeric array indices via string keys).
|
|
16
|
+
*
|
|
17
|
+
* @param obj - Root object (slice or value).
|
|
18
|
+
* @param path - Dotted path; leading dot is ignored.
|
|
19
|
+
* @returns The value at the path, or `undefined`.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export declare function getAtPath(obj: any, path: string): any;
|
|
24
|
+
/**
|
|
25
|
+
* Builds ancestor paths for a dotted path.
|
|
26
|
+
*
|
|
27
|
+
* For `"a.b.c"`, returns `["a", "a.b", "a.b.c"]`. Leading dots are trimmed.
|
|
28
|
+
*
|
|
29
|
+
* @param path - Dotted path string.
|
|
30
|
+
* @returns Array of ancestor paths.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* buildAncestorPaths('x.y.z'); // ['x','x.y','x.y.z']
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildAncestorPaths(path: string): string[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DeepReadonly, EffectSpec, EmitOptions, EmitResult, EventMapBase, EventUnion } from '../types.js';
|
|
2
|
+
import { CallHandle, CallOptions } from './call.js';
|
|
3
|
+
/**
|
|
4
|
+
* What `performCall` needs from the store.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Three members, named rather than structural over the whole class, because three is few enough
|
|
8
|
+
* that naming them documents the coupling instead of hiding it.
|
|
9
|
+
*/
|
|
10
|
+
export interface CallDeps<St, EM extends EventMapBase> {
|
|
11
|
+
readonly idFactory: () => string;
|
|
12
|
+
readonly registerEffect: (spec: EffectSpec<DeepReadonly<St>, EM>) => () => void;
|
|
13
|
+
readonly emit: <C extends keyof EM & string, T extends keyof EM[C] & string>(channel: C, type: T, payload: EM[C][T], opts?: EmitOptions) => Promise<EmitResult>;
|
|
14
|
+
}
|
|
15
|
+
export declare function performCall<St, EM extends EventMapBase, C extends keyof EM & string, T extends keyof EM[C] & string>(deps: CallDeps<St, EM>, channel: C, type: T, payload: EM[C][T], opts: CallOptions<EM>): CallHandle<EventUnion<EM>, EventUnion<EM>>;
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @remarks
|
|
8
8
|
* `Symbol.for` rather than `Symbol()`, so the brand survives two copies of this package meeting
|
|
9
|
-
* at runtime — a duplicated dependency, a
|
|
10
|
-
* minor. With a unique symbol the check would silently answer `false` across that boundary and a
|
|
9
|
+
* at runtime — a duplicated dependency, a bundle that inlined a second copy, a consumer that
|
|
10
|
+
* pinned an older minor. With a unique symbol the check would silently answer `false` across that boundary and a
|
|
11
11
|
* refusal would read as ordinary state, which is the failure this whole feature exists to end.
|
|
12
12
|
*
|
|
13
13
|
* @internal
|