@deepseek-ai/dsh-session 0.1.1-rc.2 → 0.1.2-alpha.2

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.
@@ -12,8 +12,11 @@
12
12
  * in `./types.ts`): such a log was likely written by a newer harness, and
13
13
  * silently skipping a required event would reconstruct a wrong session.
14
14
  * Downstream (out-of-repo) plugin events are outside this list by
15
- * construction; a registration surface for them is deferred until such a
16
- * consumer exists.
15
+ * construction. The persisted `SessionEvent.ignorable` marker is the
16
+ * compatibility mechanism; event-name registration was rejected because
17
+ * it does not classify omission safety and would make reads
18
+ * composition-dependent. The rationale is in
19
+ * `.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md`.
17
20
  */
18
21
  export declare const KNOWN_SESSION_EVENT_TYPES: ReadonlySet<string>;
19
22
  //# sourceMappingURL=known-event-types.d.ts.map
@@ -12,8 +12,11 @@
12
12
  * in `./types.ts`): such a log was likely written by a newer harness, and
13
13
  * silently skipping a required event would reconstruct a wrong session.
14
14
  * Downstream (out-of-repo) plugin events are outside this list by
15
- * construction; a registration surface for them is deferred until such a
16
- * consumer exists.
15
+ * construction. The persisted `SessionEvent.ignorable` marker is the
16
+ * compatibility mechanism; event-name registration was rejected because
17
+ * it does not classify omission safety and would make reads
18
+ * composition-dependent. The rationale is in
19
+ * `.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md`.
17
20
  */
