@volter/twin-sentry 0.1.37 → 1.0.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.
- package/README.md +21 -134
- package/api/spec/SOURCE.md +15 -0
- package/api/spec/doc-examples.json +12 -0
- package/api/spec/openapi.json.gz +0 -0
- package/api/spec/patches.json +5350 -0
- package/api/spec/sources.json +122 -0
- package/api/src/fetch.ts +18 -0
- package/api/src/generated/surface.gen.json +1 -0
- package/api/src/generated/ui.gen.json +1 -0
- package/api/src/index.ts +3 -0
- package/api/src/manifest.ts +136 -0
- package/api/src/semantics/api.ts +246 -0
- package/api/src/semantics/around.ts +36 -0
- package/api/src/semantics/doors.ts +50 -0
- package/api/src/semantics/index.ts +19 -0
- package/api/src/semantics/shared.ts +35 -0
- package/api/src/semantics/states.ts +17 -0
- package/dist/api/spec/SOURCE.md +15 -0
- package/dist/api/spec/doc-examples.json +12 -0
- package/dist/api/spec/openapi.json.gz +0 -0
- package/dist/api/spec/patches.json +5350 -0
- package/dist/api/spec/sources.json +122 -0
- package/dist/api/src/fetch.d.ts +2 -0
- package/dist/api/src/fetch.js +17 -0
- package/dist/api/src/fetch.ts +18 -0
- package/dist/api/src/generated/surface.gen.json +1 -0
- package/dist/api/src/generated/ui.gen.json +1 -0
- package/dist/api/src/index.d.ts +2 -0
- package/dist/api/src/index.js +3 -0
- package/dist/api/src/index.ts +3 -0
- package/dist/api/src/manifest.d.ts +2 -0
- package/dist/api/src/manifest.js +135 -0
- package/dist/api/src/manifest.ts +136 -0
- package/dist/api/src/semantics/api.d.ts +13 -0
- package/dist/api/src/semantics/api.js +267 -0
- package/dist/api/src/semantics/api.ts +246 -0
- package/dist/api/src/semantics/around.d.ts +2 -0
- package/dist/api/src/semantics/around.js +41 -0
- package/dist/api/src/semantics/around.ts +36 -0
- package/dist/api/src/semantics/doors.d.ts +8 -0
- package/dist/api/src/semantics/doors.js +39 -0
- package/dist/api/src/semantics/doors.ts +50 -0
- package/dist/api/src/semantics/index.d.ts +2 -0
- package/dist/api/src/semantics/index.js +15 -0
- package/dist/api/src/semantics/index.ts +19 -0
- package/dist/api/src/semantics/shared.d.ts +15 -0
- package/dist/api/src/semantics/shared.js +27 -0
- package/dist/api/src/semantics/shared.ts +35 -0
- package/dist/api/src/semantics/states.d.ts +24 -0
- package/dist/api/src/semantics/states.js +17 -0
- package/dist/api/src/semantics/states.ts +17 -0
- package/dist/generated/pack-facts.json +87 -0
- package/dist/ingest/spec/SOURCE.md +30 -0
- package/dist/ingest/spec/authentication.mdx.gz +0 -0
- package/dist/ingest/spec/compression.mdx.gz +0 -0
- package/dist/ingest/spec/doc-examples.json +216 -0
- package/dist/ingest/spec/envelope-items.mdx.gz +0 -0
- package/dist/ingest/spec/envelopes.mdx.gz +0 -0
- package/dist/ingest/spec/openapi.json +178 -0
- package/dist/ingest/spec/rate-limiting.mdx.gz +0 -0
- package/dist/ingest/spec/sources.json +25 -0
- package/dist/ingest/spec/transport.mdx.gz +0 -0
- package/dist/ingest/spec/write-spec.ts +192 -0
- package/dist/ingest/src/fetch.d.ts +2 -0
- package/dist/ingest/src/fetch.js +15 -0
- package/dist/ingest/src/fetch.ts +16 -0
- package/dist/ingest/src/generated/surface.gen.json +1 -0
- package/dist/ingest/src/generated/ui.gen.json +1 -0
- package/dist/ingest/src/index.d.ts +2 -0
- package/dist/ingest/src/index.js +3 -0
- package/dist/ingest/src/index.ts +3 -0
- package/dist/ingest/src/manifest.d.ts +2 -0
- package/dist/ingest/src/manifest.js +34 -0
- package/dist/ingest/src/manifest.ts +39 -0
- package/dist/ingest/src/semantics/api.d.ts +2 -0
- package/dist/ingest/src/semantics/api.js +240 -0
- package/dist/ingest/src/semantics/api.ts +188 -0
- package/dist/ingest/src/semantics/around.d.ts +2 -0
- package/dist/ingest/src/semantics/around.js +6 -0
- package/dist/ingest/src/semantics/around.ts +7 -0
- package/dist/ingest/src/semantics/index.d.ts +2 -0
- package/dist/ingest/src/semantics/index.js +4 -0
- package/dist/ingest/src/semantics/index.ts +8 -0
- package/dist/ingest/src/semantics/shared.d.ts +4 -0
- package/dist/ingest/src/semantics/shared.js +2 -0
- package/dist/ingest/src/semantics/shared.ts +7 -0
- package/dist/ingest/src/semantics/states.d.ts +1 -0
- package/dist/ingest/src/semantics/states.js +2 -0
- package/dist/ingest/src/semantics/states.ts +2 -0
- package/dist/journeys/coverage.json +205 -0
- package/dist/journeys/customer-life.json +923 -0
- package/dist/journeys/customer-life.outline.md +51 -0
- package/dist/journeys/decisions.json +170 -0
- package/dist/journeys/demand.json +136 -0
- package/dist/journeys/score.json +231 -0
- package/dist/journeys/unreachable.json +29 -0
- package/dist/journeys/vendor-examples.json +474 -0
- package/dist/spec/sources.json +76 -0
- package/dist/src/cli.js +12 -27
- package/dist/src/fetch.d.ts +2 -0
- package/dist/src/fetch.js +9 -0
- package/dist/src/index.d.ts +5 -15
- package/dist/src/index.js +12 -75
- package/dist/src/manifest.d.ts +2 -0
- package/dist/src/manifest.js +34 -0
- package/dist/src/semantics/shared.d.ts +87 -0
- package/dist/src/semantics/shared.js +411 -0
- package/dist/src/server.d.ts +7 -0
- package/dist/src/server.js +7 -0
- package/generated/pack-facts.json +87 -0
- package/ingest/spec/SOURCE.md +30 -0
- package/ingest/spec/authentication.mdx.gz +0 -0
- package/ingest/spec/compression.mdx.gz +0 -0
- package/ingest/spec/doc-examples.json +216 -0
- package/ingest/spec/envelope-items.mdx.gz +0 -0
- package/ingest/spec/envelopes.mdx.gz +0 -0
- package/ingest/spec/openapi.json +178 -0
- package/ingest/spec/rate-limiting.mdx.gz +0 -0
- package/ingest/spec/sources.json +25 -0
- package/ingest/spec/transport.mdx.gz +0 -0
- package/ingest/spec/write-spec.ts +192 -0
- package/ingest/src/fetch.ts +16 -0
- package/ingest/src/generated/surface.gen.json +1 -0
- package/ingest/src/generated/ui.gen.json +1 -0
- package/ingest/src/index.ts +3 -0
- package/ingest/src/manifest.ts +39 -0
- package/ingest/src/semantics/api.ts +188 -0
- package/ingest/src/semantics/around.ts +7 -0
- package/ingest/src/semantics/index.ts +8 -0
- package/ingest/src/semantics/shared.ts +7 -0
- package/ingest/src/semantics/states.ts +2 -0
- package/journeys/coverage.json +205 -0
- package/journeys/customer-life.json +923 -0
- package/journeys/customer-life.outline.md +51 -0
- package/journeys/decisions.json +170 -0
- package/journeys/demand.json +136 -0
- package/journeys/score.json +231 -0
- package/journeys/unreachable.json +29 -0
- package/journeys/vendor-examples.json +474 -0
- package/package.json +16 -42
- package/spec/sources.json +76 -0
- package/src/cli.ts +12 -24
- package/src/fetch.ts +10 -0
- package/src/index.ts +12 -143
- package/src/manifest.ts +40 -0
- package/src/semantics/shared.ts +443 -0
- package/src/server.ts +8 -0
- package/client/sentry-mirror.css +0 -99
- package/client/sentry-mirror.tsx +0 -352
- package/dist/client/sentry-mirror.bundle.js +0 -388
- package/dist/client/sentry-mirror.css +0 -99
- package/dist/client/sentry-mirror.d.ts +0 -17
- package/dist/client/sentry-mirror.js +0 -156
- package/dist/client/sentry-mirror.tsx +0 -352
- package/dist/src/sentry-budget.d.ts +0 -50
- package/dist/src/sentry-budget.js +0 -145
- package/dist/src/sentry-capabilities.d.ts +0 -3
- package/dist/src/sentry-capabilities.js +0 -1180
- package/dist/src/sentry-conformance.d.ts +0 -22
- package/dist/src/sentry-conformance.js +0 -97
- package/dist/src/sentry-connector.d.ts +0 -77
- package/dist/src/sentry-connector.js +0 -226
- package/dist/src/sentry-events.d.ts +0 -54
- package/dist/src/sentry-events.js +0 -132
- package/dist/src/sentry-ingest.d.ts +0 -137
- package/dist/src/sentry-ingest.js +0 -387
- package/dist/src/sentry-mirror-ui.d.ts +0 -38
- package/dist/src/sentry-mirror-ui.js +0 -162
- package/dist/src/sentry-perform-harness.d.ts +0 -9
- package/dist/src/sentry-perform-harness.js +0 -20
- package/dist/src/sentry-server.d.ts +0 -14
- package/dist/src/sentry-server.js +0 -27
- package/dist/src/sentry-twin.d.ts +0 -17
- package/dist/src/sentry-twin.js +0 -1739
- package/dist/test-fixtures/sentry-openapi-operations.SOURCE.md +0 -24
- package/dist/test-fixtures/sentry-openapi-operations.json +0 -1837
- package/src/sentry-budget.ts +0 -171
- package/src/sentry-capabilities.ts +0 -1222
- package/src/sentry-conformance.ts +0 -110
- package/src/sentry-connector.ts +0 -260
- package/src/sentry-events.ts +0 -172
- package/src/sentry-ingest.ts +0 -471
- package/src/sentry-mirror-ui.ts +0 -162
- package/src/sentry-perform-harness.ts +0 -19
- package/src/sentry-server.ts +0 -35
- package/src/sentry-twin.ts +0 -1615
- package/test-fixtures/sentry-openapi-operations.SOURCE.md +0 -24
- package/test-fixtures/sentry-openapi-operations.json +0 -1837
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Sentry's ingestion endpoint (spec: sendEnvelope; develop-docs/sdk/foundations/envelopes/index.mdx): an envelope, its
|
|
2
|
+
// header line and its items, each kept as what it is. The envelope is authenticated by the DSN's public key (the
|
|
3
|
+
// X-Sentry-Auth header, the `sentry_key` query parameter, or the envelope header's `dsn`), and answered with its event id.
|
|
4
|
+
import { s3, type HandlerContext } from '@volter/world-core';
|
|
5
|
+
import { apiManifest, ATTACHMENT, CHECK_IN, CLIENT_REPORT, keepEvent, LOG, METRIC, MONITOR, next, OTHER_ITEM, PROFILE, projectByKey, REPLAY, type Row, SESSION } from './shared.ts';
|
|
6
|
+
|
|
7
|
+
const obj = (v: unknown): Row => (v && typeof v === 'object' && !Array.isArray(v) ? v as Row : {});
|
|
8
|
+
const refuse = (status: number, detail: string): Response =>
|
|
9
|
+
Response.json({ detail }, { status, headers: { 'x-sentry-error': detail } });
|
|
10
|
+
|
|
11
|
+
// source: https://raw.githubusercontent.com/getsentry/sentry-docs/3ec4e139711af82b38a3c2c17261f5f5cdb66dfc/develop-docs/sdk/foundations/envelopes/index.mdx "*200 MiB* for an envelope after decompression including all envelope items."
|
|
12
|
+
const MAX_ENVELOPE = 200 * 1024 * 1024;
|
|
13
|
+
|
|
14
|
+
/** The body's bytes: the kernel has decoded a gzip, deflate, br or zstd body (the manifest's `encodings`) and dropped the
|
|
15
|
+
* header, so a Content-Encoding still on the request is one Sentry does not read. */
|
|
16
|
+
function decoded(bytes: Uint8Array, encoding: string | null): Uint8Array | string {
|
|
17
|
+
const coding = (encoding ?? '').toLowerCase();
|
|
18
|
+
return coding === '' || coding === 'identity' ? bytes : `unsupported content encoding: ${encoding}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type Item = { header: Row; payload: Uint8Array };
|
|
22
|
+
|
|
23
|
+
/** An envelope read: its header and items. An item's header may give its `length`, else it runs to the newline
|
|
24
|
+
* (develop-docs/sdk/foundations/envelopes/index.mdx, "Envelopes" and "Items"). */
|
|
25
|
+
function parse(bytes: Uint8Array): { header: Row; items: Item[] } | string {
|
|
26
|
+
let at = 0;
|
|
27
|
+
const line = (): Uint8Array | undefined => {
|
|
28
|
+
if (at >= bytes.length) return undefined;
|
|
29
|
+
let end = bytes.indexOf(10, at);
|
|
30
|
+
if (end < 0) end = bytes.length;
|
|
31
|
+
const out = bytes.subarray(at, end);
|
|
32
|
+
at = end + 1;
|
|
33
|
+
return out;
|
|
34
|
+
};
|
|
35
|
+
const json = (b: Uint8Array | undefined): Row | undefined => {
|
|
36
|
+
if (!b) return undefined;
|
|
37
|
+
try { const v = JSON.parse(new TextDecoder().decode(b)); return v && typeof v === 'object' && !Array.isArray(v) ? v : undefined; } catch { return undefined; }
|
|
38
|
+
};
|
|
39
|
+
const header = json(line());
|
|
40
|
+
if (!header) return 'invalid envelope header';
|
|
41
|
+
const items: Item[] = [];
|
|
42
|
+
while (at < bytes.length) {
|
|
43
|
+
const raw = line();
|
|
44
|
+
if (!raw || raw.length === 0 || (raw.length === 1 && raw[0] === 13)) continue;
|
|
45
|
+
const h = json(raw);
|
|
46
|
+
if (!h || typeof h.type !== 'string') return 'invalid item header';
|
|
47
|
+
let payload: Uint8Array;
|
|
48
|
+
if (typeof h.length === 'number') {
|
|
49
|
+
if (!Number.isInteger(h.length) || h.length < 0) return 'invalid item length';
|
|
50
|
+
if (at + h.length > bytes.length) return 'item payload is shorter than its length';
|
|
51
|
+
payload = bytes.subarray(at, at + h.length);
|
|
52
|
+
at += h.length;
|
|
53
|
+
if (at < bytes.length && bytes[at] !== 10) return 'length-prefixed payload must end with newline or EOF';
|
|
54
|
+
if (bytes[at] === 10) at += 1;
|
|
55
|
+
} else payload = line() ?? new Uint8Array(0);
|
|
56
|
+
items.push({ header: h, payload });
|
|
57
|
+
}
|
|
58
|
+
return { header, items };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The public key a request authenticates with: X-Sentry-Auth's `sentry_key`, the query's, or the envelope's DSN. */
|
|
62
|
+
function publicKeyOf(request: Request, header: Row): string | undefined {
|
|
63
|
+
const auth = request.headers.get('x-sentry-auth') ?? '';
|
|
64
|
+
const fromHeader = /sentry_key=([^,\s]+)/.exec(auth)?.[1];
|
|
65
|
+
const fromQuery = new URL(request.url).searchParams.get('sentry_key') ?? undefined;
|
|
66
|
+
let fromDsn: string | undefined;
|
|
67
|
+
if (typeof header.dsn === 'string') { try { fromDsn = new URL(header.dsn).username || undefined; } catch { /* not a URL */ } }
|
|
68
|
+
const given = [fromHeader, fromQuery, fromDsn].filter((k): k is string => !!k);
|
|
69
|
+
// "If multiple forms of authentication are given, the endpoint validates that the information matches and otherwise
|
|
70
|
+
// rejects the request" (envelopes/index.mdx, Authentication)
|
|
71
|
+
return given.length && given.every((k) => k === given[0]) ? given[0] : given.length ? '' : undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const text = (b: Uint8Array): string => new TextDecoder().decode(b);
|
|
75
|
+
const asJson = (b: Uint8Array): Row => { try { return obj(JSON.parse(text(b))); } catch { return {}; } };
|
|
76
|
+
|
|
77
|
+
// source: spec:sendEnvelope "The main ingestion endpoint for Envelopes."
|
|
78
|
+
export async function sendEnvelope(ctx: HandlerContext): Promise<Response> {
|
|
79
|
+
const request = ctx.call.request;
|
|
80
|
+
const body = decoded(s3.bytesOf(request), request.headers.get('content-encoding'));
|
|
81
|
+
ctx = await ctx.over(apiManifest);
|
|
82
|
+
if (typeof body === 'string') return refuse(400, body);
|
|
83
|
+
if (body.length > MAX_ENVELOPE) return refuse(413, 'envelope exceeded size limits');
|
|
84
|
+
const envelope = parse(body);
|
|
85
|
+
if (typeof envelope === 'string') return refuse(400, `invalid envelope: ${envelope}`);
|
|
86
|
+
const key = publicKeyOf(request, envelope.header);
|
|
87
|
+
// "If both are missing, the Envelope is rejected with status code `403 Forbidden`." (envelopes/index.mdx)
|
|
88
|
+
if (key === undefined) return refuse(403, 'missing authorization information');
|
|
89
|
+
if (key === '') return refuse(403, 'the authentication given does not match');
|
|
90
|
+
// Where the documentation stops: Relay's answer for a key no project has is not a page's; the twin refuses it as
|
|
91
|
+
// missing authentication is refused, and a key of another project than the path's likewise
|
|
92
|
+
const project = projectByKey(ctx, key);
|
|
93
|
+
if (!project || String(project.id) !== String(ctx.call.params.project_id)) return refuse(403, 'invalid api key');
|
|
94
|
+
const org = obj(ctx.row('organization', String(project._organization)));
|
|
95
|
+
const envelopeId = typeof envelope.header.event_id === 'string' ? envelope.header.event_id.replace(/-/g, '') : undefined;
|
|
96
|
+
const sentAt = typeof envelope.header.sent_at === 'string' ? envelope.header.sent_at : ctx.occurredAt;
|
|
97
|
+
const base = { project: project.id, organization: org.id ?? project._organization, received: ctx.occurredAt, sent_at: sentAt };
|
|
98
|
+
// source: https://raw.githubusercontent.com/getsentry/sentry-docs/3ec4e139711af82b38a3c2c17261f5f5cdb66dfc/develop-docs/sdk/foundations/envelopes/index.mdx "*1 MiB* for event (errors and transactions), span, log, and metric (statsd, buckets, meta) envelope items."
|
|
99
|
+
let sessions = 0;
|
|
100
|
+
for (const item of envelope.items) {
|
|
101
|
+
const kind = String(item.header.type);
|
|
102
|
+
const limit = ({ event: 1048576, transaction: 1048576, span: 1048576, log: 1048576, trace_metric: 1048576, statsd: 1048576, metric_buckets: 1048576, metric_meta: 1048576, check_in: 102400, client_report: 4096, profile: 52428800, replay_event: 104857600, replay_recording: 10485760, replay_video: 10485760 } as Record<string, number>)[kind];
|
|
103
|
+
if (limit && item.payload.length > limit) return refuse(413, 'item exceeded size limits');
|
|
104
|
+
const payload = asJson(item.payload);
|
|
105
|
+
if (kind === 'session') sessions++;
|
|
106
|
+
if (sessions > 100 || (kind === 'sessions' && Array.isArray(payload.aggregates) && payload.aggregates.length > 100)) return refuse(413, 'too many sessions');
|
|
107
|
+
if (kind === 'trace_metric' && Array.isArray(payload.items) && payload.items.some((metric) => new TextEncoder().encode(JSON.stringify(metric)).length > 2048)) return refuse(413, 'metric exceeded size limits');
|
|
108
|
+
}
|
|
109
|
+
let n = 0;
|
|
110
|
+
const envelopeN = await next(ctx, 'envelope');
|
|
111
|
+
const receipt = `envelope/${String(project.id)}/${envelopeN}`;
|
|
112
|
+
await ctx.blobs.put(receipt, body);
|
|
113
|
+
await ctx.record('_envelope', { ...base, header: envelope.header, blob: receipt, size: body.length }, `${String(project.id)}:${envelopeN}`);
|
|
114
|
+
const id = (kind: string): string => `${String(project.id)}:${kind}:${envelopeN}:${n++}`;
|
|
115
|
+
for (const item of envelope.items) {
|
|
116
|
+
const type = String(item.header.type);
|
|
117
|
+
const p = asJson(item.payload);
|
|
118
|
+
switch (type) {
|
|
119
|
+
case 'event': await keepEvent(ctx, project, { ...p, event_id: envelopeId ?? p.event_id }, p.exception ? 'error' : 'default'); break;
|
|
120
|
+
case 'transaction': await keepEvent(ctx, project, { ...p, event_id: envelopeId ?? p.event_id }, 'transaction'); break;
|
|
121
|
+
case 'feedback': await keepEvent(ctx, project, { ...p, event_id: envelopeId ?? p.event_id }, 'feedback'); break;
|
|
122
|
+
case 'attachment': {
|
|
123
|
+
const blob = `attachment/${String(project.id)}/${envelopeId ?? 'none'}/${String(item.header.filename ?? n)}`;
|
|
124
|
+
await ctx.blobs.put(blob, item.payload);
|
|
125
|
+
await ctx.record(ATTACHMENT, { ...base, event_id: envelopeId ?? null, name: item.header.filename ?? null, type: item.header.attachment_type ?? 'event.attachment', mimetype: item.header.content_type ?? 'application/octet-stream', size: item.payload.length, blob }, id(ATTACHMENT));
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case 'session': await ctx.record(SESSION, { ...base, sid: p.sid ?? null, did: p.did ?? null, status: p.status ?? 'ok', errors: p.errors ?? 0, init: p.init ?? false, started: p.started ?? null, duration: p.duration ?? null, attrs: obj(p.attrs) }, `${String(project.id)}:session:${String(p.sid ?? id(SESSION))}`); break;
|
|
129
|
+
case 'sessions': await ctx.record(SESSION, { ...base, aggregates: p.aggregates ?? [], attrs: obj(p.attrs) }, id(SESSION)); break;
|
|
130
|
+
case 'client_report': await ctx.record(CLIENT_REPORT, { ...base, timestamp: p.timestamp ?? null, discarded_events: p.discarded_events ?? [] }, id(CLIENT_REPORT)); break;
|
|
131
|
+
case 'replay_event': await ctx.record(REPLAY, { ...base, replay_id: p.replay_id ?? envelopeId ?? null, segment_id: p.segment_id ?? 0, replay_type: p.replay_type ?? null, urls: p.urls ?? [], error_ids: p.error_ids ?? [], trace_ids: p.trace_ids ?? [], timestamp: p.timestamp ?? null }, id(REPLAY)); break;
|
|
132
|
+
case 'replay_recording': case 'replay_video': {
|
|
133
|
+
const blob = `replay/${String(project.id)}/${envelopeId ?? 'none'}/${n}`;
|
|
134
|
+
await ctx.blobs.put(blob, item.payload);
|
|
135
|
+
await ctx.record(REPLAY, { ...base, replay_id: envelopeId ?? null, recording: blob, kind: type, size: item.payload.length }, id(REPLAY));
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'profile': case 'profile_chunk': {
|
|
139
|
+
const blob = `profile/${String(project.id)}/${envelopeId ?? 'none'}/${n}`;
|
|
140
|
+
await ctx.blobs.put(blob, item.payload);
|
|
141
|
+
await ctx.record(PROFILE, { ...base, kind: type, profile_id: p.profile_id ?? p.event_id ?? null, chunk_id: p.chunk_id ?? null, platform: p.platform ?? null, release: p.release ?? null, size: item.payload.length, blob }, id(PROFILE));
|
|
142
|
+
await ctx.write('project', String(project.id), { hasProfiles: true }, 'project.update');
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case 'check_in': {
|
|
146
|
+
const slug = String(p.monitor_slug ?? '');
|
|
147
|
+
const config = obj(p.monitor_config);
|
|
148
|
+
const monitor = ctx.rowsRaw(MONITOR).find((m) => m._project === project.id && m.slug === slug);
|
|
149
|
+
const monitorId = monitor ? String(monitor.id) : ctx.mint(MONITOR);
|
|
150
|
+
const monitorConfig = Object.keys(config).length ? {
|
|
151
|
+
schedule_type: obj(config.schedule).type, schedule: obj(config.schedule).value,
|
|
152
|
+
checkin_margin: config.checkin_margin ?? null, max_runtime: config.max_runtime ?? null, timezone: config.timezone ?? null,
|
|
153
|
+
failure_issue_threshold: config.failure_issue_threshold ?? null, recovery_threshold: config.recovery_threshold ?? null, alert_rule_id: null,
|
|
154
|
+
} : monitor?.config ?? {};
|
|
155
|
+
if (Object.keys(config).length) await ctx.write(MONITOR, monitorId, {
|
|
156
|
+
id: monitorId, _project: project.id, _organization: project._organization,
|
|
157
|
+
slug, name: slug, config: monitorConfig, status: 'active', isMuted: false, isUpserting: true,
|
|
158
|
+
dateCreated: monitor?.dateCreated ?? ctx.occurredAt, project: Object.fromEntries(Object.entries(ctx.own(project)).filter(([k]) => !k.startsWith('_'))),
|
|
159
|
+
environments: [], owner: null, alertRule: null,
|
|
160
|
+
}, monitor ? 'monitor.update' : 'monitor.create');
|
|
161
|
+
// source: https://raw.githubusercontent.com/getsentry/sentry/2efdd4760a75a3d395e3534b7f66a7fbb0c3f22e/src/sentry/monitors/serializers.py "str(obj.guid)"
|
|
162
|
+
const rawCheckInId = String(p.check_in_id ?? ctx.mint(CHECK_IN)).replace(/-/g, '');
|
|
163
|
+
const checkInId = rawCheckInId.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '$1-$2-$3-$4-$5');
|
|
164
|
+
const prior = ctx.row(CHECK_IN, checkInId);
|
|
165
|
+
if (prior && p.status !== prior.status) ctx.legal(CHECK_IN, 'status', 'sendEnvelope', prior.status, String(p.status), checkInId, 'vendor');
|
|
166
|
+
await ctx.write(CHECK_IN, checkInId, {
|
|
167
|
+
id: checkInId, _monitor: monitor || Object.keys(config).length ? monitorId : null,
|
|
168
|
+
status: p.status ?? null, duration: p.duration ?? prior?.duration ?? null, environment: p.environment ?? '',
|
|
169
|
+
dateCreated: prior?.dateCreated ?? ctx.occurredAt, dateAdded: prior?.dateAdded ?? ctx.occurredAt, dateUpdated: ctx.occurredAt,
|
|
170
|
+
dateInProgress: prior?.dateInProgress ?? (p.status === 'in_progress' ? ctx.occurredAt : null), dateClock: ctx.occurredAt, expectedTime: prior?.expectedTime ?? ctx.occurredAt,
|
|
171
|
+
monitorConfig, groups: [],
|
|
172
|
+
}, prior ? 'check_in.update' : 'check_in.create');
|
|
173
|
+
await ctx.write('project', String(project.id), { hasMonitors: true }, 'project.update');
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'log': for (const l of Array.isArray(p.items) ? p.items as Row[] : []) await ctx.record(LOG, { ...base, timestamp: typeof l.timestamp === 'number' ? new Date(l.timestamp * 1000).toISOString() : l.timestamp ?? null, level: l.level ?? null, body: l.body ?? null, trace_id: l.trace_id ?? null, attributes: obj(l.attributes) }, id(LOG)); break;
|
|
177
|
+
case 'trace_metric': for (const m of Array.isArray(p.items) ? p.items as Row[] : []) await ctx.record(METRIC, { ...base, timestamp: m.timestamp ?? null, name: m.name ?? null, type: m.type ?? null, value: m.value ?? null, unit: m.unit ?? null, attributes: obj(m.attributes) }, id(METRIC)); break;
|
|
178
|
+
// an item of a type the twin does not keep otherwise is skipped and retained as it came
|
|
179
|
+
// source: https://raw.githubusercontent.com/getsentry/sentry-docs/3ec4e139711af82b38a3c2c17261f5f5cdb66dfc/develop-docs/sdk/foundations/envelopes/index.mdx "Implementations **must gracefully skip and retain** Items of unknown type"
|
|
180
|
+
default: {
|
|
181
|
+
const blob = `envelope-item/${String(project.id)}/${envelopeN}/${n}`;
|
|
182
|
+
await ctx.blobs.put(blob, item.payload);
|
|
183
|
+
await ctx.record(OTHER_ITEM, { ...base, type, header: item.header, size: item.payload.length, blob }, id(OTHER_ITEM));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return Response.json(envelopeId ? { id: envelopeId } : {});
|
|
188
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// What Sentry's ingest does around every request (architecture, around.ts): the body is kept as its bytes, which an
|
|
2
|
+
// envelope is (a compressed one is not text), before the dispatch reads it. Its CORS is the manifest's (`cors`).
|
|
3
|
+
import { s3 } from '@volter/world-core';
|
|
4
|
+
export async function around(ctx, next) {
|
|
5
|
+
return next(await s3.withBytes(ctx.call.request));
|
|
6
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// What Sentry's ingest does around every request (architecture, around.ts): the body is kept as its bytes, which an
|
|
2
|
+
// envelope is (a compressed one is not text), before the dispatch reads it. Its CORS is the manifest's (`cors`).
|
|
3
|
+
import { s3, type HandlerContext } from '@volter/world-core';
|
|
4
|
+
|
|
5
|
+
export async function around(ctx: HandlerContext, next: (request?: Request) => Promise<Response>): Promise<Response> {
|
|
6
|
+
return next(await s3.withBytes(ctx.call.request));
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// GENERATED by scripts/create-pack.ts --index: the handler map, by operationId, from the families' files (each
|
|
2
|
+
// handler named as its operation). Do not edit; add a handler to its family's file and generate again.
|
|
3
|
+
import type { Handler } from '@volter/world-core';
|
|
4
|
+
import * as api from './api.ts';
|
|
5
|
+
|
|
6
|
+
export const ingestSemantics: Record<string, Handler> = {
|
|
7
|
+
sendEnvelope: api.sendEnvelope,
|
|
8
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// What every family of ingest shares: its stored shapes, who is calling, and the vendor's answers.
|
|
2
|
+
import type { HandlerContext } from '@volter/world-core';
|
|
3
|
+
|
|
4
|
+
export * from '../../../src/semantics/shared.ts';
|
|
5
|
+
export type { HandlerContext };
|
|
6
|
+
|
|
7
|
+
export { manifest as apiManifest } from '../../../api/src/manifest.ts';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const states: {};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
{
|
|
2
|
+
"vendor": "sentry",
|
|
3
|
+
"missing": 11,
|
|
4
|
+
"stale": [],
|
|
5
|
+
"code": {
|
|
6
|
+
"lines": 919,
|
|
7
|
+
"run": 900,
|
|
8
|
+
"recorded": 8,
|
|
9
|
+
"missing": 11
|
|
10
|
+
},
|
|
11
|
+
"state": {
|
|
12
|
+
"declared": 2,
|
|
13
|
+
"reached": 2,
|
|
14
|
+
"recorded": 0,
|
|
15
|
+
"missing": [],
|
|
16
|
+
"undeclared": []
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
{
|
|
20
|
+
"file": "semantics/shared.ts",
|
|
21
|
+
"lines": {
|
|
22
|
+
"found": 279,
|
|
23
|
+
"hit": 277
|
|
24
|
+
},
|
|
25
|
+
"missing": []
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"file": "manifest.ts",
|
|
29
|
+
"lines": {
|
|
30
|
+
"found": 33,
|
|
31
|
+
"hit": 33
|
|
32
|
+
},
|
|
33
|
+
"missing": []
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"file": "index.ts",
|
|
37
|
+
"lines": {
|
|
38
|
+
"found": 10,
|
|
39
|
+
"hit": 10
|
|
40
|
+
},
|
|
41
|
+
"missing": []
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"file": "server.ts",
|
|
45
|
+
"lines": {
|
|
46
|
+
"found": 4,
|
|
47
|
+
"hit": 2
|
|
48
|
+
},
|
|
49
|
+
"missing": []
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"file": "fetch.ts",
|
|
53
|
+
"lines": {
|
|
54
|
+
"found": 6,
|
|
55
|
+
"hit": 6
|
|
56
|
+
},
|
|
57
|
+
"missing": []
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"file": "../api/src/semantics/doors.ts",
|
|
61
|
+
"lines": {
|
|
62
|
+
"found": 27,
|
|
63
|
+
"hit": 23
|
|
64
|
+
},
|
|
65
|
+
"missing": []
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"file": "../api/src/semantics/states.ts",
|
|
69
|
+
"lines": {
|
|
70
|
+
"found": 9,
|
|
71
|
+
"hit": 9
|
|
72
|
+
},
|
|
73
|
+
"missing": []
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"file": "../api/src/semantics/api.ts",
|
|
77
|
+
"lines": {
|
|
78
|
+
"found": 164,
|
|
79
|
+
"hit": 157
|
|
80
|
+
},
|
|
81
|
+
"missing": [
|
|
82
|
+
{
|
|
83
|
+
"from": 38,
|
|
84
|
+
"to": 41,
|
|
85
|
+
"fn": "makeRelease"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"from": 222,
|
|
89
|
+
"to": 224,
|
|
90
|
+
"fn": "listOrganizationReleases"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"file": "../api/src/semantics/shared.ts",
|
|
96
|
+
"lines": {
|
|
97
|
+
"found": 17,
|
|
98
|
+
"hit": 17
|
|
99
|
+
},
|
|
100
|
+
"missing": []
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"file": "../api/src/semantics/around.ts",
|
|
104
|
+
"lines": {
|
|
105
|
+
"found": 27,
|
|
106
|
+
"hit": 23
|
|
107
|
+
},
|
|
108
|
+
"missing": [
|
|
109
|
+
{
|
|
110
|
+
"from": 27,
|
|
111
|
+
"to": 30,
|
|
112
|
+
"fn": "around"
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"file": "../api/src/semantics/index.ts",
|
|
118
|
+
"lines": {
|
|
119
|
+
"found": 15,
|
|
120
|
+
"hit": 15
|
|
121
|
+
},
|
|
122
|
+
"missing": []
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"file": "../api/src/manifest.ts",
|
|
126
|
+
"lines": {
|
|
127
|
+
"found": 112,
|
|
128
|
+
"hit": 112
|
|
129
|
+
},
|
|
130
|
+
"missing": []
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"file": "../api/src/index.ts",
|
|
134
|
+
"lines": {
|
|
135
|
+
"found": 2,
|
|
136
|
+
"hit": 2
|
|
137
|
+
},
|
|
138
|
+
"missing": []
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"file": "../api/src/fetch.ts",
|
|
142
|
+
"lines": {
|
|
143
|
+
"found": 14,
|
|
144
|
+
"hit": 14
|
|
145
|
+
},
|
|
146
|
+
"missing": []
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"file": "../ingest/src/semantics/api.ts",
|
|
150
|
+
"lines": {
|
|
151
|
+
"found": 153,
|
|
152
|
+
"hit": 153
|
|
153
|
+
},
|
|
154
|
+
"missing": []
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"file": "../ingest/src/semantics/shared.ts",
|
|
158
|
+
"lines": {
|
|
159
|
+
"found": 2,
|
|
160
|
+
"hit": 2
|
|
161
|
+
},
|
|
162
|
+
"missing": []
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"file": "../ingest/src/semantics/around.ts",
|
|
166
|
+
"lines": {
|
|
167
|
+
"found": 3,
|
|
168
|
+
"hit": 3
|
|
169
|
+
},
|
|
170
|
+
"missing": []
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"file": "../ingest/src/semantics/index.ts",
|
|
174
|
+
"lines": {
|
|
175
|
+
"found": 4,
|
|
176
|
+
"hit": 4
|
|
177
|
+
},
|
|
178
|
+
"missing": []
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"file": "../ingest/src/manifest.ts",
|
|
182
|
+
"lines": {
|
|
183
|
+
"found": 24,
|
|
184
|
+
"hit": 24
|
|
185
|
+
},
|
|
186
|
+
"missing": []
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"file": "../ingest/src/index.ts",
|
|
190
|
+
"lines": {
|
|
191
|
+
"found": 2,
|
|
192
|
+
"hit": 2
|
|
193
|
+
},
|
|
194
|
+
"missing": []
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"file": "../ingest/src/fetch.ts",
|
|
198
|
+
"lines": {
|
|
199
|
+
"found": 12,
|
|
200
|
+
"hit": 12
|
|
201
|
+
},
|
|
202
|
+
"missing": []
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|