@smithers-orchestrator/devtools 0.16.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,143 @@
1
+ /** @typedef {import("./DevToolsNode.ts").DevToolsNode} DevToolsNode */
2
+ /** @typedef {import("./SnapshotSerializerOptions.ts").SnapshotSerializerOptions} SnapshotSerializerOptions */
3
+ /** @typedef {import("./SnapshotSerializerWarning.ts").SnapshotSerializerWarning} SnapshotSerializerWarning */
4
+
5
+ import { SNAPSHOT_SERIALIZER_DEFAULT_MAX_DEPTH } from "./SNAPSHOT_SERIALIZER_DEFAULT_MAX_DEPTH.js";
6
+
7
+ const SNAPSHOT_SERIALIZER_DEFAULT_MAX_ENTRIES = 100_000;
8
+
9
+ /**
10
+ * @param {unknown} value
11
+ * @returns {value is Record<string, unknown>}
12
+ */
13
+ function isPlainObject(value) {
14
+ if (!value || typeof value !== "object") {
15
+ return false;
16
+ }
17
+ const proto = Object.getPrototypeOf(value);
18
+ return proto === Object.prototype || proto === null;
19
+ }
20
+
21
+ /**
22
+ * @param {SnapshotSerializerWarning["code"]} code
23
+ * @param {string} path
24
+ * @param {SnapshotSerializerOptions["onWarning"]} onWarning
25
+ * @param {string} [detail]
26
+ */
27
+ function warn(code, path, onWarning, detail) {
28
+ if (!onWarning) {
29
+ return;
30
+ }
31
+ onWarning({
32
+ code,
33
+ path,
34
+ ...(detail ? { detail } : {}),
35
+ });
36
+ }
37
+
38
+ /**
39
+ * @param {unknown} value
40
+ * @param {{
41
+ * maxDepth: number;
42
+ * maxEntries: number;
43
+ * onWarning?: (warning: SnapshotSerializerWarning) => void;
44
+ * seen: WeakSet<object>;
45
+ * traversed: number;
46
+ * }} state
47
+ * @param {number} depth
48
+ * @param {string} path
49
+ * @returns {unknown}
50
+ */
51
+ function serializeInternal(value, state, depth, path) {
52
+ if (depth > state.maxDepth) {
53
+ warn("MaxDepthExceeded", path, state.onWarning);
54
+ return "[MaxDepth]";
55
+ }
56
+ if (value === null || value === undefined) {
57
+ return value;
58
+ }
59
+ const valueType = typeof value;
60
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") {
61
+ return value;
62
+ }
63
+ if (valueType === "bigint") {
64
+ return `[BigInt: ${value.toString()}]`;
65
+ }
66
+ if (valueType === "function") {
67
+ return "[Function]";
68
+ }
69
+ if (valueType === "symbol") {
70
+ return value.description
71
+ ? `[Symbol: ${value.description}]`
72
+ : "[Symbol]";
73
+ }
74
+ if (value instanceof Date) {
75
+ return Number.isNaN(value.getTime())
76
+ ? "[Date: Invalid]"
77
+ : `[Date: ${value.toISOString()}]`;
78
+ }
79
+ if (valueType !== "object") {
80
+ return String(value);
81
+ }
82
+ if (state.traversed >= state.maxEntries) {
83
+ warn("MaxEntriesExceeded", path, state.onWarning);
84
+ return "[MaxEntries]";
85
+ }
86
+ state.traversed += 1;
87
+ const objectValue = /** @type {object} */ (value);
88
+ if (state.seen.has(objectValue)) {
89
+ warn("CircularReference", path, state.onWarning);
90
+ return "[Circular]";
91
+ }
92
+ state.seen.add(objectValue);
93
+ try {
94
+ if (Array.isArray(value)) {
95
+ return value.map((entry, index) => serializeInternal(entry, state, depth + 1, `${path}[${index}]`));
96
+ }
97
+ if (!isPlainObject(value)) {
98
+ const ctorName = value.constructor?.name;
99
+ if (ctorName && ctorName !== "Object") {
100
+ warn("UnsupportedType", path, state.onWarning, ctorName);
101
+ return `[${ctorName}]`;
102
+ }
103
+ }
104
+ /** @type {Record<string, unknown>} */
105
+ const out = {};
106
+ for (const key of Object.keys(/** @type {Record<string, unknown>} */ (value)).sort()) {
107
+ try {
108
+ out[key] = serializeInternal(/** @type {Record<string, unknown>} */ (value)[key], state, depth + 1, `${path}.${key}`);
109
+ }
110
+ catch {
111
+ warn("UnsupportedType", `${path}.${key}`, state.onWarning, "ThrownDuringRead");
112
+ out[key] = "[Unserializable]";
113
+ }
114
+ }
115
+ return out;
116
+ }
117
+ finally {
118
+ state.seen.delete(objectValue);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Serialize arbitrary values into a stable JSON-safe shape for devtools snapshots.
124
+ *
125
+ * @param {unknown} value
126
+ * @param {SnapshotSerializerOptions} [options]
127
+ * @returns {unknown}
128
+ */
129
+ export function snapshotSerialize(value, options = {}) {
130
+ const maxDepth = Number.isFinite(options.maxDepth)
131
+ ? Math.max(0, Math.floor(options.maxDepth))
132
+ : SNAPSHOT_SERIALIZER_DEFAULT_MAX_DEPTH;
133
+ const maxEntries = Number.isFinite(options.maxEntries)
134
+ ? Math.max(1, Math.floor(options.maxEntries))
135
+ : SNAPSHOT_SERIALIZER_DEFAULT_MAX_ENTRIES;
136
+ return serializeInternal(value, {
137
+ maxDepth,
138
+ maxEntries,
139
+ onWarning: options.onWarning,
140
+ seen: new WeakSet(),
141
+ traversed: 0,
142
+ }, 0, "$");
143
+ }