18
21
  export const KNOWN_SESSION_EVENT_TYPES = new Set([
19
22
  'agent-preset/selected',
@@ -35,18 +38,21 @@ export const KNOWN_SESSION_EVENT_TYPES = new Set([
35
38
  'hook/result',
36
39
  'llm/retry',
37
40
  'llm/retry-started',
41
+ 'model/selection',
38
42
  'permission/preset',
39
43
  'plan/mode',
40
44
  'request/context',
41
45
  'request/header',
42
46
  'sandbox/mode',
43
47
  'schedule/change',
48
+ 'session-log-deepseek/delivery-accepted',
44
49
  'session/end-seed',
45
50
  'session/title',
46
51
  'session/title-llm-request',
47
52
  'step/end',
48
53
  'step/start',
49
54
  'subagent/descriptor',
55
+ 'subagent/model-selection-policy',
50
56
  'team/member',
51
57
  'team/message/delivered',
52
58
  'team/message/queued',
@@ -4,7 +4,8 @@
4
4
  * needed to resume with a provider-valid transcript.
5
5
  * @module @deepseek-ai/dsh-session/repair
6
6
  */
7
- import { MessageId, freezeMessage } from '@deepseek-ai/dsh-llm';
7
+ import { brandString } from '@deepseek-ai/dsh-brand';
8
+ import { deepFreeze } from '@deepseek-ai/dsh-util-values';
8
9
  /** Recovery code for an assistant tool request that never reached a recorded call start. */
9
10
  export const TOOL_NOT_STARTED = 'TOOL_NOT_STARTED';
10
11
  /** Recovery code for a recorded tool call whose completed outcome was not durably recorded. */
@@ -83,8 +84,8 @@ export function interruptedTurnClosers(events) {
83
84
  // and Map insertion order preserves their transcript order.
84
85
  for (const [callId, { step, callSeq }] of pendingCalls) {
85
86
  const started = callSeq !== undefined;
86
- const message = freezeMessage({
87
- id: MessageId(`interrupted-tool-result-${callId}-${seq}`),
87
+ const message = deepFreeze({
88
+ id: brandString(`interrupted-tool-result-${callId}-${seq}`),
88
89
  role: 'user',
89
90
  source: { kind: 'tool', callId },
90
91
  content: [{
@@ -0,0 +1,17 @@
1
+ /** Lossless range encoding for JSONL `sourceEventSeqs` arrays. */
2
+ /** A stored source sequence or inclusive consecutive range. */
3
+ export type EncodedSeq = number | [number, number];
4
+ /**
5
+ * Replace profitable consecutive runs with inclusive pairs.
6
+ * @param values - validated in-memory source sequences.
7
+ * @returns a lossless JSON storage form.
8
+ */
9
+ export declare function encodeSeqRanges(values: readonly number[]): EncodedSeq[];
10
+ /**
11
+ * Expand a JSON storage-form source sequence array.
12
+ * @param value - parsed storage value.
13
+ * @param maxEntries - largest list permitted by the owning event.
14
+ * @returns the in-memory source sequences.
15
+ */
16
+ export declare function decodeSeqRanges(value: unknown, maxEntries?: number): number[];
17
+ //# sourceMappingURL=seq-ranges.d.ts.map
@@ -0,0 +1,73 @@
1
+ /** Lossless range encoding for JSONL `sourceEventSeqs` arrays. */
2
+ function isStrictlyIncreasing(values) {
3
+ return values.every((value, index) => index === 0 || value > values[index - 1]);
4
+ }
5
+ /**
6
+ * Replace profitable consecutive runs with inclusive pairs.
7
+ * @param values - validated in-memory source sequences.
8
+ * @returns a lossless JSON storage form.
9
+ */
10
+ export function encodeSeqRanges(values) {
11
+ if (!isStrictlyIncreasing(values))
12
+ return [...values];
13
+ const encoded = [];
14
+ for (let start = 0; start < values.length;) {
15
+ let end = start;
16
+ while (end + 1 < values.length && values[end + 1] === values[end] + 1)
17
+ end += 1;
18
+ if (end - start >= 2)
19
+ encoded.push([values[start], values[end]]);
20
+ else
21
+ for (let index = start; index <= end; index += 1)
22
+ encoded.push(values[index]);
23
+ start = end + 1;
24
+ }
25
+ return encoded;
26
+ }
27
+ /**
28
+ * Expand a JSON storage-form source sequence array.
29
+ * @param value - parsed storage value.
30
+ * @param maxEntries - largest list permitted by the owning event.
31
+ * @returns the in-memory source sequences.
32
+ */
33
+ export function decodeSeqRanges(value, maxEntries = Number.MAX_SAFE_INTEGER) {
34
+ if (!Array.isArray(value))
35
+ throw new TypeError('sourceEventSeqs must be an array');
36
+ const decoded = [];
37
+ let hasRange = false;
38
+ for (const entry of value) {
39
+ if (typeof entry === 'number') {
40
+ assertSeq(entry);
41
+ if (decoded.length >= maxEntries)
42
+ throw new TypeError('sourceEventSeqs exceeds its event sequence');
43
+ decoded.push(entry);
44
+ continue;
45
+ }
46
+ if (!Array.isArray(entry) || entry.length !== 2) {
47
+ throw new TypeError('sourceEventSeqs range entries must be [start, end] pairs');
48
+ }
49
+ const start = entry[0];
50
+ const end = entry[1];
51
+ assertSeq(start);
52
+ assertSeq(end);
53
+ if (end < start)
54
+ throw new TypeError('sourceEventSeqs ranges require start <= end');
55
+ const length = end - start + 1;
56
+ if (length > maxEntries - decoded.length) {
57
+ throw new TypeError('sourceEventSeqs range exceeds its event sequence');
58
+ }
59
+ for (let seq = start; seq <= end; seq += 1)
60
+ decoded.push(seq);
61
+ hasRange = true;
62
+ }
63
+ if (hasRange && !isStrictlyIncreasing(decoded)) {
64
+ throw new TypeError('sourceEventSeqs ranges must be strictly increasing');
65
+ }
66
+ return decoded;
67
+ }
68
+ function assertSeq(value) {
69
+ if (!Number.isSafeInteger(value) || value < 0) {
70
+ throw new TypeError('sourceEventSeqs must contain non-negative safe integers');
71
+ }
72
+ }
73
+ //# sourceMappingURL=seq-ranges.js.map
@@ -1,13 +1,12 @@
1
- import type { Branded } from '@deepseek-ai/dsh-brand';
2
- import type { AssistantMessage, CallId, LlmCallConfig, LlmCallConfigAdapterDefaults, LlmFailure, StreamChunk, TokenUsage, ToolResultMessage, ToolSchema, UserMessage } from '@deepseek-ai/dsh-llm';
3
- import type { JsonValue } from './json.ts';
4
- export type { JsonValue } from './json.ts';
1
+ import { type Branded } from '@deepseek-ai/dsh-brand';
2
+ import type { AssistantMessage, ToolCallId, LlmCallConfig, LlmCallConfigAdapterDefaults, LlmFailure, StreamChunk, TokenUsage, ToolResultMessage, ToolSchema, UserMessage } from '@deepseek-ai/dsh-llm';
3
+ import type { JsonValue } from '@deepseek-ai/dsh-util-values';
5
4
  /** Identifies one session in the store (and its persistence artifacts). */
6
5
  export type SessionId = Branded<'SessionId'>;
7
6
  /**
8
7
  * Brand a string as a {@link SessionId}.
9
8
  * @param id - the raw session id string.
10
- * @returns the same string, branded (a compile-time cast — no runtime cost).
9
+ * @returns the same string with the session-id brand.
11
10
  */
12
11
  export declare function SessionId(id: string): SessionId;
13
12
  /**
@@ -167,22 +166,6 @@ export interface TurnEndReasonMap {
167
166
  }
168
167
  /** The union over {@link TurnEndReasonMap} — why a turn ended; plugins extend it by merging variants into the map. */
169
168
  export type TurnEndReason = TurnEndReasonMap[keyof TurnEndReasonMap];
170
- /**
171
- * One entry in an agent's todo list — the unit of the `todo/write`
172
- * {@link SessionEventMap} event's whole-list snapshot.
173
- *
174
- * Deliberately minimal: a human-readable `content` line and a three-state
175
- * `status`. No id, priority, or `activeForm` — the list is replaced wholesale
176
- * on every write (last-write-wins), so entries need no stable identity. The
177
- * three statuses describe the complete portable lifecycle needed by model and
178
- * UI consumers.
179
- */
180
- export interface TodoItem {
181
- /** What this task is — a short imperative line shown in the UI. */
182
- content: string;
183
- /** Lifecycle state. `in_progress` marks a task being worked now; parallel work may mark several. */
184
- status: 'pending' | 'in_progress' | 'completed';
185
- }
186
169
  /**
187
170
  * Logged request state outside derived history: call config, system prompt, and
188
171
  * tools. The latest full `request/header` snapshot reconstructs it; canonical
@@ -211,9 +194,11 @@ export interface RequestContext {
211
194
  * Why a `request/header` snapshot was appended: `'initial'` — the log's first
212
195
  * header (a new conversation); `'resume'` — a loop instance's first request
213
196
  * over a log that already has header events (process restart, fork seed);
214
- * `'change'` — a later request used a different header.
197
+ * `'change'` — a later request used a different header, with `startsSeries`
198
+ * preserving a coincident series boundary; `'series'` — an unchanged header
199
+ * began an explicitly distinct message series or followed a surface replacement.
215
200
  */
216
- export type RequestHeaderReason = 'initial' | 'resume' | 'change';
201
+ export type RequestHeaderReason = 'initial' | 'resume' | 'change' | 'series';
217
202
  /**
218
203
  * The merge-extensible, append-only source of truth for an agent interaction.
219
204
  * Message history is derived from this log. Every event is lossless JSON and
@@ -291,7 +276,7 @@ export interface SessionEventMap {
291
276
  'tool/call': {
292
277
  turn: number;
293
278
  step: number;
294
- callId: CallId;
279
+ callId: ToolCallId;
295
280
  name: string;
296
281
  arguments: string;
297
282
  };
@@ -316,10 +301,6 @@ export interface SessionEventMap {
316
301
  };
317
302
  meta?: JsonValue;
318
303
  };
319
- /** Whole-list snapshot; latest write wins on replay. Log-only UI state; never derived history. */
320
- 'todo/write': {
321
- todos: TodoItem[];
322
- };
323
304
  /**
324
305
  * Full header for the next request, appended inside its step before dispatch.
325
306
  * It is log-only; the latest snapshot reconstructs the request header.
@@ -327,6 +308,8 @@ export interface SessionEventMap {
327
308
  'request/header': {
328
309
  header: EpochHeader;
329
310
  reason: RequestHeaderReason;
311
+ /** A changed header also begins a distinct model-message series. */
312
+ startsSeries?: true;
330
313
  };
331
314
  /**
332
315
  * Route metadata for the next request, logged only when the route or capacity
@@ -455,4 +438,12 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
455
438
  surfaceOp?: SurfaceOp;
456
439
  } : object);
457
440
  }[T];
441
+ declare module '@deepseek-ai/dsh-typert-protocol' {
442
+ interface RemoteErrorDetailsMap {
443
+ /** The named Session does not exist; produced by every layer that resolves a SessionId. */
444
+ 'session/not-found': {
445
+ readonly sessionId: SessionId;
446
+ };
447
+ }
448
+ }
458
449
  //# sourceMappingURL=types.d.ts.map
@@ -1,10 +1,11 @@
1
+ import { brandString } from '@deepseek-ai/dsh-brand';
1
2
  /**
2
3
  * Brand a string as a {@link SessionId}.
3
4
  * @param id - the raw session id string.
4
- * @returns the same string, branded (a compile-time cast — no runtime cost).
5
+ * @returns the same string with the session-id brand.
5
6
  */
6
7
  export function SessionId(id) {
7
- return id;
8
+ return brandString(id);
8
9
  }
9
10
  /**
10
11
  * The on-disk session format version, stamped into every newly-written {@link SessionHeader}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-session",
3
3
  "description": "Event-sourced session store for the DeepSeek Harness",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -26,6 +26,10 @@
26
26
  "types": "./lib/types/types.d.ts",
27
27
  "default": "./lib/types/types.js"
28
28
  },
29
+ "./chunk-rows": {
30
+ "types": "./lib/types/chunk-rows.d.ts",
31
+ "default": "./lib/types/chunk-rows.js"
32
+ },
29
33
  "./src/*": "./src/*",
30
34
  "./package.json": "./package.json",
31
35
  "./surface": {
@@ -41,20 +45,19 @@
41
45
  ],
42
46
  "license": "MIT",
43
47
  "peerDependencies": {
44
- "@deepseek-ai/dsh-brand": "^0.1.1-rc.2",
45
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
46
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
47
- "@deepseek-ai/dsh-scope": "^0.1.1-rc.2",
48
- "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2",
49
- "@deepseek-ai/cordis": "^4.0.1"
48
+ "@deepseek-ai/dsh-scope": "^0.1.2-alpha.2",
49
+ "@deepseek-ai/cordis": "^4.0.2"
50
50
  },
51
51
  "devDependencies": {
52
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
53
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
54
- "@deepseek-ai/dsh-scope": "^0.1.1-rc.2",
55
- "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2",
56
- "@deepseek-ai/dsh-typert-registry": "^0.1.1-rc.2",
57
- "@deepseek-ai/dsh-brand": "^0.1.1-rc.2",
58
- "@deepseek-ai/cordis": "^4.0.1"
52
+ "@deepseek-ai/dsh-scope": "^0.1.2-alpha.2",
53
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.2-alpha.2",
54
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
55
+ "@deepseek-ai/cordis": "^4.0.2",
56
+ "@deepseek-ai/dsh-typert-registry": "^0.1.2-alpha.2"
57
+ },
58
+ "dependencies": {
59
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
60
+ "@deepseek-ai/dsh-brand": "^0.1.2-alpha.2",
61
+ "@deepseek-ai/dsh-util-values": "^0.1.2-alpha.2"
59
62
  }
60
63
  }
@@ -1,36 +0,0 @@
1
- /** Lossless-JSON validation and detached snapshots for durable session data. @module @deepseek-ai/dsh-session/json */
2
- /**
3
- * A value that round-trips losslessly through JSON: `null`, a boolean, a finite
4
- * number other than negative zero, a string, an array of such values, or a
5
- * plain object whose values are such values. Arrays may carry only their dense
6
- * indexed elements; extra own properties would be discarded by JSON. TypeScript
7
- * cannot distinguish `-0` from `number`, so {@link isJsonValue} and
8
- * {@link snapshotJsonValue} enforce these details at runtime. Use this type for
9
- * a payload that must survive session-log persistence and replay byte-identically
10
- * — e.g. a tool's private presentation `meta`.
11
- */
12
- export type JsonValue = null | boolean | number | string | JsonValue[] | {
13
- [key: string]: JsonValue;
14
- };
15
- /**
16
- * Validate and detach lossless JSON in one read per property, so a stateful
17
- * getter cannot change between validation and copying. Traversal is iterative,
18
- * so valid nesting is bounded by available memory rather than the JavaScript
19
- * call stack. Accepts ordinary arrays, plain or null-prototype objects, and JSON
20
- * scalars; rejects sparse, cyclic, exotic, negative-zero, and non-finite values.
21
- * Getter throws propagate.
22
- *
23
- * @param value - the candidate value to validate and detach.
24
- * @returns the detached snapshot, or `undefined` when the value is not
25
- * losslessly JSON-serializable.
26
- */
27
- export declare function snapshotJsonValue<T>(value: T): T | undefined;
28
- /**
29
- * Test the same lossless JSON boundary as {@link snapshotJsonValue} without
30
- * detaching it. Only own enumerable string properties participate; `toJSON`
31
- * is ignored and getters run, so persistence boundaries use the snapshotter.
32
- * @param value - the candidate event data to test.
33
- * @returns whether `value` survives JSON round-trip losslessly.
34
- */
35
- export declare function isJsonValue(value: unknown): boolean;
36
- //# sourceMappingURL=json.d.ts.map
package/lib/types/json.js DELETED
@@ -1,174 +0,0 @@
1
- /** Lossless-JSON validation and detached snapshots for durable session data. @module @deepseek-ai/dsh-session/json */
2
- /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
3
- function hasIntrinsicConstructor(prototype, name) {
4
- const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor');
5
- const constructor = descriptor?.value;
6
- if (typeof constructor !== 'function')
7
- return false;
8
- try {
9
- return constructor.name === name
10
- && constructor.prototype === prototype
11
- && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
12
- }
13
- catch {
14
- return false;
15
- }
16
- }
17
- /** Whether a candidate is one realm's intrinsic `Object.prototype`. */
18
- function isIntrinsicObjectPrototype(value) {
19
- return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, 'Object');
20
- }
21
- /** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
22
- function hasPlainArrayPrototype(value) {
23
- const prototype = Object.getPrototypeOf(value);
24
- if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, 'Array'))
25
- return false;
26
- const objectPrototype = Object.getPrototypeOf(prototype);
27
- return typeof objectPrototype === 'object'
28
- && objectPrototype !== null
29
- && isIntrinsicObjectPrototype(objectPrototype);
30
- }
31
- /** Whether an object is a plain or null-prototype record from any JavaScript realm. */
32
- function hasPlainObjectPrototype(value) {
33
- const prototype = Object.getPrototypeOf(value);
34
- return prototype === null
35
- || typeof prototype === 'object' && isIntrinsicObjectPrototype(prototype);
36
- }
37
- /** Return every JSON-visible object key, or reject own data JSON would discard. */
38
- function enumerableStringKeys(value) {
39
- const keys = Reflect.ownKeys(value);
40
- if (keys.some(key => typeof key !== 'string' || !Object.prototype.propertyIsEnumerable.call(value, key)))
41
- return undefined;
42
- return keys;
43
- }
44
- /** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
45
- function walkJsonValue(value, detach) {
46
- const ancestors = new Set();
47
- let root;
48
- const assign = (destination, item) => {
49
- if (destination === undefined)
50
- return;
51
- if (destination.kind === 'root') {
52
- root = item;
53
- }
54
- else if (destination.kind === 'array') {
55
- destination.target[destination.index] = item;
56
- }
57
- else {
58
- Object.defineProperty(destination.target, destination.key, {
59
- value: item,
60
- enumerable: true,
61
- configurable: true,
62
- writable: true,
63
- });
64
- }
65
- };
66
- const tasks = [{
67
- kind: 'visit',
68
- value,
69
- ...(detach ? { destination: { kind: 'root' } } : {}),
70
- }];
71
- for (let task = tasks.pop(); task !== undefined; task = tasks.pop()) {
72
- if (task.kind === 'leave') {
73
- ancestors.delete(task.source);
74
- continue;
75
- }
76
- if (task.kind === 'array-item') {
77
- if (!Object.prototype.hasOwnProperty.call(task.source, task.index))
78
- return undefined;
79
- tasks.push({
80
- kind: 'visit',
81
- value: task.source[task.index],
82
- ...(task.target === undefined ? {} : { destination: { kind: 'array', target: task.target, index: task.index } }),
83
- });
84
- continue;
85
- }
86
- if (task.kind === 'object-property') {
87
- tasks.push({
88
- kind: 'visit',
89
- value: task.source[task.key],
90
- ...(task.target === undefined ? {} : { destination: { kind: 'object', target: task.target, key: task.key } }),
91
- });
92
- continue;
93
- }
94
- const current = task.value;
95
- if (current === null) {
96
- assign(task.destination, null);
97
- continue;
98
- }
99
- if (typeof current === 'boolean' || typeof current === 'string') {
100
- assign(task.destination, current);
101
- continue;
102
- }
103
- if (typeof current === 'number') {
104
- if (!Number.isFinite(current) || Object.is(current, -0))
105
- return undefined;
106
- assign(task.destination, current);
107
- continue;
108
- }
109
- if (typeof current !== 'object')
110
- return undefined;
111
- if (ancestors.has(current))
112
- return undefined;
113
- if (Array.isArray(current)) {
114
- if (!hasPlainArrayPrototype(current))
115
- return undefined;
116
- const length = current.length;
117
- if (Reflect.ownKeys(current).length !== length + 1)
118
- return undefined;
119
- const target = detach ? [] : undefined;
120
- if (target !== undefined)
121
- assign(task.destination, target);
122
- ancestors.add(current);
123
- tasks.push({ kind: 'leave', source: current });
124
- for (let index = length - 1; index >= 0; index--) {
125
- tasks.push({ kind: 'array-item', source: current, index, ...(target === undefined ? {} : { target }) });
126
- }
127
- continue;
128
- }
129
- if (!hasPlainObjectPrototype(current))
130
- return undefined;
131
- const keys = enumerableStringKeys(current);
132
- if (keys === undefined)
133
- return undefined;
134
- const target = detach ? {} : undefined;
135
- if (target !== undefined)
136
- assign(task.destination, target);
137
- ancestors.add(current);
138
- tasks.push({ kind: 'leave', source: current });
139
- for (let index = keys.length - 1; index >= 0; index--) {
140
- const key = keys[index];
141
- /* v8 ignore next -- the loop is bounded by the captured key count. */
142
- if (key === undefined)
143
- return undefined;
144
- tasks.push({ kind: 'object-property', source: current, key, ...(target === undefined ? {} : { target }) });
145
- }
146
- }
147
- return detach ? root : true;
148
- }
149
- /**
150
- * Validate and detach lossless JSON in one read per property, so a stateful
151
- * getter cannot change between validation and copying. Traversal is iterative,
152
- * so valid nesting is bounded by available memory rather than the JavaScript
153
- * call stack. Accepts ordinary arrays, plain or null-prototype objects, and JSON
154
- * scalars; rejects sparse, cyclic, exotic, negative-zero, and non-finite values.
155
- * Getter throws propagate.
156
- *
157
- * @param value - the candidate value to validate and detach.
158
- * @returns the detached snapshot, or `undefined` when the value is not
159
- * losslessly JSON-serializable.
160
- */
161
- export function snapshotJsonValue(value) {
162
- return walkJsonValue(value, true);
163
- }
164
- /**
165
- * Test the same lossless JSON boundary as {@link snapshotJsonValue} without
166
- * detaching it. Only own enumerable string properties participate; `toJSON`
167
- * is ignored and getters run, so persistence boundaries use the snapshotter.
168
- * @param value - the candidate event data to test.
169
- * @returns whether `value` survives JSON round-trip losslessly.
170
- */
171
- export function isJsonValue(value) {
172
- return walkJsonValue(value, false) === true;
173
- }
174
- //# sourceMappingURL=json.js.map