footprintjs 9.4.0 → 9.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/esm/lib/builder/FlowChartBuilder.js +15 -2
  2. package/dist/esm/lib/capture/envelope.js +187 -0
  3. package/dist/esm/lib/engine/handlers/ContinuationResolver.js +23 -4
  4. package/dist/esm/lib/engine/types.js +1 -1
  5. package/dist/esm/lib/observer-queue/deferredDispatcher.js +226 -0
  6. package/dist/esm/lib/observer-queue/flushDriver.js +163 -0
  7. package/dist/esm/lib/observer-queue/index.js +22 -0
  8. package/dist/esm/lib/observer-queue/mergedQueue.js +91 -0
  9. package/dist/esm/lib/observer-queue/ring.js +122 -0
  10. package/dist/lib/builder/FlowChartBuilder.js +15 -2
  11. package/dist/lib/capture/envelope.js +192 -0
  12. package/dist/lib/engine/handlers/ContinuationResolver.js +23 -4
  13. package/dist/lib/engine/types.js +1 -1
  14. package/dist/lib/observer-queue/deferredDispatcher.js +230 -0
  15. package/dist/lib/observer-queue/flushDriver.js +167 -0
  16. package/dist/lib/observer-queue/index.js +36 -0
  17. package/dist/lib/observer-queue/mergedQueue.js +95 -0
  18. package/dist/lib/observer-queue/ring.js +126 -0
  19. package/dist/types/lib/capture/envelope.d.ts +169 -0
  20. package/dist/types/lib/engine/handlers/ContinuationResolver.d.ts +15 -2
  21. package/dist/types/lib/engine/types.d.ts +3 -0
  22. package/dist/types/lib/observer-queue/deferredDispatcher.d.ts +169 -0
  23. package/dist/types/lib/observer-queue/flushDriver.d.ts +124 -0
  24. package/dist/types/lib/observer-queue/index.d.ts +25 -0
  25. package/dist/types/lib/observer-queue/mergedQueue.d.ts +85 -0
  26. package/dist/types/lib/observer-queue/ring.d.ts +99 -0
  27. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ /**
2
+ * observer-queue/ — RFC-001 deferred observer delivery, Blocks 2–5.
3
+ *
4
+ * The pure "one beat behind" pipeline:
5
+ *
6
+ * producer ─► capture (Block 1, `capture/envelope`) ─► MergedQueue
7
+ * (Block 3, seq-stamped over a BoundedRing, Block 2) ─► FlushDriver
8
+ * (Block 4, armed-once microtask checkpoints, flushBudgetMs) ─►
9
+ * DeferredDispatcher (Block 5, isolated listeners + inflight + stats)
10
+ *
11
+ * INTERNAL MODULE — deliberately NOT exported from the public footprintjs
12
+ * barrels yet. The engine wiring + public surface land with Blocks 6–10
13
+ * (see docs/design/rfc-001-deferred-observers.md). Zero engine imports:
14
+ * this directory may import only `../capture/` and its own files.
15
+ */
16
+ export type { CaptureChannel, CaptureEnvelope, CaptureHooks, CapturePolicy, CaptureRequest, PayloadSummary, PayloadSummaryNode, PayloadSummaryType, } from '../capture/envelope.js';
17
+ export { capture, PAYLOAD_SUMMARY_MAX_DEPTH, PAYLOAD_SUMMARY_MAX_ENTRIES, PAYLOAD_SUMMARY_MAX_NODES, summarizePayload, } from '../capture/envelope.js';
18
+ export type { DeferredDispatcherOptions, DeferredListener, DispatchErrorContext, DispatchErrorHandler, DispatcherStats, DrainResult, ListenerStats, } from './deferredDispatcher.js';
19
+ export { DeferredDispatcher } from './deferredDispatcher.js';
20
+ export type { FlushDriverOptions, FlushDriverStats, FlushOutcome, FlushSyncResult } from './flushDriver.js';
21
+ export { FLUSH_SAMPLE_WINDOW, FlushDriver } from './flushDriver.js';
22
+ export type { EnqueueInput, EnqueueOutcome, EnqueueResult, MergedQueueOptions } from './mergedQueue.js';
23
+ export { DEFAULT_MAX_QUEUE, MergedQueue } from './mergedQueue.js';
24
+ export type { OverflowPolicy, RingCounters, RingOptions, RingPushResult } from './ring.js';
25
+ export { BoundedRing } from './ring.js';
@@ -0,0 +1,85 @@
1
+ /**
2
+ * observer-queue/mergedQueue.ts — RFC-001 Block 3: seq stamping + multi-channel merge.
3
+ *
4
+ * Pattern: Single totally-ordered staging queue. All three observer
5
+ * channels (`scope` / `flow` / `emit`) funnel through ONE queue;
6
+ * the `seq` counter is assigned at capture under the single JS
7
+ * thread, so drain order == arrival order ACROSS channels with no
8
+ * cross-queue merge logic ever needed.
9
+ * Role: Glue between the capture tier (Block 1) and the flush driver
10
+ * (Block 4). Pure module — imports only `capture/envelope` and
11
+ * the ring (Block 2); zero engine knowledge.
12
+ *
13
+ * Seq semantics (normative, RFC-001 §5):
14
+ * - Stamped BEFORE admission — an event that is then dropped (overflow)
15
+ * or refused (`'block'`) still consumed its seq. Drops therefore leave
16
+ * VISIBLE gaps in the delivered stream (honest loss accounting), and
17
+ * `'block'`-refused events delivered inline keep their true arrival
18
+ * stamp even though they overtake the queued backlog.
19
+ * - Monotonic, starts at 0, never reused for the lifetime of the queue.
20
+ *
21
+ * Enqueue outcomes:
22
+ * - `'queued'` — staged for the next flush (drop-oldest may have evicted
23
+ * an older event to make room; that loss is counted, never silent).
24
+ * - `'dropped'` — the event was sampled out at saturation. Lost; counted.
25
+ * - `'inline'` — `'block'` policy refused the enqueue. NOT lost: the
26
+ * caller (the dispatcher, Block 5) must deliver the returned envelope
27
+ * synchronously inline — blocking delivery by explicit consumer choice.
28
+ */
29
+ import { type CaptureChannel, type CaptureEnvelope, type CaptureHooks, type CapturePolicy } from '../capture/envelope.js';
30
+ import { type OverflowPolicy, type RingCounters } from './ring.js';
31
+ /** RFC-001 §5 default queue bound. */
32
+ export declare const DEFAULT_MAX_QUEUE = 10000;
33
+ /** One observer event to merge — {@link capture}'s request minus `seq`. */
34
+ export interface EnqueueInput {
35
+ readonly channel: CaptureChannel;
36
+ readonly method: string;
37
+ readonly runtimeStageId: string;
38
+ readonly runId: string;
39
+ /** LIVE payload — materialized per capture policy at enqueue time. */
40
+ readonly payload: unknown;
41
+ }
42
+ /** Fate of one enqueued event — see the module header. */
43
+ export type EnqueueOutcome = 'queued' | 'dropped' | 'inline';
44
+ export interface EnqueueResult {
45
+ /** The captured, seq-stamped envelope (built even when not queued). */
46
+ readonly envelope: CaptureEnvelope;
47
+ readonly outcome: EnqueueOutcome;
48
+ }
49
+ export interface MergedQueueOptions {
50
+ /** Ring capacity. Default {@link DEFAULT_MAX_QUEUE} (10 000). */
51
+ readonly maxQueue?: number;
52
+ /** Overflow policy at capacity. Default `'drop-oldest'`. */
53
+ readonly overflow?: OverflowPolicy;
54
+ /** `'sample'` only — admit 1 in this many saturated arrivals. */
55
+ readonly sampleEvery?: number;
56
+ /** Default capture policy when `enqueue` gets none. Default `'summary'`. */
57
+ readonly capturePolicy?: CapturePolicy;
58
+ /** Engine-free seams (dev-warn, clock) passed through to {@link capture}. */
59
+ readonly hooks?: CaptureHooks;
60
+ }
61
+ export declare class MergedQueue {
62
+ private readonly ring;
63
+ private readonly overflow;
64
+ private readonly defaultPolicy;
65
+ private readonly hooks?;
66
+ /** Arrival stamp — monotonic across ALL channels (see module header). */
67
+ private seq;
68
+ constructor(opts?: MergedQueueOptions);
69
+ /**
70
+ * Capture one event (seq-stamped at arrival) and stage it for deferred
71
+ * delivery. `policy` overrides the queue default per call — e.g. `'ref'`
72
+ * for payloads the caller proved immutable. Never throws.
73
+ */
74
+ enqueue(input: EnqueueInput, policy?: CapturePolicy): EnqueueResult;
75
+ /** Pop the oldest staged envelope (total arrival order across channels). */
76
+ shift(): CaptureEnvelope | undefined;
77
+ /** Current backlog. */
78
+ get depth(): number;
79
+ /** Ring capacity (the `maxQueue` bound). */
80
+ get capacity(): number;
81
+ /** The next seq to be assigned == total events captured so far. */
82
+ get nextSeq(): number;
83
+ /** Lifetime loss/delivery accounting — delegated to the ring. */
84
+ getCounters(): RingCounters;
85
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * observer-queue/ring.ts — RFC-001 Block 2: bounded ring with overflow policies.
3
+ *
4
+ * Pattern: Fixed-capacity circular buffer with explicit, COUNTED overflow
5
+ * behavior. The deferred-observer queue must never grow without
6
+ * bound (a slow consumer cannot OOM the producer), and must never
7
+ * lose an event silently (every loss increments `drops`).
8
+ * Role: Storage primitive under the merged queue (Block 3). Pure data
9
+ * structure — zero imports, zero engine knowledge, generic over T.
10
+ *
11
+ * Overflow policies (RFC-001 §5, with the accepted 'block' resolution):
12
+ * - `'drop-oldest'` — evict the oldest queued item to admit the new one.
13
+ * The evicted item is LOST (`drops`++) and returned on the push result
14
+ * so the caller can account for it. Sequence stamps on surviving items
15
+ * keep loss visible as seq gaps (honest loss accounting). DEFAULT
16
+ * posture for telemetry-grade delivery.
17
+ * - `'sample'` — while saturated, admit 1 in `sampleEvery` arrivals
18
+ * (evicting the oldest to make room — that eviction is also a counted
19
+ * loss); refuse the rest (each a counted loss). Keeps a thinned,
20
+ * still-fresh stream under sustained overload. The saturation counter
21
+ * is episode-scoped: it resets whenever a push succeeds through the
22
+ * non-full path.
23
+ * - `'block'` — the ring REFUSES the new item (`accepted: false`,
24
+ * `rejections`++) and drops NOTHING. In a single-threaded runtime a
25
+ * queue cannot literally block its producer; the dispatcher (Block 5)
26
+ * interprets a refusal as "deliver this event synchronously inline" —
27
+ * re-introducing blocking delivery by the consumer's EXPLICIT choice.
28
+ * Rejections are NOT losses: the event is still delivered (inline), so
29
+ * `drops` stays untouched.
30
+ *
31
+ * Conservation invariant (property-tested):
32
+ * pushes === delivered + drops + rejections + size
33
+ *
34
+ * CURSOR-READY (amendment A2): v1 consumes destructively through ONE cursor
35
+ * (`shift()` advances `head`). The designed v1.1 path keeps items in the
36
+ * ring and gives each listener its own read cursor; `head` then advances to
37
+ * `min(cursors)` (the reclaim watermark) instead of on read. The storage
38
+ * layout (contiguous circular window, `head` + `count`) already supports
39
+ * that — only the consumption surface changes. Documented, not implemented.
40
+ */
41
+ /** How the ring treats a push when it is at capacity (RFC-001 §5). */
42
+ export type OverflowPolicy = 'block' | 'drop-oldest' | 'sample';
43
+ export interface RingOptions {
44
+ /** Max queued items. Positive integer. */
45
+ readonly capacity: number;
46
+ /** Overflow behavior at capacity — see the module header. */
47
+ readonly policy: OverflowPolicy;
48
+ /**
49
+ * `'sample'` only: admit 1 in this many arrivals while saturated.
50
+ * Positive integer; default 10.
51
+ */
52
+ readonly sampleEvery?: number;
53
+ }
54
+ /** Outcome of one {@link BoundedRing.push}. */
55
+ export interface RingPushResult<T> {
56
+ /** True when the pushed item is now queued. */
57
+ readonly accepted: boolean;
58
+ /**
59
+ * The oldest item, when admitting the new one evicted it
60
+ * (`'drop-oldest'`, or a `'sample'` admission). Already counted in
61
+ * `drops` — surfaced so callers can do their own loss accounting.
62
+ */
63
+ readonly evicted?: T;
64
+ }
65
+ /** Monotonic counters — never reset for the lifetime of the ring. */
66
+ export interface RingCounters {
67
+ /** Total `push()` calls. */
68
+ readonly pushes: number;
69
+ /** `shift()` calls that returned an item. */
70
+ readonly delivered: number;
71
+ /** Items LOST — evictions plus sampled-out refusals. Never silent. */
72
+ readonly drops: number;
73
+ /** `'block'` refusals — NOT losses; the caller delivers these inline. */
74
+ readonly rejections: number;
75
+ }
76
+ export declare class BoundedRing<T> {
77
+ private readonly buffer;
78
+ private readonly policy;
79
+ private readonly sampleEvery;
80
+ /** Index of the oldest queued item — the single v1 cursor (see header). */
81
+ private head;
82
+ private count;
83
+ /** Arrivals seen while saturated in the current episode (`'sample'`). */
84
+ private saturatedArrivals;
85
+ private pushes;
86
+ private delivered;
87
+ private drops;
88
+ private rejections;
89
+ constructor(opts: RingOptions);
90
+ get size(): number;
91
+ get capacity(): number;
92
+ /** Lifetime counters — see {@link RingCounters}. */
93
+ getCounters(): RingCounters;
94
+ /** Admit, evict-and-admit, refuse, or sample per policy — never throws. */
95
+ push(item: T): RingPushResult<T>;
96
+ /** Pop the oldest queued item (FIFO). `undefined` when empty. */
97
+ shift(): T | undefined;
98
+ private store;
99
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "footprintjs",
3
- "version": "9.4.0",
3
+ "version": "9.5.0",
4
4
  "description": "Explainable backend flows — automatic causal traces, decision evidence, and MCP tool generation for AI agents",
5
5
  "license": "MIT",
6
6
  "author": "Sanjay Krishna Anbalagan",