@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.
@@ -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 federated bundle, a consumer that pinned an older
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