@genn-inc/cluebase-frontend-sdk 0.0.3 → 0.0.8
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/README.md +21 -0
- package/dist/_generated/correlation-attribute-keys.d.ts +3 -0
- package/dist/_generated/correlation-attribute-keys.js +34 -0
- package/dist/_generated/diagnostic-codes.d.ts +4 -0
- package/dist/_generated/diagnostic-codes.js +48 -0
- package/dist/_generated/drop-reasons.d.ts +2 -0
- package/dist/_generated/drop-reasons.js +25 -0
- package/dist/_generated/{identity-pii-policy.d.ts → identity-pii-policy-text-contains.d.ts} +1 -1
- package/dist/_generated/{identity-pii-policy.js → identity-pii-policy-text-contains.js} +1 -1
- package/dist/_generated/identity-pii-policy-whole-value.d.ts +37 -0
- package/dist/_generated/identity-pii-policy-whole-value.js +106 -0
- package/dist/_generated/ingest-routes.d.ts +5 -0
- package/dist/_generated/ingest-routes.js +12 -0
- package/dist/_generated/otel-span-conversion.js +1 -9
- package/dist/context/browser-storage.js +1 -2
- package/dist/context/environment.js +5 -3
- package/dist/context/identity.js +5 -9
- package/dist/context/organization.d.ts +1 -1
- package/dist/context/organization.js +6 -8
- package/dist/context/session.js +6 -8
- package/dist/context/tab.js +8 -12
- package/dist/context/trace.js +4 -5
- package/dist/exporter/batch.d.ts +26 -27
- package/dist/exporter/batch.js +248 -294
- package/dist/exporter/browser-outbox-records.d.ts +8 -25
- package/dist/exporter/browser-outbox-records.js +21 -100
- package/dist/exporter/browser-outbox.d.ts +23 -25
- package/dist/exporter/browser-outbox.js +106 -181
- package/dist/exporter/client.d.ts +25 -35
- package/dist/exporter/client.js +107 -335
- package/dist/exporter/delivery-pipeline.d.ts +71 -0
- package/dist/exporter/delivery-pipeline.js +196 -0
- package/dist/exporter/delivery-response-contract.d.ts +1 -81
- package/dist/exporter/delivery-response-contract.js +0 -118
- package/dist/exporter/delivery-response.d.ts +9 -4
- package/dist/exporter/delivery-response.js +59 -122
- package/dist/exporter/diagnostics.d.ts +3 -2
- package/dist/exporter/diagnostics.js +5 -26
- package/dist/exporter/event-id.d.ts +1 -0
- package/dist/exporter/event-id.js +4 -3
- package/dist/exporter/frontend-resource.js +5 -4
- package/dist/exporter/observation-source-event-exporter.d.ts +2 -1
- package/dist/exporter/observation-source-event-exporter.js +7 -4
- package/dist/exporter/path-template.js +3 -1
- package/dist/exporter/raw-event-to-source-signal.js +4 -2
- package/dist/exporter/sanitize.d.ts +3 -2
- package/dist/exporter/sanitize.js +4 -3
- package/dist/exporter/sdk-core-delivery.d.ts +225 -0
- package/dist/exporter/sdk-core-delivery.js +97 -0
- package/dist/exporter/sdk-core-wasm.d.ts +11 -0
- package/dist/exporter/sdk-core-wasm.js +39 -0
- package/dist/exporter/source-event.d.ts +3 -0
- package/dist/exporter/source-event.js +17 -0
- package/dist/exporter/span-to-source-signal.d.ts +2 -2
- package/dist/exporter/span-to-source-signal.js +45 -125
- package/dist/index.d.ts +5 -4
- package/dist/index.js +18 -6
- package/dist/instrumentation/action.js +9 -8
- package/dist/instrumentation/dom.js +13 -21
- package/dist/instrumentation/drag.js +0 -3
- package/dist/instrumentation/error.js +3 -1
- package/dist/instrumentation/form.js +5 -6
- package/dist/instrumentation/input.js +4 -8
- package/dist/instrumentation/location.js +3 -1
- package/dist/instrumentation/network-request.js +1 -1
- package/dist/instrumentation/network-span-attributes.js +2 -2
- package/dist/instrumentation/network.d.ts +1 -1
- package/dist/instrumentation/network.js +24 -3
- package/dist/instrumentation/performance.js +1 -2
- package/dist/internal/internal-log.js +2 -1
- package/dist/provider/_version.d.ts +1 -1
- package/dist/provider/_version.js +1 -1
- package/dist/provider/config.js +16 -4
- package/dist/provider/constants.d.ts +1 -1
- package/dist/provider/constants.js +0 -2
- package/dist/provider/lifecycle-event-dedup.d.ts +7 -0
- package/dist/provider/lifecycle-event-dedup.js +31 -0
- package/dist/provider/sdk-event-pipeline.d.ts +3 -4
- package/dist/provider/sdk-event-pipeline.js +5 -17
- package/dist/provider/sdk-input-normalization.js +2 -2
- package/dist/provider/sdk-otel-pipeline.d.ts +2 -1
- package/dist/provider/sdk-otel-pipeline.js +7 -15
- package/dist/provider/sdk-runtime-surfaces.d.ts +17 -13
- package/dist/provider/sdk-runtime-surfaces.js +15 -13
- package/dist/provider/sdk.js +137 -48
- package/dist/provider/types.d.ts +18 -24
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.d.ts +44 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm.js +355 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm +0 -0
- package/dist/wasm/pkg-web/cluebase_sdk_core_wasm_bg.wasm.d.ts +11 -0
- package/dist/wasm/pkg-web/package.json +15 -0
- package/package.json +62 -53
- package/dist/provider/usage-governor.d.ts +0 -7
- package/dist/provider/usage-governor.js +0 -114
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { applyTransportResult, attemptRequest, beginClose, capturePermit, closeDriver, adoptRestoredBatch as coreAdoptRestoredBatch, setConsent as coreSetConsent, enqueueEvent, fenceQueue, openConsent, openDriver, pendingEventCount, planNextStep, queueDepth, setBrowserToken, } from "./sdk-core-delivery.js";
|
|
2
|
+
const RELEASED_PERMIT = Object.freeze({
|
|
3
|
+
generation: "",
|
|
4
|
+
gate_id: "",
|
|
5
|
+
permitted_at_start: false,
|
|
6
|
+
capture_open_at_start: false,
|
|
7
|
+
});
|
|
8
|
+
const RELEASED_ENQUEUE_REPORT = Object.freeze({
|
|
9
|
+
retained: false,
|
|
10
|
+
overflowed_event_keys: [],
|
|
11
|
+
diagnostics: [],
|
|
12
|
+
});
|
|
13
|
+
const RELEASED_RESTORE_REPORT = Object.freeze({
|
|
14
|
+
adopted: false,
|
|
15
|
+
event_keys: [],
|
|
16
|
+
});
|
|
17
|
+
const RELEASED_QUEUE_DEPTH = Object.freeze({
|
|
18
|
+
draft_count: 0,
|
|
19
|
+
draft_body_bytes: 0,
|
|
20
|
+
pending_bytes: 0,
|
|
21
|
+
});
|
|
22
|
+
const RELEASED_FENCE_REPORT = Object.freeze({
|
|
23
|
+
target_ordinal: 0,
|
|
24
|
+
oversized_event_keys: [],
|
|
25
|
+
frame_admits_no_event: true,
|
|
26
|
+
diagnostics: [],
|
|
27
|
+
});
|
|
28
|
+
const RELEASED_RUN_SUMMARY = Object.freeze({
|
|
29
|
+
observations: [],
|
|
30
|
+
attempts: 0,
|
|
31
|
+
stop: "queue_drained",
|
|
32
|
+
});
|
|
33
|
+
export class DeliveryPipeline {
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.released = false;
|
|
36
|
+
this.consentHandle = openConsent(options.initialConsent);
|
|
37
|
+
this.driverOptions = {
|
|
38
|
+
transport: {
|
|
39
|
+
surface: "frontend",
|
|
40
|
+
endpoint: options.endpoint,
|
|
41
|
+
project_key: options.projectKey,
|
|
42
|
+
sdk_request: options.sdkRequest,
|
|
43
|
+
sdk_version: options.sdkVersion,
|
|
44
|
+
source_schema_version: options.sourceSchemaVersion,
|
|
45
|
+
},
|
|
46
|
+
queue_limits: options.queueLimits,
|
|
47
|
+
...(options.requestTimeoutMs === undefined
|
|
48
|
+
? {}
|
|
49
|
+
: { request_timeout_ms: options.requestTimeoutMs }),
|
|
50
|
+
};
|
|
51
|
+
this.handle = openDriver(this.driverOptions, this.consentHandle);
|
|
52
|
+
this.transport = options.transport;
|
|
53
|
+
this.clock = options.clock;
|
|
54
|
+
}
|
|
55
|
+
reset() {
|
|
56
|
+
if (this.released)
|
|
57
|
+
return;
|
|
58
|
+
closeDriver(this.handle);
|
|
59
|
+
this.handle = openDriver(this.driverOptions, this.consentHandle);
|
|
60
|
+
}
|
|
61
|
+
permit() {
|
|
62
|
+
return this.released ? RELEASED_PERMIT : capturePermit(this.handle);
|
|
63
|
+
}
|
|
64
|
+
admit(event, permit) {
|
|
65
|
+
if (this.released)
|
|
66
|
+
return RELEASED_ENQUEUE_REPORT;
|
|
67
|
+
return enqueueEvent(this.handle, {
|
|
68
|
+
event_key: event.eventKey,
|
|
69
|
+
byte_length: event.byteLength,
|
|
70
|
+
permit,
|
|
71
|
+
...(event.diagnosticCanonicalBytes === undefined
|
|
72
|
+
? {}
|
|
73
|
+
: { diagnostic_canonical_bytes: event.diagnosticCanonicalBytes }),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
adoptRestoredBatch(ordinal, events) {
|
|
77
|
+
if (this.released)
|
|
78
|
+
return RELEASED_RESTORE_REPORT;
|
|
79
|
+
return coreAdoptRestoredBatch(this.handle, ordinal, events);
|
|
80
|
+
}
|
|
81
|
+
depth(envelopeOverheadBytes) {
|
|
82
|
+
if (this.released)
|
|
83
|
+
return RELEASED_QUEUE_DEPTH;
|
|
84
|
+
return queueDepth(this.handle, envelopeOverheadBytes);
|
|
85
|
+
}
|
|
86
|
+
pendingCount() {
|
|
87
|
+
return this.released ? 0 : pendingEventCount(this.handle);
|
|
88
|
+
}
|
|
89
|
+
fence(envelopeOverheadBytes) {
|
|
90
|
+
if (this.released)
|
|
91
|
+
return RELEASED_FENCE_REPORT;
|
|
92
|
+
return fenceQueue(this.handle, envelopeOverheadBytes);
|
|
93
|
+
}
|
|
94
|
+
rememberBrowserToken(token) {
|
|
95
|
+
if (this.released)
|
|
96
|
+
return;
|
|
97
|
+
setBrowserToken(this.handle, token);
|
|
98
|
+
}
|
|
99
|
+
declareConsent(consent) {
|
|
100
|
+
return coreSetConsent(this.consentHandle, consent);
|
|
101
|
+
}
|
|
102
|
+
beginClose() {
|
|
103
|
+
if (this.released)
|
|
104
|
+
return;
|
|
105
|
+
beginClose(this.handle);
|
|
106
|
+
}
|
|
107
|
+
dispose() {
|
|
108
|
+
if (this.released)
|
|
109
|
+
return false;
|
|
110
|
+
this.released = true;
|
|
111
|
+
return closeDriver(this.handle);
|
|
112
|
+
}
|
|
113
|
+
async run(input) {
|
|
114
|
+
if (this.released)
|
|
115
|
+
return RELEASED_RUN_SUMMARY;
|
|
116
|
+
const observations = [];
|
|
117
|
+
let attempts = 0;
|
|
118
|
+
let stop = "queue_drained";
|
|
119
|
+
const deadlineMs = () => typeof input.deadlineMs === "function"
|
|
120
|
+
? input.deadlineMs()
|
|
121
|
+
: input.deadlineMs;
|
|
122
|
+
while (true) {
|
|
123
|
+
if (this.released)
|
|
124
|
+
break;
|
|
125
|
+
const step = planNextStep(this.handle, this.clock.nowMs(), deadlineMs());
|
|
126
|
+
if (step.kind === "idle")
|
|
127
|
+
break;
|
|
128
|
+
if (step.kind === "blocked") {
|
|
129
|
+
stop = step.reason;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
if (step.kind === "wait_retry") {
|
|
133
|
+
await this.clock.sleep(step.delay_ms);
|
|
134
|
+
if (this.clock.nowMs() < step.resume_at_ms) {
|
|
135
|
+
stop = "retry_not_due";
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const batch = input.body(step.batch_id, step.event_keys);
|
|
141
|
+
if (!batch) {
|
|
142
|
+
stop = "batch_unavailable";
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
attempts += 1;
|
|
146
|
+
const request = attemptRequest(this.handle, {
|
|
147
|
+
attemptId: step.attempt_id,
|
|
148
|
+
body: batch.body,
|
|
149
|
+
timeoutMs: step.timeout_ms,
|
|
150
|
+
...(batch.diagnosticCanonicalBytes === undefined
|
|
151
|
+
? {}
|
|
152
|
+
: { batchCanonicalBytes: batch.diagnosticCanonicalBytes }),
|
|
153
|
+
});
|
|
154
|
+
const outcome = await this.transport(request);
|
|
155
|
+
if (this.released)
|
|
156
|
+
break;
|
|
157
|
+
const report = applyTransportResult(this.handle, step.attempt_id, outcome, this.clock.nowMs());
|
|
158
|
+
const observation = observe(report);
|
|
159
|
+
observations.push(observation);
|
|
160
|
+
input.onObservation?.(observation);
|
|
161
|
+
if (report.progress.kind === "retries_exhausted") {
|
|
162
|
+
stop = "retries_exhausted";
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
if (report.progress.kind === "consent_revoked") {
|
|
166
|
+
stop = "consent_revoked";
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { observations, attempts, stop };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const observe = (report) => {
|
|
174
|
+
const progress = report.progress;
|
|
175
|
+
const settled = progress.kind === "settled" ? progress.settled_event_keys : [];
|
|
176
|
+
const dropped = progress.kind === "settled"
|
|
177
|
+
? progress.dropped_event_keys
|
|
178
|
+
: progress.kind === "dropped_oversized"
|
|
179
|
+
? [progress.event_key]
|
|
180
|
+
: [];
|
|
181
|
+
const droppedEventCount = Math.max(dropped.length, report.decision?.result.dropped_event_count ?? 0);
|
|
182
|
+
return {
|
|
183
|
+
progress,
|
|
184
|
+
diagnostics: report.diagnostics,
|
|
185
|
+
settledEventKeys: settled,
|
|
186
|
+
droppedEventKeys: dropped,
|
|
187
|
+
droppedEventCount,
|
|
188
|
+
httpStatus: report.decision?.http_status ?? null,
|
|
189
|
+
reason: report.decision?.result.reason ?? null,
|
|
190
|
+
...(report.decision?.privacy_policy === undefined
|
|
191
|
+
? {}
|
|
192
|
+
: { privacyPolicy: report.decision.privacy_policy }),
|
|
193
|
+
directiveUnreadable: report.decision?.directive_unreadable === true,
|
|
194
|
+
diagnosticCodes: report.decision?.diagnostic_codes ?? [],
|
|
195
|
+
};
|
|
196
|
+
};
|
|
@@ -6,6 +6,7 @@ declare const ingestRetryStrategySchema: z.ZodEnum<{
|
|
|
6
6
|
mark_failed: "mark_failed";
|
|
7
7
|
drop_event: "drop_event";
|
|
8
8
|
}>;
|
|
9
|
+
export type IngestRetryStrategy = z.infer<typeof ingestRetryStrategySchema>;
|
|
9
10
|
export declare const ingestPrivacyPolicySchema: z.ZodObject<{
|
|
10
11
|
token: z.ZodString;
|
|
11
12
|
entries: z.ZodArray<z.ZodObject<{
|
|
@@ -19,85 +20,4 @@ export declare const ingestPrivacyPolicySchema: z.ZodObject<{
|
|
|
19
20
|
}, z.core.$strict>>;
|
|
20
21
|
}, z.core.$strict>;
|
|
21
22
|
export type IngestPrivacyPolicy = z.output<typeof ingestPrivacyPolicySchema>;
|
|
22
|
-
export declare const observationSourceEventBatchResponseSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
23
|
-
accepted: z.ZodLiteral<true>;
|
|
24
|
-
observation_ingest_batch_id: z.ZodString;
|
|
25
|
-
status: z.ZodLiteral<"received">;
|
|
26
|
-
submitted_event_count: z.ZodNumber;
|
|
27
|
-
accepted_event_count: z.ZodNumber;
|
|
28
|
-
dropped_event_count: z.ZodNumber;
|
|
29
|
-
dropped_events: z.ZodArray<z.ZodObject<{
|
|
30
|
-
event_index: z.ZodNumber;
|
|
31
|
-
reason: z.ZodString;
|
|
32
|
-
source_event_payload_fingerprint: z.ZodNullable<z.ZodString>;
|
|
33
|
-
}, z.core.$strict>>;
|
|
34
|
-
duplicate: z.ZodBoolean;
|
|
35
|
-
governor: z.ZodOptional<z.ZodObject<{
|
|
36
|
-
disabledCapabilities: z.ZodArray<z.ZodEnum<{
|
|
37
|
-
event_collection: "event_collection";
|
|
38
|
-
replay_storage: "replay_storage";
|
|
39
|
-
ai_generation: "ai_generation";
|
|
40
|
-
raw_observation_storage: "raw_observation_storage";
|
|
41
|
-
retention_growth: "retention_growth";
|
|
42
|
-
}>>;
|
|
43
|
-
limitReasons: z.ZodArray<z.ZodObject<{
|
|
44
|
-
limitKind: z.ZodEnum<{
|
|
45
|
-
monthly_overage_amount_limit: "monthly_overage_amount_limit";
|
|
46
|
-
internal_cost_limit: "internal_cost_limit";
|
|
47
|
-
internal_limit: "internal_limit";
|
|
48
|
-
operator_global_monthly_cost_limit: "operator_global_monthly_cost_limit";
|
|
49
|
-
}>;
|
|
50
|
-
meterKey: z.ZodString;
|
|
51
|
-
thresholdStage: z.ZodEnum<{
|
|
52
|
-
info: "info";
|
|
53
|
-
warning: "warning";
|
|
54
|
-
urgent: "urgent";
|
|
55
|
-
reached: "reached";
|
|
56
|
-
}>;
|
|
57
|
-
currentValue: z.ZodNumber;
|
|
58
|
-
limitValue: z.ZodNumber;
|
|
59
|
-
retryAfterAt: z.ZodNullable<z.ZodString>;
|
|
60
|
-
}, z.core.$strip>>;
|
|
61
|
-
retryAfterAt: z.ZodNullable<z.ZodString>;
|
|
62
|
-
}, z.core.$strip>>;
|
|
63
|
-
privacy_policy: z.ZodOptional<z.ZodObject<{
|
|
64
|
-
token: z.ZodString;
|
|
65
|
-
entries: z.ZodArray<z.ZodObject<{
|
|
66
|
-
method: z.ZodString;
|
|
67
|
-
path_template: z.ZodString;
|
|
68
|
-
direction: z.ZodEnum<{
|
|
69
|
-
request: "request";
|
|
70
|
-
response: "response";
|
|
71
|
-
}>;
|
|
72
|
-
field_path: z.ZodString;
|
|
73
|
-
}, z.core.$strict>>;
|
|
74
|
-
}, z.core.$strict>>;
|
|
75
|
-
}, z.core.$strip>, z.ZodObject<{
|
|
76
|
-
accepted: z.ZodLiteral<false>;
|
|
77
|
-
status: z.ZodEnum<{
|
|
78
|
-
rejected: "rejected";
|
|
79
|
-
failed: "failed";
|
|
80
|
-
}>;
|
|
81
|
-
reason: z.ZodString;
|
|
82
|
-
retry_strategy: z.ZodEnum<{
|
|
83
|
-
split_batch: "split_batch";
|
|
84
|
-
retry_same_batch: "retry_same_batch";
|
|
85
|
-
stop_and_report: "stop_and_report";
|
|
86
|
-
mark_failed: "mark_failed";
|
|
87
|
-
drop_event: "drop_event";
|
|
88
|
-
}>;
|
|
89
|
-
submitted_event_count: z.ZodOptional<z.ZodNumber>;
|
|
90
|
-
accepted_event_count: z.ZodOptional<z.ZodNumber>;
|
|
91
|
-
dropped_event_count: z.ZodOptional<z.ZodNumber>;
|
|
92
|
-
dropped_events: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
93
|
-
event_index: z.ZodNumber;
|
|
94
|
-
reason: z.ZodString;
|
|
95
|
-
source_event_payload_fingerprint: z.ZodNullable<z.ZodString>;
|
|
96
|
-
}, z.core.$strict>>>;
|
|
97
|
-
limits: z.ZodOptional<z.ZodObject<{
|
|
98
|
-
hard_max_payload_bytes: z.ZodNumber;
|
|
99
|
-
max_event_bytes: z.ZodNumber;
|
|
100
|
-
}, z.core.$strip>>;
|
|
101
|
-
}, z.core.$strip>], "accepted">;
|
|
102
|
-
export type IngestRetryStrategy = z.infer<typeof ingestRetryStrategySchema>;
|
|
103
23
|
export {};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { COST_CAPABILITIES, LIMIT_KINDS } from "../_generated/usage-governor-tables.js";
|
|
2
1
|
import { z } from "zod";
|
|
3
|
-
const MAX_INGEST_EVENTS_PER_BATCH = 10000;
|
|
4
2
|
const ingestRetryStrategySchema = z.enum([
|
|
5
3
|
"split_batch",
|
|
6
4
|
"retry_same_batch",
|
|
@@ -8,30 +6,6 @@ const ingestRetryStrategySchema = z.enum([
|
|
|
8
6
|
"mark_failed",
|
|
9
7
|
"drop_event",
|
|
10
8
|
]);
|
|
11
|
-
const ingestDroppedEventSchema = z
|
|
12
|
-
.object({
|
|
13
|
-
event_index: z.number().int().nonnegative(),
|
|
14
|
-
reason: z.string().trim().min(1).max(128),
|
|
15
|
-
source_event_payload_fingerprint: z
|
|
16
|
-
.string()
|
|
17
|
-
.regex(/^[0-9a-f]{64}$/)
|
|
18
|
-
.nullable(),
|
|
19
|
-
})
|
|
20
|
-
.strict();
|
|
21
|
-
const ingestUsageGovernorFeedbackSchema = z.object({
|
|
22
|
-
disabledCapabilities: z.array(z.enum(COST_CAPABILITIES)),
|
|
23
|
-
limitReasons: z.array(z.object({
|
|
24
|
-
limitKind: z.enum(LIMIT_KINDS),
|
|
25
|
-
meterKey: z.string().trim().min(1),
|
|
26
|
-
thresholdStage: z.enum(["info", "warning", "urgent", "reached"]),
|
|
27
|
-
currentValue: z.number().nonnegative(),
|
|
28
|
-
limitValue: z.number().nonnegative(),
|
|
29
|
-
retryAfterAt: z.string().datetime({ offset: true }).nullable(),
|
|
30
|
-
})),
|
|
31
|
-
retryAfterAt: z.string().datetime({ offset: true }).nullable(),
|
|
32
|
-
});
|
|
33
|
-
const boundedCount = z.number().int().nonnegative().max(MAX_INGEST_EVENTS_PER_BATCH);
|
|
34
|
-
const positiveCount = boundedCount.refine((count) => count > 0);
|
|
35
9
|
export const ingestPrivacyPolicySchema = z
|
|
36
10
|
.object({
|
|
37
11
|
token: z.string().trim().min(1).max(200),
|
|
@@ -47,95 +21,3 @@ export const ingestPrivacyPolicySchema = z
|
|
|
47
21
|
.max(500),
|
|
48
22
|
})
|
|
49
23
|
.strict();
|
|
50
|
-
const observationSourceEventBatchAcceptedResponseSchema = z
|
|
51
|
-
.object({
|
|
52
|
-
accepted: z.literal(true),
|
|
53
|
-
observation_ingest_batch_id: z.string().trim().min(1),
|
|
54
|
-
status: z.literal("received"),
|
|
55
|
-
submitted_event_count: positiveCount,
|
|
56
|
-
accepted_event_count: positiveCount,
|
|
57
|
-
dropped_event_count: boundedCount,
|
|
58
|
-
dropped_events: z.array(ingestDroppedEventSchema).max(MAX_INGEST_EVENTS_PER_BATCH),
|
|
59
|
-
duplicate: z.boolean(),
|
|
60
|
-
governor: ingestUsageGovernorFeedbackSchema.optional(),
|
|
61
|
-
privacy_policy: ingestPrivacyPolicySchema.optional(),
|
|
62
|
-
})
|
|
63
|
-
.superRefine((value, ctx) => {
|
|
64
|
-
if (value.submitted_event_count !== value.accepted_event_count + value.dropped_event_count) {
|
|
65
|
-
ctx.addIssue({
|
|
66
|
-
code: z.ZodIssueCode.custom,
|
|
67
|
-
path: ["submitted_event_count"],
|
|
68
|
-
message: "submitted count must equal accepted plus dropped counts",
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
if (!value.duplicate || value.dropped_events.length > 0) {
|
|
72
|
-
validateDroppedEventDetails(value.submitted_event_count, value.dropped_event_count, value.dropped_events, ctx);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
const observationSourceEventBatchRejectedResponseSchema = z
|
|
76
|
-
.object({
|
|
77
|
-
accepted: z.literal(false),
|
|
78
|
-
status: z.enum(["rejected", "failed"]),
|
|
79
|
-
reason: z.string().trim().min(1),
|
|
80
|
-
retry_strategy: ingestRetryStrategySchema,
|
|
81
|
-
submitted_event_count: positiveCount.optional(),
|
|
82
|
-
accepted_event_count: boundedCount.optional(),
|
|
83
|
-
dropped_event_count: boundedCount.optional(),
|
|
84
|
-
dropped_events: z.array(ingestDroppedEventSchema).max(MAX_INGEST_EVENTS_PER_BATCH).optional(),
|
|
85
|
-
limits: z
|
|
86
|
-
.object({
|
|
87
|
-
hard_max_payload_bytes: z.number().int().positive(),
|
|
88
|
-
max_event_bytes: z.number().int().positive(),
|
|
89
|
-
})
|
|
90
|
-
.refine((limits) => limits.max_event_bytes <= limits.hard_max_payload_bytes)
|
|
91
|
-
.optional(),
|
|
92
|
-
})
|
|
93
|
-
.superRefine((value, ctx) => {
|
|
94
|
-
const counts = [value.submitted_event_count, value.accepted_event_count, value.dropped_event_count];
|
|
95
|
-
const hasAnyCount = counts.some((count) => count !== undefined);
|
|
96
|
-
const hasAllCounts = counts.every((count) => count !== undefined);
|
|
97
|
-
if (hasAnyCount !== hasAllCounts) {
|
|
98
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["submitted_event_count"], message: "rejected response counts must be all present or all absent" });
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (hasAllCounts) {
|
|
102
|
-
const [submitted, accepted, dropped] = counts;
|
|
103
|
-
if (accepted > submitted || dropped > submitted || accepted + dropped > submitted) {
|
|
104
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dropped_event_count"], message: "rejected counts must not exceed submitted count" });
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (value.retry_strategy === "drop_event") {
|
|
108
|
-
if (!hasAllCounts) {
|
|
109
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["submitted_event_count"], message: "drop rejection requires all counts" });
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
const [submitted, accepted, dropped] = counts;
|
|
113
|
-
if (accepted !== 0 || dropped !== submitted) {
|
|
114
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dropped_event_count"], message: "drop rejection must reject every submitted event" });
|
|
115
|
-
}
|
|
116
|
-
validateDroppedEventDetails(submitted, dropped, value.dropped_events ?? [], ctx);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
else if ((value.dropped_events?.length ?? 0) > 0) {
|
|
120
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dropped_events"], message: "non-drop rejection must not contain dropped event details" });
|
|
121
|
-
}
|
|
122
|
-
if ((value.retry_strategy === "split_batch") !== (value.limits !== undefined)) {
|
|
123
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["limits"], message: "byte limits are valid only for split rejection" });
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
export const observationSourceEventBatchResponseSchema = z.discriminatedUnion("accepted", [
|
|
127
|
-
observationSourceEventBatchAcceptedResponseSchema,
|
|
128
|
-
observationSourceEventBatchRejectedResponseSchema,
|
|
129
|
-
]);
|
|
130
|
-
function validateDroppedEventDetails(submittedCount, droppedCount, droppedEvents, ctx) {
|
|
131
|
-
if (droppedEvents.length !== droppedCount) {
|
|
132
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dropped_events"], message: "dropped event details must match dropped count" });
|
|
133
|
-
}
|
|
134
|
-
const indexes = new Set();
|
|
135
|
-
for (const [detailIndex, detail] of droppedEvents.entries()) {
|
|
136
|
-
if (detail.event_index >= submittedCount || indexes.has(detail.event_index)) {
|
|
137
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["dropped_events", detailIndex, "event_index"], message: "dropped event indexes must be valid and unique" });
|
|
138
|
-
}
|
|
139
|
-
indexes.add(detail.event_index);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
1
|
+
import type { IngestRetryStrategy } from "./delivery-response-contract.js";
|
|
2
|
+
import type { DiagnosticCode } from "./diagnostics.js";
|
|
3
|
+
import { type TransportOutcome } from "./sdk-core-delivery.js";
|
|
4
4
|
export type { IngestRetryStrategy } from "./delivery-response-contract.js";
|
|
5
5
|
export type BrowserDeliveryDecision = {
|
|
6
6
|
readonly ok: boolean;
|
|
7
7
|
readonly privacyPolicy?: unknown;
|
|
8
|
+
readonly directiveUnreadable: boolean;
|
|
8
9
|
readonly retryStrategy: IngestRetryStrategy | null;
|
|
9
|
-
readonly governor: IngestUsageGovernorFeedback | null;
|
|
10
10
|
readonly observationIngestBatchId?: string;
|
|
11
11
|
readonly droppedEventCount: number;
|
|
12
12
|
readonly acceptedEventCount: number;
|
|
13
13
|
readonly acceptedPartial: boolean;
|
|
14
|
+
readonly acceptedFiltered: boolean;
|
|
14
15
|
readonly reason: string | null;
|
|
16
|
+
readonly diagnosticCodes: readonly DiagnosticCode[];
|
|
15
17
|
};
|
|
16
18
|
export declare const evaluateBrowserDeliveryResponse: (input: {
|
|
17
19
|
readonly status: number;
|
|
18
20
|
readonly bodyText: string;
|
|
19
21
|
readonly localSubmittedCount: number;
|
|
20
22
|
}) => BrowserDeliveryDecision;
|
|
23
|
+
export declare const evaluateBrowserTransportOutcome: (outcome: Exclude<TransportOutcome, {
|
|
24
|
+
kind: "response";
|
|
25
|
+
}>) => BrowserDeliveryDecision;
|
|
21
26
|
export declare const classifyIngestHttpStatus: (status: number) => IngestRetryStrategy;
|
|
@@ -1,132 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
stale_browser_token: "retry_same_batch",
|
|
13
|
-
origin_not_allowed: "stop_and_report",
|
|
14
|
-
authorization_denied: "stop_and_report",
|
|
15
|
-
idempotency_conflict: "mark_failed",
|
|
16
|
-
batch_too_large: "split_batch",
|
|
17
|
-
all_events_dropped: "drop_event",
|
|
18
|
-
usage_governor_denied: "retry_same_batch",
|
|
19
|
-
all_events_rejected_by_ingest_controls: "retry_same_batch",
|
|
20
|
-
route_throttled: "retry_same_batch",
|
|
21
|
-
raw_ingest_produce_failed: "retry_same_batch",
|
|
22
|
-
raw_ingest_ack_unknown: "retry_same_batch",
|
|
23
|
-
raw_ingest_receipt_metadata_failed: "retry_same_batch",
|
|
24
|
-
ingest_config_changed_retryable: "retry_same_batch",
|
|
25
|
-
usage_meter_unavailable: "retry_same_batch",
|
|
1
|
+
import { evaluateResponse, } from "./sdk-core-delivery.js";
|
|
2
|
+
const RETRY_STRATEGY_BY_OUTCOME = {
|
|
3
|
+
accepted_complete: null,
|
|
4
|
+
accepted_filtered: null,
|
|
5
|
+
accepted_partial: null,
|
|
6
|
+
retry_same_batch: "retry_same_batch",
|
|
7
|
+
split_batch: "split_batch",
|
|
8
|
+
terminal_stop: "stop_and_report",
|
|
9
|
+
terminal_failed: "mark_failed",
|
|
10
|
+
terminal_drop: "drop_event",
|
|
11
|
+
invalid_response: "retry_same_batch",
|
|
26
12
|
};
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
ok: true,
|
|
41
|
-
retryStrategy: null,
|
|
42
|
-
privacyPolicy: parsed.data.privacy_policy,
|
|
43
|
-
governor: parseGovernorFeedbackEnvelope(parsed.data),
|
|
44
|
-
observationIngestBatchId: parsed.data.observation_ingest_batch_id,
|
|
45
|
-
droppedEventCount: parsed.data.dropped_event_count,
|
|
46
|
-
acceptedEventCount: parsed.data.accepted_event_count,
|
|
47
|
-
acceptedPartial: partial,
|
|
48
|
-
reason: partial ? "accepted_partial" : null,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
if (BROWSER_REJECTION_STRATEGIES[parsed.data.reason] !==
|
|
52
|
-
parsed.data.retry_strategy) {
|
|
53
|
-
return invalidResponse();
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
ok: false,
|
|
57
|
-
retryStrategy: parsed.data.retry_strategy,
|
|
58
|
-
governor: null,
|
|
59
|
-
droppedEventCount: parsed.data.dropped_event_count ?? 0,
|
|
60
|
-
acceptedEventCount: parsed.data.accepted_event_count ?? 0,
|
|
61
|
-
acceptedPartial: false,
|
|
62
|
-
reason: parsed.data.reason,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
return invalidResponse();
|
|
13
|
+
const ACCEPTED_OUTCOMES = new Set([
|
|
14
|
+
"accepted_complete",
|
|
15
|
+
"accepted_filtered",
|
|
16
|
+
"accepted_partial",
|
|
17
|
+
]);
|
|
18
|
+
export const evaluateBrowserDeliveryResponse = (input) => translate({ kind: "response", status: input.status, body: input.bodyText }, input.localSubmittedCount);
|
|
19
|
+
export const evaluateBrowserTransportOutcome = (outcome) => translate(outcome, 1);
|
|
20
|
+
export const classifyIngestHttpStatus = (status) => translate({ kind: "response", status, body: "" }, 1).retryStrategy ??
|
|
21
|
+
"retry_same_batch";
|
|
22
|
+
const translate = (outcome, localSubmittedCount) => {
|
|
23
|
+
const evaluation = evaluateResponse(outcome, localSubmittedCount);
|
|
24
|
+
if (evaluation.kind !== "decision") {
|
|
25
|
+
return consentRevoked();
|
|
66
26
|
}
|
|
67
|
-
return
|
|
27
|
+
return project(evaluation.decision);
|
|
68
28
|
};
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
29
|
+
const project = (decision) => {
|
|
30
|
+
const accepted = ACCEPTED_OUTCOMES.has(decision.outcome);
|
|
31
|
+
const batchId = decision.result.observation_ingest_batch_id;
|
|
32
|
+
return {
|
|
33
|
+
ok: accepted,
|
|
34
|
+
retryStrategy: accepted
|
|
35
|
+
? null
|
|
36
|
+
: (RETRY_STRATEGY_BY_OUTCOME[decision.outcome] ?? "retry_same_batch"),
|
|
37
|
+
...(decision.privacy_policy === undefined
|
|
38
|
+
? {}
|
|
39
|
+
: { privacyPolicy: decision.privacy_policy }),
|
|
40
|
+
directiveUnreadable: decision.directive_unreadable === true,
|
|
41
|
+
...(batchId === undefined ? {} : { observationIngestBatchId: batchId }),
|
|
42
|
+
droppedEventCount: decision.result.dropped_event_count ?? 0,
|
|
43
|
+
acceptedEventCount: decision.result.accepted_event_count ?? 0,
|
|
44
|
+
acceptedPartial: decision.outcome === "accepted_partial",
|
|
45
|
+
acceptedFiltered: decision.outcome === "accepted_filtered",
|
|
46
|
+
reason: reasonOf(decision),
|
|
47
|
+
diagnosticCodes: decision.diagnostic_codes,
|
|
48
|
+
};
|
|
81
49
|
};
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
droppedEventCount: 0,
|
|
91
|
-
acceptedEventCount: 0,
|
|
92
|
-
acceptedPartial: false,
|
|
93
|
-
reason: "invalid_response",
|
|
94
|
-
});
|
|
95
|
-
export const classifyIngestHttpStatus = (status) => {
|
|
96
|
-
if (status >= 200 && status < 300)
|
|
97
|
-
return "retry_same_batch";
|
|
98
|
-
if (status === 409)
|
|
99
|
-
return "mark_failed";
|
|
100
|
-
if (status === 413)
|
|
101
|
-
return "split_batch";
|
|
102
|
-
if ([400, 401, 403, 404, 405, 406, 410, 415].includes(status)) {
|
|
103
|
-
return "stop_and_report";
|
|
104
|
-
}
|
|
105
|
-
return "retry_same_batch";
|
|
106
|
-
};
|
|
107
|
-
const fallback = (status) => {
|
|
108
|
-
const retryStrategy = classifyIngestHttpStatus(status);
|
|
109
|
-
if (status >= 200 && status < 300)
|
|
110
|
-
return invalidResponse();
|
|
111
|
-
if (retryStrategy === "retry_same_batch")
|
|
112
|
-
return retry(status);
|
|
113
|
-
return terminal(retryStrategy, status);
|
|
50
|
+
const reasonOf = (decision) => {
|
|
51
|
+
if (decision.outcome === "accepted_complete")
|
|
52
|
+
return null;
|
|
53
|
+
if (decision.outcome === "accepted_partial")
|
|
54
|
+
return "accepted_partial";
|
|
55
|
+
if (decision.outcome === "accepted_filtered")
|
|
56
|
+
return "accepted_filtered";
|
|
57
|
+
return decision.result.reason ?? decision.outcome;
|
|
114
58
|
};
|
|
115
|
-
const
|
|
116
|
-
ok: false,
|
|
117
|
-
retryStrategy: "retry_same_batch",
|
|
118
|
-
governor: null,
|
|
119
|
-
droppedEventCount: 0,
|
|
120
|
-
acceptedEventCount: 0,
|
|
121
|
-
acceptedPartial: false,
|
|
122
|
-
reason: `http_${status}`,
|
|
123
|
-
});
|
|
124
|
-
const terminal = (retryStrategy, status) => ({
|
|
59
|
+
const consentRevoked = () => ({
|
|
125
60
|
ok: false,
|
|
126
|
-
retryStrategy,
|
|
127
|
-
|
|
61
|
+
retryStrategy: "drop_event",
|
|
62
|
+
directiveUnreadable: false,
|
|
128
63
|
droppedEventCount: 0,
|
|
129
64
|
acceptedEventCount: 0,
|
|
130
65
|
acceptedPartial: false,
|
|
131
|
-
|
|
66
|
+
acceptedFiltered: false,
|
|
67
|
+
reason: "consent_denied",
|
|
68
|
+
diagnosticCodes: [],
|
|
132
69
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
1
|
+
import { type DiagnosticCode } from "../_generated/diagnostic-codes.js";
|
|
2
|
+
import type { IngestRetryStrategy } from "./delivery-response-contract.js";
|
|
3
|
+
export type { DiagnosticCode };
|
|
3
4
|
export type DiagnosticLifecycle = "open" | "closing" | "closing_failed" | "closed";
|
|
4
5
|
export type DiagnosticFlushOutcome = "clean" | "failed" | null;
|
|
5
6
|
export type DiagnosticCloseOutcome = "clean" | "retryable_failure" | "terminal_failure" | null;
|