@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.
- package/automatic-delivery.cjs +271 -0
- package/batch-sender.cjs +164 -0
- package/bounded-number.cjs +34 -0
- package/browser-breadcrumbs.cjs +346 -136
- package/capture-support.cjs +100 -0
- package/client-options.cjs +292 -0
- package/client-report.cjs +232 -0
- package/client.cjs +447 -0
- package/console-arguments.cjs +151 -0
- package/console-capture.cjs +194 -0
- package/core.cjs +35 -2984
- package/delivery-health.cjs +133 -0
- package/error-causes.cjs +130 -0
- package/error-details.cjs +124 -0
- package/error-issue.cjs +187 -0
- package/event-clone.cjs +55 -0
- package/event-metadata.cjs +40 -0
- package/event-pipeline.cjs +325 -0
- package/event-queue.cjs +306 -0
- package/event-store.cjs +237 -0
- package/event-validation.cjs +159 -0
- package/examples/agent-timeline.cjs +57 -46
- package/examples/agent-timeline.mjs +41 -35
- package/examples/index.mjs +122 -94
- package/examples/readme-example.cjs +36 -27
- package/examples/readme-example.mjs +21 -17
- package/examples/real-user-smoke.cjs +41 -32
- package/examples/real-user-smoke.mjs +26 -22
- package/exception-chain.cjs +205 -0
- package/index.d.ts +144 -1076
- package/index.js +1 -1
- package/issue-breadcrumbs.cjs +143 -0
- package/issue-diagnostics.cjs +94 -582
- package/issue-evidence.cjs +256 -0
- package/issue-exception.cjs +268 -0
- package/issue-stack-frames.cjs +220 -0
- package/issue-stack-text.cjs +101 -0
- package/issue-stack.cjs +188 -225
- package/issue-text.cjs +72 -0
- package/log-context.cjs +87 -67
- package/metric-validation.cjs +95 -0
- package/network-milestone.cjs +155 -0
- package/null-value.cjs +4 -0
- package/opentelemetry-bridge.cjs +330 -0
- package/opentelemetry-context.cjs +245 -0
- package/opentelemetry-exceptions.cjs +69 -0
- package/opentelemetry-keys.cjs +183 -0
- package/opentelemetry-span.cjs +286 -0
- package/opentelemetry-summary.cjs +213 -0
- package/opentelemetry-time.cjs +131 -0
- package/opentelemetry.cjs +99 -1068
- package/package.json +64 -1
- package/pino-destination.cjs +108 -0
- package/pino-record.cjs +237 -0
- package/product-action.cjs +163 -0
- package/provided-breadcrumbs.cjs +117 -0
- package/random-hex.cjs +29 -0
- package/react-native.js +1 -1
- package/recording-transport.cjs +68 -0
- package/release-artifacts-build.cjs +263 -84
- package/release-artifacts-common.js +347 -66
- package/release-artifacts-debug-id.js +178 -0
- package/release-artifacts-inspect.js +213 -0
- package/release-artifacts-issue-event.js +99 -0
- package/release-artifacts-manifest-artifact.js +107 -0
- package/release-artifacts-manifest.js +77 -0
- package/release-artifacts-mappings.js +265 -0
- package/release-artifacts-prepare.js +297 -0
- package/release-artifacts-source-context.js +210 -0
- package/release-artifacts-symbolication.js +272 -421
- package/release-artifacts-upload-files.js +122 -0
- package/release-artifacts-upload-request.js +246 -0
- package/release-artifacts-upload.js +155 -300
- package/release-artifacts-urls.js +49 -0
- package/release-artifacts.js +189 -662
- package/retry-delay.cjs +32 -0
- package/sdk-error.cjs +36 -0
- package/span-validation.cjs +191 -0
- package/stored-events.cjs +243 -0
- package/support-ticket.cjs +232 -122
- package/telemetry-context.cjs +343 -251
- package/trace-context.cjs +376 -179
- package/transport-error.cjs +23 -0
- package/types/client.d.ts +67 -0
- package/types/delivery.d.ts +125 -0
- package/types/issues.d.ts +249 -0
- package/types/logging.d.ts +175 -0
- package/types/opentelemetry.d.ts +205 -0
- package/types/product.d.ts +128 -0
- package/types/recording-transport.d.ts +15 -0
- package/types/sdk-error.d.ts +9 -0
- package/types/telemetry.d.ts +171 -0
- package/types/trace-context.d.ts +110 -0
- package/types/transport-error.d.ts +10 -0
- package/utf8.cjs +37 -0
- package/validation.cjs +327 -0
- package/vite-release-artifacts-config.cjs +120 -0
- package/vite-release-artifacts.cjs +199 -99
- package/vite-release-artifacts.d.cts +65 -40
- package/vite-release-artifacts.d.ts +65 -40
- package/vite-release-artifacts.js +2 -2
- package/winston-info.cjs +205 -0
- package/winston.cjs +189 -290
package/retry-delay.cjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const MAX_DELIVERY_INTERVAL_MS = 60_000;
|
|
2
|
+
const MIN_RATE_LIMIT_WAIT_MS = 1000;
|
|
3
|
+
const MAX_RATE_LIMIT_WAIT_MS = 3_600_000;
|
|
4
|
+
const MAX_BACKOFF_EXPONENT = 30;
|
|
5
|
+
const FIRST_FAILURE = 1;
|
|
6
|
+
const BACKOFF_BASE = 2;
|
|
7
|
+
const HALF = 2;
|
|
8
|
+
const INCLUSIVE = 1;
|
|
9
|
+
const JITTER_DIVISOR = 10;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {number} deliveryIntervalMs
|
|
13
|
+
* @param {number} consecutiveFailures
|
|
14
|
+
* @returns {number}
|
|
15
|
+
*/
|
|
16
|
+
function automaticRetryDelayMs(deliveryIntervalMs, consecutiveFailures) {
|
|
17
|
+
const exponent = Math.min(consecutiveFailures - FIRST_FAILURE, MAX_BACKOFF_EXPONENT);
|
|
18
|
+
const maximumDelay = Math.min(MAX_DELIVERY_INTERVAL_MS, deliveryIntervalMs * (BACKOFF_BASE ** exponent));
|
|
19
|
+
const minimumDelay = Math.ceil(maximumDelay / HALF);
|
|
20
|
+
return minimumDelay + Math.floor(Math.random() * (maximumDelay - minimumDelay + INCLUSIVE));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {number} delayMs
|
|
25
|
+
* @returns {number}
|
|
26
|
+
*/
|
|
27
|
+
function rateLimitWaitMs(delayMs) {
|
|
28
|
+
const bounded = Math.min(MAX_RATE_LIMIT_WAIT_MS, Math.max(MIN_RATE_LIMIT_WAIT_MS, delayMs));
|
|
29
|
+
return Math.min(MAX_RATE_LIMIT_WAIT_MS, bounded + Math.floor(Math.random() * (Math.floor(bounded / JITTER_DIVISOR) + INCLUSIVE)));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { MAX_DELIVERY_INTERVAL_MS, automaticRetryDelayMs, rateLimitWaitMs };
|
package/sdk-error.cjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const MIN_RETRY_AFTER_MS = 0;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {unknown} value
|
|
5
|
+
* @returns {number | undefined}
|
|
6
|
+
*/
|
|
7
|
+
function retryAfterMsOrUndefined(value) {
|
|
8
|
+
if (value === undefined) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= MIN_RETRY_AFTER_MS ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class SdkError extends Error {
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} code
|
|
17
|
+
* @param {string} message
|
|
18
|
+
* @param {Readonly<{ retryAfterMs?: unknown, retryable?: unknown }> | null} [details]
|
|
19
|
+
*/
|
|
20
|
+
constructor(code, message, details = {}) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "SdkError";
|
|
23
|
+
this.code = code;
|
|
24
|
+
const retryAfterMs = retryAfterMsOrUndefined(details?.retryAfterMs);
|
|
25
|
+
if (retryAfterMs !== undefined) {
|
|
26
|
+
/** @type {number | undefined} */
|
|
27
|
+
this.retryAfterMs = retryAfterMs;
|
|
28
|
+
}
|
|
29
|
+
if (details?.retryable === true) {
|
|
30
|
+
/** @type {boolean | undefined} */
|
|
31
|
+
this.retryable = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { SdkError, retryAfterMsOrUndefined };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
const { withMetadata } = require("./event-metadata.cjs");
|
|
2
|
+
const { compactMetadata } = require("./log-context.cjs");
|
|
3
|
+
const { SdkError } = require("./sdk-error.cjs");
|
|
4
|
+
const { parseTraceparent } = require("./trace-context.cjs");
|
|
5
|
+
const {
|
|
6
|
+
isRecord,
|
|
7
|
+
optionalEntry,
|
|
8
|
+
requireAllowedValue,
|
|
9
|
+
requireNonEmpty,
|
|
10
|
+
requireSpanId,
|
|
11
|
+
requireTimestamp,
|
|
12
|
+
requireTraceId
|
|
13
|
+
} = require("./validation.cjs");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @import { Metadata, SpanAttributes, SpanEventSummary, SpanLinkSummary } from "./index.cjs"
|
|
17
|
+
* @import { EventAttributes } from "./event-validation.cjs"
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const MAX_SPAN_EVENTS = 8;
|
|
21
|
+
const MAX_SPAN_LINKS = 8;
|
|
22
|
+
const NO_ENTRIES = 0;
|
|
23
|
+
const MIN_DURATION_MS = 0;
|
|
24
|
+
/** @type {ReadonlySet<SpanAttributes["status"]>} */
|
|
25
|
+
const SPAN_STATUSES = new Set(["ok", "error"]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {unknown} metadata
|
|
29
|
+
* @returns {Metadata | undefined}
|
|
30
|
+
*/
|
|
31
|
+
function nonEmptyMetadata(metadata) {
|
|
32
|
+
if (metadata === undefined) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const compacted = compactMetadata(metadata);
|
|
36
|
+
return Object.keys(compacted).length > NO_ENTRIES ? compacted : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {unknown} durationMs
|
|
41
|
+
* @returns {number | undefined}
|
|
42
|
+
*/
|
|
43
|
+
function optionalSpanDuration(durationMs) {
|
|
44
|
+
if (durationMs === undefined) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
if (typeof durationMs !== "number" || Number.isNaN(durationMs) || durationMs < MIN_DURATION_MS) {
|
|
48
|
+
throw new SdkError("validation_error", "span durationMs must be non-negative");
|
|
49
|
+
}
|
|
50
|
+
return durationMs;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {unknown} list
|
|
55
|
+
* @param {string} label
|
|
56
|
+
* @param {number} maxEntries
|
|
57
|
+
* @returns {readonly unknown[] | undefined}
|
|
58
|
+
*/
|
|
59
|
+
function optionalBoundedList(list, label, maxEntries) {
|
|
60
|
+
if (list === undefined) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
if (!Array.isArray(list)) {
|
|
64
|
+
throw new SdkError("validation_error", `${label} must be an array`);
|
|
65
|
+
}
|
|
66
|
+
if (list.length > maxEntries) {
|
|
67
|
+
throw new SdkError("validation_error", `${label} must contain at most ${maxEntries} entries`);
|
|
68
|
+
}
|
|
69
|
+
return list.length === NO_ENTRIES ? undefined : list;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {unknown} event
|
|
74
|
+
* @returns {SpanEventSummary}
|
|
75
|
+
*/
|
|
76
|
+
function validateSpanEvent(event) {
|
|
77
|
+
if (!isRecord(event)) {
|
|
78
|
+
throw new SdkError("validation_error", "span event must be an object");
|
|
79
|
+
}
|
|
80
|
+
const name = requireNonEmpty("span event name", event["name"]);
|
|
81
|
+
const timestamp = event["timestamp"] === undefined ? undefined : requireTimestamp(event["timestamp"]);
|
|
82
|
+
return {
|
|
83
|
+
name,
|
|
84
|
+
...optionalEntry("timestamp", timestamp),
|
|
85
|
+
...optionalEntry("metadata", nonEmptyMetadata(event["metadata"]))
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {unknown} events
|
|
91
|
+
* @returns {SpanEventSummary[] | undefined}
|
|
92
|
+
*/
|
|
93
|
+
function validateSpanEvents(events) {
|
|
94
|
+
return optionalBoundedList(events, "span events", MAX_SPAN_EVENTS)?.map((event) => validateSpanEvent(event));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {unknown} link
|
|
99
|
+
* @returns {SpanLinkSummary}
|
|
100
|
+
*/
|
|
101
|
+
function validateSpanLink(link) {
|
|
102
|
+
if (!isRecord(link)) {
|
|
103
|
+
throw new SdkError("validation_error", "span link must be an object");
|
|
104
|
+
}
|
|
105
|
+
const traceId = requireTraceId(link["traceId"]);
|
|
106
|
+
const spanId = requireSpanId("span link spanId", link["spanId"]);
|
|
107
|
+
const { sampled } = link;
|
|
108
|
+
if (sampled !== undefined && typeof sampled !== "boolean") {
|
|
109
|
+
throw new SdkError("validation_error", "span link sampled must be a boolean");
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
traceId: traceId.toLowerCase(),
|
|
113
|
+
|
|
114
|
+
spanId: spanId.toLowerCase(),
|
|
115
|
+
...optionalEntry("sampled", sampled),
|
|
116
|
+
...optionalEntry("metadata", nonEmptyMetadata(link["metadata"]))
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {unknown} links
|
|
122
|
+
* @returns {SpanLinkSummary[] | undefined}
|
|
123
|
+
*/
|
|
124
|
+
function validateSpanLinks(links) {
|
|
125
|
+
return optionalBoundedList(links, "span links", MAX_SPAN_LINKS)?.map((link) => validateSpanLink(link));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {Readonly<Record<string, unknown>>} attributes
|
|
130
|
+
* @returns {EventAttributes}
|
|
131
|
+
*/
|
|
132
|
+
function validateSpan(attributes) {
|
|
133
|
+
const name = requireNonEmpty("span name", attributes["name"]);
|
|
134
|
+
const traceId = requireNonEmpty("span traceId", attributes["traceId"]);
|
|
135
|
+
const spanId = requireNonEmpty("span spanId", attributes["spanId"]);
|
|
136
|
+
const status = requireAllowedValue("span status", attributes["status"], SPAN_STATUSES);
|
|
137
|
+
const parentSpanId = attributes["parentSpanId"] === undefined
|
|
138
|
+
? undefined
|
|
139
|
+
: requireNonEmpty("span parentSpanId", attributes["parentSpanId"]);
|
|
140
|
+
const durationMs = optionalSpanDuration(attributes["durationMs"]);
|
|
141
|
+
const events = validateSpanEvents(attributes["events"]);
|
|
142
|
+
const links = validateSpanLinks(attributes["links"]);
|
|
143
|
+
return withMetadata({
|
|
144
|
+
name,
|
|
145
|
+
|
|
146
|
+
traceId,
|
|
147
|
+
|
|
148
|
+
spanId,
|
|
149
|
+
|
|
150
|
+
status,
|
|
151
|
+
...optionalEntry("parentSpanId", parentSpanId),
|
|
152
|
+
...optionalEntry("durationMs", durationMs),
|
|
153
|
+
...optionalEntry("events", events),
|
|
154
|
+
...optionalEntry("links", links)
|
|
155
|
+
}, attributes["metadata"], attributes["context"]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @param {unknown} traceparent
|
|
160
|
+
* @param {unknown} attributes
|
|
161
|
+
* @returns {SpanAttributes}
|
|
162
|
+
*/
|
|
163
|
+
function spanAttributesFromTraceparent(traceparent, attributes) {
|
|
164
|
+
if (!isRecord(attributes)) {
|
|
165
|
+
throw new SdkError("validation_error", "span attributes must be an object");
|
|
166
|
+
}
|
|
167
|
+
const context = parseTraceparent(traceparent);
|
|
168
|
+
const name = requireNonEmpty("span name", attributes["name"]);
|
|
169
|
+
const spanId = requireSpanId("spanId", attributes["spanId"]);
|
|
170
|
+
const status = requireAllowedValue("span status", attributes["status"], SPAN_STATUSES);
|
|
171
|
+
const durationMs = optionalSpanDuration(attributes["durationMs"]);
|
|
172
|
+
const events = validateSpanEvents(attributes["events"]);
|
|
173
|
+
const links = validateSpanLinks(attributes["links"]);
|
|
174
|
+
return {
|
|
175
|
+
name,
|
|
176
|
+
|
|
177
|
+
traceId: context.traceId,
|
|
178
|
+
|
|
179
|
+
spanId: spanId.toLowerCase(),
|
|
180
|
+
|
|
181
|
+
parentSpanId: context.parentSpanId,
|
|
182
|
+
|
|
183
|
+
status,
|
|
184
|
+
...optionalEntry("durationMs", durationMs),
|
|
185
|
+
...optionalEntry("events", events),
|
|
186
|
+
...optionalEntry("links", links),
|
|
187
|
+
...optionalEntry("metadata", attributes["metadata"] === undefined ? undefined : compactMetadata(attributes["metadata"]))
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = { spanAttributesFromTraceparent, validateSpan };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
const { isObjectLike } = require("./error-details.cjs");
|
|
2
|
+
const { cloneEvent } = require("./event-clone.cjs");
|
|
3
|
+
const { EVENT_VALIDATORS, isEventType } = require("./event-validation.cjs");
|
|
4
|
+
const { SdkError } = require("./sdk-error.cjs");
|
|
5
|
+
const { utf8ByteLength } = require("./utf8.cjs");
|
|
6
|
+
const { isRecord, requireNonEmpty, requireTimestamp } = require("./validation.cjs");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @import { EventType, EventValidator, SdkEvent } from "./event-validation.cjs"
|
|
10
|
+
* @import { DeepReadonly } from "./validation.cjs"
|
|
11
|
+
* @typedef {{ event: DeepReadonly<SdkEvent>, eventBytes: number, serializedEvent: string }} StoredRecord
|
|
12
|
+
* @typedef {Readonly<Record<string, unknown>>} UnknownRecord
|
|
13
|
+
* @typedef {{
|
|
14
|
+
* attributes: UnknownRecord,
|
|
15
|
+
* event: UnknownRecord,
|
|
16
|
+
* eventBytes: number,
|
|
17
|
+
* header: { type: EventType, id: string, timestamp: string },
|
|
18
|
+
* serializedEvent: string,
|
|
19
|
+
* validator: EventValidator
|
|
20
|
+
* }} RecordParts
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const MIN_EVENT_BYTES = 0;
|
|
24
|
+
const CONTEXT_SCHEMA_VERSION = 1;
|
|
25
|
+
const KEY_INDEX = 0;
|
|
26
|
+
const STEP = 1;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @returns {InstanceType<typeof SdkError>}
|
|
30
|
+
*/
|
|
31
|
+
function invalidStoredRecord() {
|
|
32
|
+
return new SdkError("persistence_error", "event store returned an invalid record");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} operation
|
|
37
|
+
* @param {unknown} result
|
|
38
|
+
* @returns {unknown}
|
|
39
|
+
*/
|
|
40
|
+
function requireSynchronousStoreResult(operation, result) {
|
|
41
|
+
if (isObjectLike(result) && typeof Reflect.get(result, "then") === "function") {
|
|
42
|
+
throw new SdkError("persistence_error", `eventStore.${operation} must complete synchronously`);
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {readonly [string, unknown]} left
|
|
49
|
+
* @param {readonly [string, unknown]} right
|
|
50
|
+
* @returns {number}
|
|
51
|
+
*/
|
|
52
|
+
function byKey(left, right) {
|
|
53
|
+
return left[KEY_INDEX].localeCompare(right[KEY_INDEX]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} _key
|
|
58
|
+
* @param {unknown} entry
|
|
59
|
+
* @returns {unknown}
|
|
60
|
+
*/
|
|
61
|
+
function sortedEntries(_key, entry) {
|
|
62
|
+
if (!isRecord(entry)) {
|
|
63
|
+
return entry;
|
|
64
|
+
}
|
|
65
|
+
const entries = Object.entries(entry);
|
|
66
|
+
entries.sort(byKey);
|
|
67
|
+
return Object.fromEntries(entries);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {unknown} value
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
function structuralJson(value) {
|
|
75
|
+
return JSON.stringify(value, sortedEntries);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {UnknownRecord} event
|
|
80
|
+
* @param {UnknownRecord} attributes
|
|
81
|
+
* @returns {{ event: UnknownRecord, attributes: UnknownRecord }}
|
|
82
|
+
*/
|
|
83
|
+
function recoveredEvent(event, attributes) {
|
|
84
|
+
const fatalAttributes = event["type"] === "issue" && attributes["level"] === "fatal"
|
|
85
|
+
? { ...attributes, level: "critical" }
|
|
86
|
+
: attributes;
|
|
87
|
+
const { context } = fatalAttributes;
|
|
88
|
+
const recoveredAttributes = isRecord(context) && context["schemaVersion"] === undefined
|
|
89
|
+
? { ...fatalAttributes, context: { schemaVersion: CONTEXT_SCHEMA_VERSION, ...context } }
|
|
90
|
+
: fatalAttributes;
|
|
91
|
+
return { attributes: recoveredAttributes, event: { ...event, attributes: recoveredAttributes } };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {unknown} eventBytes
|
|
96
|
+
* @param {unknown} serializedEvent
|
|
97
|
+
* @returns {Readonly<{ eventBytes: number, serializedEvent: string }>}
|
|
98
|
+
*/
|
|
99
|
+
function storedPayload(eventBytes, serializedEvent) {
|
|
100
|
+
if (typeof eventBytes !== "number" || !Number.isSafeInteger(eventBytes) || eventBytes <= MIN_EVENT_BYTES || typeof serializedEvent !== "string") {
|
|
101
|
+
throw invalidStoredRecord();
|
|
102
|
+
}
|
|
103
|
+
return { eventBytes, serializedEvent };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {UnknownRecord} event
|
|
108
|
+
* @param {Readonly<{ eventBytes: number, serializedEvent: string }>} payload
|
|
109
|
+
* @returns {RecordParts}
|
|
110
|
+
*/
|
|
111
|
+
function storedEventParts(event, payload) {
|
|
112
|
+
const { type } = event;
|
|
113
|
+
const validator = isEventType(type) ? EVENT_VALIDATORS.get(type) : undefined;
|
|
114
|
+
if (!isEventType(type) || validator === undefined) {
|
|
115
|
+
throw invalidStoredRecord();
|
|
116
|
+
}
|
|
117
|
+
const header = {
|
|
118
|
+
type,
|
|
119
|
+
|
|
120
|
+
id: requireNonEmpty("event id", event["id"]),
|
|
121
|
+
|
|
122
|
+
timestamp: requireTimestamp(event["timestamp"])
|
|
123
|
+
};
|
|
124
|
+
const { attributes } = event;
|
|
125
|
+
if (!isRecord(attributes)) {
|
|
126
|
+
throw invalidStoredRecord();
|
|
127
|
+
}
|
|
128
|
+
return { ...payload, attributes, event, header, validator };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {UnknownRecord} record
|
|
133
|
+
* @returns {RecordParts}
|
|
134
|
+
*/
|
|
135
|
+
function storedRecordParts(record) {
|
|
136
|
+
const { event, eventBytes, serializedEvent } = record;
|
|
137
|
+
if (!isRecord(event)) {
|
|
138
|
+
throw invalidStoredRecord();
|
|
139
|
+
}
|
|
140
|
+
return storedEventParts(event, storedPayload(eventBytes, serializedEvent));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {Readonly<RecordParts["header"]>} header
|
|
145
|
+
* @param {Readonly<{ event: UnknownRecord, attributes: UnknownRecord }>} recovered
|
|
146
|
+
* @param {EventValidator} validator
|
|
147
|
+
* @returns {StoredRecord}
|
|
148
|
+
*/
|
|
149
|
+
function normalizedRecord(header, recovered, validator) {
|
|
150
|
+
/** @type {SdkEvent} */
|
|
151
|
+
const normalizedEvent = { ...header, attributes: validator(recovered.attributes) };
|
|
152
|
+
const normalizedSerializedEvent = JSON.stringify(normalizedEvent);
|
|
153
|
+
if (structuralJson(normalizedEvent) !== structuralJson(recovered.event)) {
|
|
154
|
+
throw invalidStoredRecord();
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
event: cloneEvent(normalizedEvent),
|
|
158
|
+
eventBytes: utf8ByteLength(normalizedSerializedEvent),
|
|
159
|
+
serializedEvent: normalizedSerializedEvent
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @param {unknown} record
|
|
165
|
+
* @returns {StoredRecord}
|
|
166
|
+
*/
|
|
167
|
+
function normalizeRecord(record) {
|
|
168
|
+
if (!isRecord(record)) {
|
|
169
|
+
throw invalidStoredRecord();
|
|
170
|
+
}
|
|
171
|
+
const { attributes, event, eventBytes, header, serializedEvent, validator } = storedRecordParts(record);
|
|
172
|
+
if (JSON.stringify(event) !== serializedEvent || utf8ByteLength(serializedEvent) !== eventBytes) {
|
|
173
|
+
throw invalidStoredRecord();
|
|
174
|
+
}
|
|
175
|
+
return normalizedRecord(header, recoveredEvent(event, attributes), validator);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {unknown} record
|
|
180
|
+
* @returns {StoredRecord}
|
|
181
|
+
*/
|
|
182
|
+
function normalizeStoredRecord(record) {
|
|
183
|
+
try {
|
|
184
|
+
return normalizeRecord(record);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error instanceof SdkError && error.code === "persistence_error") {
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
throw invalidStoredRecord();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {string} serializedEvent
|
|
195
|
+
* @returns {unknown}
|
|
196
|
+
*/
|
|
197
|
+
function parseStoredEvent(serializedEvent) {
|
|
198
|
+
try {
|
|
199
|
+
/** @type {unknown} */
|
|
200
|
+
const event = JSON.parse(serializedEvent);
|
|
201
|
+
return event;
|
|
202
|
+
} catch {
|
|
203
|
+
throw invalidStoredRecord();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @param {unknown} serializedEvent
|
|
209
|
+
* @returns {DeepReadonly<SdkEvent>}
|
|
210
|
+
*/
|
|
211
|
+
function restoreStoredEvent(serializedEvent) {
|
|
212
|
+
if (typeof serializedEvent !== "string" || serializedEvent === "") {
|
|
213
|
+
throw invalidStoredRecord();
|
|
214
|
+
}
|
|
215
|
+
return normalizeStoredRecord({
|
|
216
|
+
event: parseStoredEvent(serializedEvent),
|
|
217
|
+
eventBytes: utf8ByteLength(serializedEvent),
|
|
218
|
+
serializedEvent
|
|
219
|
+
}).event;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* @param {readonly string[]} expected
|
|
224
|
+
* @param {readonly string[]} actual
|
|
225
|
+
* @returns {boolean}
|
|
226
|
+
*/
|
|
227
|
+
function isOrderedSubsequence(expected, actual) {
|
|
228
|
+
let index = 0;
|
|
229
|
+
for (const value of actual) {
|
|
230
|
+
if (value === expected[index]) {
|
|
231
|
+
index += STEP;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return index === expected.length;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = {
|
|
238
|
+
invalidStoredRecord,
|
|
239
|
+
isOrderedSubsequence,
|
|
240
|
+
normalizeStoredRecord,
|
|
241
|
+
requireSynchronousStoreResult,
|
|
242
|
+
restoreStoredEvent
|
|
243
|
+
};
|