@logbrew/sdk 0.1.37 → 0.1.39

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 (103) hide show
  1. package/automatic-delivery.cjs +271 -0
  2. package/batch-sender.cjs +164 -0
  3. package/bounded-number.cjs +34 -0
  4. package/browser-breadcrumbs.cjs +346 -136
  5. package/capture-support.cjs +100 -0
  6. package/client-options.cjs +292 -0
  7. package/client-report.cjs +232 -0
  8. package/client.cjs +447 -0
  9. package/console-arguments.cjs +151 -0
  10. package/console-capture.cjs +194 -0
  11. package/core.cjs +35 -2984
  12. package/delivery-health.cjs +133 -0
  13. package/error-causes.cjs +130 -0
  14. package/error-details.cjs +124 -0
  15. package/error-issue.cjs +187 -0
  16. package/event-clone.cjs +55 -0
  17. package/event-metadata.cjs +40 -0
  18. package/event-pipeline.cjs +325 -0
  19. package/event-queue.cjs +306 -0
  20. package/event-store.cjs +237 -0
  21. package/event-validation.cjs +159 -0
  22. package/examples/agent-timeline.cjs +57 -46
  23. package/examples/agent-timeline.mjs +41 -35
  24. package/examples/index.mjs +122 -94
  25. package/examples/readme-example.cjs +36 -27
  26. package/examples/readme-example.mjs +21 -17
  27. package/examples/real-user-smoke.cjs +41 -32
  28. package/examples/real-user-smoke.mjs +26 -22
  29. package/exception-chain.cjs +205 -0
  30. package/index.d.ts +144 -1076
  31. package/index.js +1 -1
  32. package/issue-breadcrumbs.cjs +143 -0
  33. package/issue-diagnostics.cjs +94 -582
  34. package/issue-evidence.cjs +256 -0
  35. package/issue-exception.cjs +268 -0
  36. package/issue-stack-frames.cjs +220 -0
  37. package/issue-stack-text.cjs +101 -0
  38. package/issue-stack.cjs +188 -225
  39. package/issue-text.cjs +72 -0
  40. package/log-context.cjs +87 -67
  41. package/metric-validation.cjs +95 -0
  42. package/network-milestone.cjs +155 -0
  43. package/null-value.cjs +4 -0
  44. package/opentelemetry-bridge.cjs +330 -0
  45. package/opentelemetry-context.cjs +245 -0
  46. package/opentelemetry-exceptions.cjs +69 -0
  47. package/opentelemetry-keys.cjs +183 -0
  48. package/opentelemetry-span.cjs +286 -0
  49. package/opentelemetry-summary.cjs +213 -0
  50. package/opentelemetry-time.cjs +131 -0
  51. package/opentelemetry.cjs +99 -1068
  52. package/package.json +64 -1
  53. package/pino-destination.cjs +108 -0
  54. package/pino-record.cjs +237 -0
  55. package/product-action.cjs +163 -0
  56. package/provided-breadcrumbs.cjs +117 -0
  57. package/random-hex.cjs +29 -0
  58. package/react-native.js +1 -1
  59. package/recording-transport.cjs +68 -0
  60. package/release-artifacts-build.cjs +263 -84
  61. package/release-artifacts-common.js +347 -66
  62. package/release-artifacts-debug-id.js +178 -0
  63. package/release-artifacts-inspect.js +213 -0
  64. package/release-artifacts-issue-event.js +99 -0
  65. package/release-artifacts-manifest-artifact.js +107 -0
  66. package/release-artifacts-manifest.js +77 -0
  67. package/release-artifacts-mappings.js +265 -0
  68. package/release-artifacts-prepare.js +297 -0
  69. package/release-artifacts-source-context.js +210 -0
  70. package/release-artifacts-symbolication.js +272 -421
  71. package/release-artifacts-upload-files.js +122 -0
  72. package/release-artifacts-upload-request.js +246 -0
  73. package/release-artifacts-upload.js +155 -300
  74. package/release-artifacts-urls.js +49 -0
  75. package/release-artifacts.js +189 -662
  76. package/retry-delay.cjs +32 -0
  77. package/sdk-error.cjs +36 -0
  78. package/span-validation.cjs +191 -0
  79. package/stored-events.cjs +243 -0
  80. package/support-ticket.cjs +232 -122
  81. package/telemetry-context.cjs +343 -251
  82. package/trace-context.cjs +376 -179
  83. package/transport-error.cjs +23 -0
  84. package/types/client.d.ts +67 -0
  85. package/types/delivery.d.ts +125 -0
  86. package/types/issues.d.ts +249 -0
  87. package/types/logging.d.ts +175 -0
  88. package/types/opentelemetry.d.ts +205 -0
  89. package/types/product.d.ts +128 -0
  90. package/types/recording-transport.d.ts +15 -0
  91. package/types/sdk-error.d.ts +9 -0
  92. package/types/telemetry.d.ts +171 -0
  93. package/types/trace-context.d.ts +110 -0
  94. package/types/transport-error.d.ts +10 -0
  95. package/utf8.cjs +37 -0
  96. package/validation.cjs +327 -0
  97. package/vite-release-artifacts-config.cjs +120 -0
  98. package/vite-release-artifacts.cjs +199 -99
  99. package/vite-release-artifacts.d.cts +65 -40
  100. package/vite-release-artifacts.d.ts +65 -40
  101. package/vite-release-artifacts.js +2 -2
  102. package/winston-info.cjs +205 -0
  103. package/winston.cjs +189 -290
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+
3
+ const { SdkError } = require("./sdk-error.cjs");
4
+ const { boundedIdentity, optionalText } = require("./issue-text.cjs");
5
+ const { optionalEntry, rejectUnknownKeys, requireObject } = require("./validation.cjs");
6
+
7
+ /**
8
+ * @import { IssueDiagnosticEvidence, IssueImpactEvidence, IssueLikelyFixArea } from "./index.cjs"
9
+ * @import { DeepReadonly } from "./validation.cjs"
10
+ * @typedef {"capturedFields" | "missingFields" | "redactedFields" | "truncatedFields"} EvidenceFieldKey
11
+ * @typedef {Pick<IssueDiagnosticEvidence, EvidenceFieldKey>} EvidenceFieldLists
12
+ */
13
+
14
+ const MAX_DIAGNOSTIC_IDENTITY_LENGTH = 256;
15
+ const MAX_DIAGNOSTIC_CAUSE_LENGTH = 1024;
16
+ const MAX_DIAGNOSTIC_OUTCOME_LENGTH = 512;
17
+ const MAX_DIAGNOSTIC_FIELDS = 32;
18
+ const MIN_DIAGNOSTIC_FIELDS = 1;
19
+ const MIN_COORDINATE = 1;
20
+ const MAX_COORDINATE = 2_147_483_647;
21
+ const NO_FIELDS = 0;
22
+ const EVIDENCE_FIELD_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/u;
23
+ const WINDOWS_DRIVE_PATTERN = /^[A-Za-z]:\//u;
24
+ /** @type {readonly EvidenceFieldKey[]} */
25
+ const EVIDENCE_FIELD_KEYS = ["capturedFields", "missingFields", "redactedFields", "truncatedFields"];
26
+ const EVIDENCE_KEYS = new Set(["likelyRootCause", "likelyFixArea", "impact", ...EVIDENCE_FIELD_KEYS]);
27
+ const FIX_AREA_KEYS = new Set(["component", "module", "function", "file", "line", "column", "inApp"]);
28
+ const IMPACT_KEYS = new Set(["affectedUserSegment", "failedAction", "userVisibleOutcome"]);
29
+ /** @type {readonly ["component", "module", "function"]} */
30
+ const FIX_AREA_IDENTITY_KEYS = ["component", "module", "function"];
31
+ /** @type {readonly ["affectedUserSegment", "failedAction"]} */
32
+ const IMPACT_IDENTITY_KEYS = ["affectedUserSegment", "failedAction"];
33
+
34
+ /**
35
+ * @template {string} Field
36
+ * @param {Readonly<Record<string, unknown>>} source
37
+ * @param {readonly Field[]} fields
38
+ * @param {string} label
39
+ * @returns {{ [Key in Field]?: string }}
40
+ */
41
+ function identityFields(source, fields, label) {
42
+ /** @type {{ [Key in Field]?: string }} */
43
+ const validated = {};
44
+ for (const field of fields) {
45
+ const value = source[field];
46
+ if (value !== undefined) {
47
+ validated[field] = boundedIdentity(`${label} ${field}`, value, MAX_DIAGNOSTIC_IDENTITY_LENGTH);
48
+ }
49
+ }
50
+ return validated;
51
+ }
52
+
53
+ /**
54
+ * @param {unknown} value
55
+ * @returns {string}
56
+ */
57
+ function safeRelativeSourcePath(value) {
58
+ const path = boundedIdentity("issue evidence likelyFixArea file", value, MAX_DIAGNOSTIC_IDENTITY_LENGTH).replaceAll("\\", "/");
59
+ if (
60
+ path.startsWith("/")
61
+ || WINDOWS_DRIVE_PATTERN.test(path)
62
+ || path.includes("://")
63
+ || path.split("/").some((part) => part === "" || part === "." || part === "..")
64
+ ) {
65
+ throw new SdkError("validation_error", "issue evidence likelyFixArea file must be a safe relative path");
66
+ }
67
+ return path;
68
+ }
69
+
70
+ /**
71
+ * @param {"line" | "column"} key
72
+ * @param {unknown} value
73
+ * @returns {number | undefined}
74
+ */
75
+ function optionalCoordinate(key, value) {
76
+ if (value === undefined) {
77
+ return undefined;
78
+ }
79
+ if (typeof value !== "number" || !Number.isInteger(value) || value < MIN_COORDINATE || value > MAX_COORDINATE) {
80
+ throw new SdkError("validation_error", `issue evidence likelyFixArea ${key} must be a positive integer`);
81
+ }
82
+ return value;
83
+ }
84
+
85
+ /**
86
+ * @param {unknown} value
87
+ * @returns {boolean | undefined}
88
+ */
89
+ function optionalInApp(value) {
90
+ if (value !== undefined && typeof value !== "boolean") {
91
+ throw new SdkError("validation_error", "issue evidence likelyFixArea inApp must be a boolean");
92
+ }
93
+ return value;
94
+ }
95
+
96
+ /**
97
+ * @param {Readonly<Record<string, unknown>>} source
98
+ * @returns {Omit<IssueLikelyFixArea, "inApp">}
99
+ */
100
+ function fixAreaLocation(source) {
101
+ const identities = identityFields(source, FIX_AREA_IDENTITY_KEYS, "issue evidence likelyFixArea");
102
+ const file = source["file"] === undefined ? undefined : safeRelativeSourcePath(source["file"]);
103
+ const line = optionalCoordinate("line", source["line"]);
104
+ const column = optionalCoordinate("column", source["column"]);
105
+ return {
106
+ ...identities,
107
+ ...optionalEntry("file", file),
108
+ ...optionalEntry("line", line),
109
+ ...optionalEntry("column", column)
110
+ };
111
+ }
112
+
113
+ /**
114
+ * @param {unknown} area
115
+ * @returns {IssueLikelyFixArea | undefined}
116
+ */
117
+ function validateLikelyFixArea(area) {
118
+ if (area === undefined) {
119
+ return undefined;
120
+ }
121
+ const source = requireObject("issue evidence likelyFixArea", area);
122
+ rejectUnknownKeys("issue evidence likelyFixArea", source, FIX_AREA_KEYS);
123
+ const location = fixAreaLocation(source);
124
+ const inApp = optionalInApp(source["inApp"]);
125
+ if (Object.keys(location).length === NO_FIELDS) {
126
+ throw new SdkError("validation_error", "issue evidence likelyFixArea must identify a code location");
127
+ }
128
+ return { ...location, ...optionalEntry("inApp", inApp) };
129
+ }
130
+
131
+ /**
132
+ * @param {unknown} impact
133
+ * @returns {IssueImpactEvidence | undefined}
134
+ */
135
+ function validateImpactEvidence(impact) {
136
+ if (impact === undefined) {
137
+ return undefined;
138
+ }
139
+ const source = requireObject("issue evidence impact", impact);
140
+ rejectUnknownKeys("issue evidence impact", source, IMPACT_KEYS);
141
+ const validated = {
142
+ ...identityFields(source, IMPACT_IDENTITY_KEYS, "issue evidence impact"),
143
+ ...optionalEntry(
144
+ "userVisibleOutcome",
145
+ optionalText("issue evidence impact userVisibleOutcome", source["userVisibleOutcome"], MAX_DIAGNOSTIC_OUTCOME_LENGTH)
146
+ )
147
+ };
148
+ if (Object.keys(validated).length === NO_FIELDS) {
149
+ throw new SdkError("validation_error", "issue evidence impact must contain at least one field");
150
+ }
151
+ return validated;
152
+ }
153
+
154
+ /**
155
+ * @param {unknown} field
156
+ * @returns {field is string}
157
+ */
158
+ function isEvidenceField(field) {
159
+ return typeof field === "string" && EVIDENCE_FIELD_PATTERN.test(field);
160
+ }
161
+
162
+ /**
163
+ * @param {EvidenceFieldKey} key
164
+ * @param {unknown} fields
165
+ * @returns {string[] | undefined}
166
+ */
167
+ function validateEvidenceFields(key, fields) {
168
+ if (fields === undefined) {
169
+ return undefined;
170
+ }
171
+ if (!Array.isArray(fields) || fields.length < MIN_DIAGNOSTIC_FIELDS || fields.length > MAX_DIAGNOSTIC_FIELDS) {
172
+ throw new SdkError("validation_error", `issue evidence ${key} must contain 1-${MAX_DIAGNOSTIC_FIELDS} fields`);
173
+ }
174
+ /** @type {readonly unknown[]} */
175
+ const list = fields;
176
+ const identifiers = list.filter((field) => isEvidenceField(field));
177
+ if (new Set(list).size !== list.length || identifiers.length !== list.length) {
178
+ throw new SdkError("validation_error", `issue evidence ${key} fields must be unique bounded identifiers`);
179
+ }
180
+ return identifiers;
181
+ }
182
+
183
+ /**
184
+ * @param {DeepReadonly<EvidenceFieldLists>} lists
185
+ * @returns {void}
186
+ */
187
+ function rejectConflictingFields(lists) {
188
+ const present = new Set();
189
+ for (const field of Object.values(lists).flat()) {
190
+ if (present.has(field)) {
191
+ throw new SdkError("validation_error", `issue evidence field ${field} has conflicting states`);
192
+ }
193
+ present.add(field);
194
+ }
195
+ }
196
+
197
+ /**
198
+ * @param {Readonly<Record<string, unknown>>} source
199
+ * @returns {EvidenceFieldLists}
200
+ */
201
+ function evidenceFieldLists(source) {
202
+ /** @type {EvidenceFieldLists} */
203
+ const lists = {};
204
+ for (const key of EVIDENCE_FIELD_KEYS) {
205
+ const fields = validateEvidenceFields(key, source[key]);
206
+ if (fields !== undefined) {
207
+ lists[key] = fields;
208
+ }
209
+ }
210
+ rejectConflictingFields(lists);
211
+ return lists;
212
+ }
213
+
214
+ /**
215
+ * @param {unknown} evidence
216
+ * @returns {IssueDiagnosticEvidence | undefined}
217
+ */
218
+ function validateIssueEvidence(evidence) {
219
+ if (evidence === undefined) {
220
+ return undefined;
221
+ }
222
+ const source = requireObject("issue evidence", evidence);
223
+ rejectUnknownKeys("issue evidence", source, EVIDENCE_KEYS);
224
+ const validated = {
225
+ ...optionalEntry(
226
+ "likelyRootCause",
227
+ optionalText("issue evidence likelyRootCause", source["likelyRootCause"], MAX_DIAGNOSTIC_CAUSE_LENGTH)
228
+ ),
229
+ ...optionalEntry("likelyFixArea", validateLikelyFixArea(source["likelyFixArea"])),
230
+ ...optionalEntry("impact", validateImpactEvidence(source["impact"])),
231
+ ...evidenceFieldLists(source)
232
+ };
233
+ if (Object.keys(validated).length === NO_FIELDS) {
234
+ throw new SdkError("validation_error", "issue evidence must contain at least one field");
235
+ }
236
+ return validated;
237
+ }
238
+
239
+ /**
240
+ * @param {DeepReadonly<IssueDiagnosticEvidence>} evidence
241
+ * @returns {DeepReadonly<IssueDiagnosticEvidence>}
242
+ */
243
+ function cloneIssueEvidence(evidence) {
244
+ const { capturedFields, impact, likelyFixArea, missingFields, redactedFields, truncatedFields } = evidence;
245
+ return {
246
+ ...evidence,
247
+ ...optionalEntry("likelyFixArea", likelyFixArea === undefined ? undefined : { ...likelyFixArea }),
248
+ ...optionalEntry("impact", impact === undefined ? undefined : { ...impact }),
249
+ ...optionalEntry("capturedFields", capturedFields === undefined ? undefined : [...capturedFields]),
250
+ ...optionalEntry("missingFields", missingFields === undefined ? undefined : [...missingFields]),
251
+ ...optionalEntry("redactedFields", redactedFields === undefined ? undefined : [...redactedFields]),
252
+ ...optionalEntry("truncatedFields", truncatedFields === undefined ? undefined : [...truncatedFields])
253
+ };
254
+ }
255
+
256
+ module.exports = { cloneIssueEvidence, validateIssueEvidence };
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+
3
+ const { SdkError } = require("./sdk-error.cjs");
4
+ const { validateIssueStackFrames } = require("./issue-stack-frames.cjs");
5
+ const { boundedIdentity, optionalIdentity, optionalText, requireMachineName } = require("./issue-text.cjs");
6
+ const { isAllowedValue, optionalEntry, rejectUnknownKeys, requireObject } = require("./validation.cjs");
7
+
8
+ /**
9
+ * @import {
10
+ * IssueException,
11
+ * IssueExceptionChain,
12
+ * IssueExceptionChainEntry,
13
+ * IssueExceptionMechanism,
14
+ * IssueExceptionMessageState,
15
+ * IssueExceptionRelationship,
16
+ * IssueExceptionStackFramesState
17
+ * } from "./index.cjs"
18
+ * @import { DeepReadonly } from "./validation.cjs"
19
+ * @typedef {Pick<IssueExceptionChainEntry, "id" | "parentId" | "relationship">} EntryLineage
20
+ * @typedef {Pick<IssueExceptionChainEntry, "message" | "messageState">} EntryMessage
21
+ * @typedef {Pick<IssueExceptionChainEntry, "stackFrames" | "stackFramesState">} EntryStack
22
+ */
23
+
24
+ const MAX_ISSUE_EXCEPTIONS = 8;
25
+ const MAX_EXCEPTION_TYPE_LENGTH = 256;
26
+ const MAX_EXCEPTION_MESSAGE_LENGTH = 1024;
27
+ const MAX_EXCEPTION_MODULE_LENGTH = 512;
28
+ const ROOT_INDEX = 0;
29
+ const NEXT_INDEX = 1;
30
+ const EXCEPTION_KEYS = new Set(["type", "mechanism"]);
31
+ const MECHANISM_KEYS = new Set(["type", "handled"]);
32
+ const CHAIN_KEYS = new Set(["entries", "truncated"]);
33
+ const ENTRY_KEYS = new Set([
34
+ "id",
35
+ "parentId",
36
+ "relationship",
37
+ "type",
38
+ "message",
39
+ "messageState",
40
+ "module",
41
+ "mechanism",
42
+ "stackFrames",
43
+ "stackFramesState"
44
+ ]);
45
+ /** @type {ReadonlySet<IssueExceptionRelationship>} */
46
+ const CHILD_RELATIONSHIPS = new Set(["cause", "context", "aggregate_member", "suppressed"]);
47
+ /** @type {ReadonlySet<IssueExceptionMessageState>} */
48
+ const MESSAGE_STATES = new Set(["captured", "truncated", "redacted", "not_captured"]);
49
+ /** @type {ReadonlySet<IssueExceptionStackFramesState>} */
50
+ const STACK_FRAMES_STATES = new Set(["captured", "truncated", "not_captured"]);
51
+
52
+ /**
53
+ * @param {unknown} mechanism
54
+ * @returns {IssueExceptionMechanism | undefined}
55
+ */
56
+ function validateIssueExceptionMechanism(mechanism) {
57
+ if (mechanism === undefined) {
58
+ return undefined;
59
+ }
60
+ const source = requireObject("issue exception mechanism", mechanism);
61
+ rejectUnknownKeys("issue exception mechanism", source, MECHANISM_KEYS);
62
+ const type = requireMachineName("issue exception mechanism type", source["type"]);
63
+ const { handled } = source;
64
+ if (typeof handled !== "boolean") {
65
+ throw new SdkError("validation_error", "issue exception mechanism handled must be a boolean");
66
+ }
67
+ return {
68
+ type,
69
+
70
+ handled
71
+ };
72
+ }
73
+
74
+ /**
75
+ * @param {unknown} exception
76
+ * @returns {IssueException}
77
+ */
78
+ function requireIssueException(exception) {
79
+ const source = requireObject("issue exception", exception);
80
+ rejectUnknownKeys("issue exception", source, EXCEPTION_KEYS);
81
+ const type = boundedIdentity("issue exception type", source["type"], MAX_EXCEPTION_TYPE_LENGTH);
82
+ const mechanism = validateIssueExceptionMechanism(source["mechanism"]);
83
+ return { type, ...optionalEntry("mechanism", mechanism) };
84
+ }
85
+
86
+ /**
87
+ * @param {unknown} exception
88
+ * @returns {IssueException | undefined}
89
+ */
90
+ function validateIssueException(exception) {
91
+ return exception === undefined ? undefined : requireIssueException(exception);
92
+ }
93
+
94
+ /**
95
+ * @param {string} type
96
+ * @param {string} mechanism
97
+ * @param {unknown} handled
98
+ * @returns {IssueException}
99
+ */
100
+ function createIssueException(type, mechanism, handled) {
101
+ return requireIssueException({ mechanism: { handled, type: mechanism }, type });
102
+ }
103
+
104
+ /**
105
+ * @param {unknown} parentId
106
+ * @param {number} index
107
+ * @returns {parentId is number}
108
+ */
109
+ function isParentIndex(parentId, index) {
110
+ return typeof parentId === "number" && Number.isInteger(parentId) && parentId >= ROOT_INDEX && parentId < index;
111
+ }
112
+
113
+ /**
114
+ * @param {Readonly<Record<string, unknown>>} source
115
+ * @param {number} index
116
+ * @returns {EntryLineage}
117
+ */
118
+ function entryLineage(source, index) {
119
+ const { id, parentId, relationship } = source;
120
+ if (typeof id !== "number" || !Number.isInteger(id) || id !== index) {
121
+ throw new SdkError("validation_error", `issue exceptionChain entry ${index} id must equal its array index`);
122
+ }
123
+ if (index === ROOT_INDEX) {
124
+ if (relationship !== "reported" || parentId !== undefined) {
125
+ throw new SdkError("validation_error", "issue exceptionChain entry 0 must be the parentless reported exception");
126
+ }
127
+ return { id, relationship };
128
+ }
129
+ if (typeof relationship !== "string" || !isAllowedValue(relationship, CHILD_RELATIONSHIPS) || !isParentIndex(parentId, index)) {
130
+ throw new SdkError("validation_error", `issue exceptionChain entry ${index} parent relationship is invalid`);
131
+ }
132
+ return { id, parentId, relationship };
133
+ }
134
+
135
+ /**
136
+ * @param {Readonly<Record<string, unknown>>} source
137
+ * @param {number} index
138
+ * @returns {EntryMessage}
139
+ */
140
+ function entryMessage(source, index) {
141
+ const { messageState } = source;
142
+ if (typeof messageState !== "string" || !isAllowedValue(messageState, MESSAGE_STATES)) {
143
+ throw new SdkError("validation_error", `issue exceptionChain entry ${index} messageState is invalid`);
144
+ }
145
+ const message = optionalText(`issue exceptionChain entry ${index} message`, source["message"], MAX_EXCEPTION_MESSAGE_LENGTH);
146
+ if ((messageState === "captured" || messageState === "truncated") !== (message !== undefined)) {
147
+ throw new SdkError("validation_error", `issue exceptionChain entry ${index} message does not match messageState`);
148
+ }
149
+ return { ...optionalEntry("message", message), messageState };
150
+ }
151
+
152
+ /**
153
+ * @param {Readonly<Record<string, unknown>>} source
154
+ * @param {number} index
155
+ * @returns {EntryStack}
156
+ */
157
+ function entryStack(source, index) {
158
+ const { stackFramesState } = source;
159
+ if (typeof stackFramesState !== "string" || !isAllowedValue(stackFramesState, STACK_FRAMES_STATES)) {
160
+ throw new SdkError("validation_error", `issue exceptionChain entry ${index} stackFramesState is invalid`);
161
+ }
162
+ const stackFrames = validateIssueStackFrames(source["stackFrames"]);
163
+ if ((stackFramesState === "captured" || stackFramesState === "truncated") !== (stackFrames !== undefined)) {
164
+ throw new SdkError("validation_error", `issue exceptionChain entry ${index} stackFrames do not match stackFramesState`);
165
+ }
166
+ return { ...optionalEntry("stackFrames", stackFrames), stackFramesState };
167
+ }
168
+
169
+ /**
170
+ * @param {unknown} entry
171
+ * @param {number} index
172
+ * @returns {IssueExceptionChainEntry}
173
+ */
174
+ function validateIssueExceptionChainEntry(entry, index) {
175
+ const label = `issue exceptionChain entry ${index}`;
176
+ const source = requireObject(label, entry);
177
+ rejectUnknownKeys(label, source, ENTRY_KEYS);
178
+ const lineage = entryLineage(source, index);
179
+ const type = boundedIdentity(`${label} type`, source["type"], MAX_EXCEPTION_TYPE_LENGTH);
180
+ const message = entryMessage(source, index);
181
+ const moduleName = optionalIdentity(`${label} module`, source["module"], MAX_EXCEPTION_MODULE_LENGTH);
182
+ const mechanism = validateIssueExceptionMechanism(source["mechanism"]);
183
+ const stack = entryStack(source, index);
184
+ return {
185
+ ...lineage,
186
+ type,
187
+ ...message,
188
+ ...optionalEntry("module", moduleName),
189
+ ...optionalEntry("mechanism", mechanism),
190
+ ...stack
191
+ };
192
+ }
193
+
194
+ /**
195
+ * @param {readonly unknown[]} entries
196
+ * @returns {entries is readonly [unknown, ...unknown[]]}
197
+ */
198
+ function hasChainEntries(entries) {
199
+ return entries.length >= NEXT_INDEX && entries.length <= MAX_ISSUE_EXCEPTIONS;
200
+ }
201
+
202
+ /**
203
+ * @param {unknown} entries
204
+ * @returns {readonly [unknown, ...unknown[]]}
205
+ */
206
+ function requireChainEntries(entries) {
207
+ if (Array.isArray(entries)) {
208
+ /** @type {readonly unknown[]} */
209
+ const list = entries;
210
+ if (hasChainEntries(list)) {
211
+ return list;
212
+ }
213
+ }
214
+ throw new SdkError("validation_error", `issue exceptionChain entries must contain 1-${MAX_ISSUE_EXCEPTIONS} exceptions`);
215
+ }
216
+
217
+ /**
218
+ * @param {unknown} truncated
219
+ * @returns {boolean}
220
+ */
221
+ function requireChainTruncated(truncated) {
222
+ if (typeof truncated !== "boolean") {
223
+ throw new SdkError("validation_error", "issue exceptionChain truncated must be a boolean");
224
+ }
225
+ return truncated;
226
+ }
227
+
228
+ /**
229
+ * @param {DeepReadonly<IssueExceptionChainEntry>} reported
230
+ * @param {DeepReadonly<IssueException> | undefined} legacyException
231
+ * @param {unknown} legacyStackFrames
232
+ * @returns {void}
233
+ */
234
+ function requireReportedMatch(reported, legacyException, legacyStackFrames) {
235
+ const reportedException = { type: reported.type, ...optionalEntry("mechanism", reported.mechanism) };
236
+ if (legacyException === undefined || JSON.stringify(reportedException) !== JSON.stringify(legacyException)) {
237
+ throw new SdkError("validation_error", "issue exceptionChain reported exception must match exception");
238
+ }
239
+ const canonicalLegacyFrames = validateIssueStackFrames(legacyStackFrames);
240
+ const framesMatch = reported.stackFramesState === "not_captured"
241
+ ? canonicalLegacyFrames === undefined
242
+ : JSON.stringify(reported.stackFrames) === JSON.stringify(canonicalLegacyFrames);
243
+ if (!framesMatch) {
244
+ throw new SdkError("validation_error", "issue exceptionChain reported stack must match stackFrames");
245
+ }
246
+ }
247
+
248
+ /**
249
+ * @param {unknown} chain
250
+ * @param {DeepReadonly<IssueException> | undefined} legacyException
251
+ * @param {unknown} legacyStackFrames
252
+ * @returns {IssueExceptionChain | undefined}
253
+ */
254
+ function validateIssueExceptionChain(chain, legacyException, legacyStackFrames) {
255
+ if (chain === undefined) {
256
+ return undefined;
257
+ }
258
+ const source = requireObject("issue exceptionChain", chain);
259
+ rejectUnknownKeys("issue exceptionChain", source, CHAIN_KEYS);
260
+ const [first, ...rest] = requireChainEntries(source["entries"]);
261
+ const truncated = requireChainTruncated(source["truncated"]);
262
+ const reported = validateIssueExceptionChainEntry(first, ROOT_INDEX);
263
+ const entries = [reported, ...rest.map((entry, offset) => validateIssueExceptionChainEntry(entry, offset + NEXT_INDEX))];
264
+ requireReportedMatch(reported, legacyException, legacyStackFrames);
265
+ return { entries, truncated };
266
+ }
267
+
268
+ module.exports = { createIssueException, validateIssueException, validateIssueExceptionChain };