@volter/twin-sentry 0.1.37 → 1.0.1

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 (189) hide show
  1. package/README.md +28 -140
  2. package/api/spec/SOURCE.md +32 -0
  3. package/api/spec/doc-examples.json +12 -0
  4. package/api/spec/openapi.json.gz +0 -0
  5. package/api/spec/patches.json +5350 -0
  6. package/api/spec/sources.json +122 -0
  7. package/api/src/fetch.ts +18 -0
  8. package/api/src/generated/surface.gen.json +1 -0
  9. package/api/src/generated/ui.gen.json +1 -0
  10. package/api/src/index.ts +3 -0
  11. package/api/src/manifest.ts +137 -0
  12. package/api/src/semantics/api.ts +247 -0
  13. package/api/src/semantics/around.ts +36 -0
  14. package/api/src/semantics/doors.ts +76 -0
  15. package/api/src/semantics/index.ts +19 -0
  16. package/api/src/semantics/shared.ts +31 -0
  17. package/api/src/semantics/states.ts +17 -0
  18. package/dist/api/spec/SOURCE.md +32 -0
  19. package/dist/api/spec/doc-examples.json +12 -0
  20. package/dist/api/spec/openapi.json.gz +0 -0
  21. package/dist/api/spec/patches.json +5350 -0
  22. package/dist/api/spec/sources.json +122 -0
  23. package/dist/api/src/fetch.d.ts +2 -0
  24. package/dist/api/src/fetch.js +17 -0
  25. package/dist/api/src/fetch.ts +18 -0
  26. package/dist/api/src/generated/surface.gen.json +1 -0
  27. package/dist/api/src/generated/ui.gen.json +1 -0
  28. package/dist/api/src/index.d.ts +2 -0
  29. package/dist/api/src/index.js +3 -0
  30. package/dist/api/src/index.ts +3 -0
  31. package/dist/api/src/manifest.d.ts +2 -0
  32. package/dist/api/src/manifest.js +136 -0
  33. package/dist/api/src/manifest.ts +137 -0
  34. package/dist/api/src/semantics/api.d.ts +13 -0
  35. package/dist/api/src/semantics/api.js +269 -0
  36. package/dist/api/src/semantics/api.ts +247 -0
  37. package/dist/api/src/semantics/around.d.ts +2 -0
  38. package/dist/api/src/semantics/around.js +41 -0
  39. package/dist/api/src/semantics/around.ts +36 -0
  40. package/dist/api/src/semantics/doors.d.ts +5 -0
  41. package/dist/api/src/semantics/doors.js +71 -0
  42. package/dist/api/src/semantics/doors.ts +76 -0
  43. package/dist/api/src/semantics/index.d.ts +2 -0
  44. package/dist/api/src/semantics/index.js +15 -0
  45. package/dist/api/src/semantics/index.ts +19 -0
  46. package/dist/api/src/semantics/shared.d.ts +14 -0
  47. package/dist/api/src/semantics/shared.js +26 -0
  48. package/dist/api/src/semantics/shared.ts +31 -0
  49. package/dist/api/src/semantics/states.d.ts +24 -0
  50. package/dist/api/src/semantics/states.js +17 -0
  51. package/dist/api/src/semantics/states.ts +17 -0
  52. package/dist/ingest/spec/SOURCE.md +30 -0
  53. package/dist/ingest/spec/authentication.mdx.gz +0 -0
  54. package/dist/ingest/spec/compression.mdx.gz +0 -0
  55. package/dist/ingest/spec/doc-examples.json +216 -0
  56. package/dist/ingest/spec/envelope-items.mdx.gz +0 -0
  57. package/dist/ingest/spec/envelopes.mdx.gz +0 -0
  58. package/dist/ingest/spec/openapi.json +178 -0
  59. package/dist/ingest/spec/rate-limiting.mdx.gz +0 -0
  60. package/dist/ingest/spec/sources.json +25 -0
  61. package/dist/ingest/spec/transport.mdx.gz +0 -0
  62. package/dist/ingest/spec/write-spec.ts +192 -0
  63. package/dist/ingest/src/fetch.d.ts +2 -0
  64. package/dist/ingest/src/fetch.js +15 -0
  65. package/dist/ingest/src/fetch.ts +16 -0
  66. package/dist/ingest/src/generated/surface.gen.json +1 -0
  67. package/dist/ingest/src/generated/ui.gen.json +1 -0
  68. package/dist/ingest/src/index.d.ts +2 -0
  69. package/dist/ingest/src/index.js +3 -0
  70. package/dist/ingest/src/index.ts +3 -0
  71. package/dist/ingest/src/manifest.d.ts +2 -0
  72. package/dist/ingest/src/manifest.js +37 -0
  73. package/dist/ingest/src/manifest.ts +42 -0
  74. package/dist/ingest/src/semantics/api.d.ts +2 -0
  75. package/dist/ingest/src/semantics/api.js +240 -0
  76. package/dist/ingest/src/semantics/api.ts +188 -0
  77. package/dist/ingest/src/semantics/around.d.ts +2 -0
  78. package/dist/ingest/src/semantics/around.js +6 -0
  79. package/dist/ingest/src/semantics/around.ts +7 -0
  80. package/dist/ingest/src/semantics/index.d.ts +2 -0
  81. package/dist/ingest/src/semantics/index.js +4 -0
  82. package/dist/ingest/src/semantics/index.ts +8 -0
  83. package/dist/ingest/src/semantics/shared.d.ts +4 -0
  84. package/dist/ingest/src/semantics/shared.js +2 -0
  85. package/dist/ingest/src/semantics/shared.ts +7 -0
  86. package/dist/ingest/src/semantics/states.d.ts +1 -0
  87. package/dist/ingest/src/semantics/states.js +2 -0
  88. package/dist/ingest/src/semantics/states.ts +2 -0
  89. package/dist/journeys/coverage.json +205 -0
  90. package/dist/journeys/customer-life.json +921 -0
  91. package/dist/journeys/customer-life.outline.md +51 -0
  92. package/dist/journeys/decisions.json +170 -0
  93. package/dist/journeys/demand.json +92 -0
  94. package/dist/journeys/first-use.json +22 -0
  95. package/dist/journeys/score.json +231 -0
  96. package/dist/journeys/unreachable.json +29 -0
  97. package/dist/journeys/vendor-examples.json +474 -0
  98. package/dist/spec/sources.json +76 -0
  99. package/dist/src/cli.js +12 -27
  100. package/dist/src/fetch.d.ts +2 -0
  101. package/dist/src/fetch.js +9 -0
  102. package/dist/src/index.d.ts +5 -15
  103. package/dist/src/index.js +13 -75
  104. package/dist/src/manifest.d.ts +2 -0
  105. package/dist/src/manifest.js +35 -0
  106. package/dist/src/semantics/shared.d.ts +88 -0
  107. package/dist/src/semantics/shared.js +407 -0
  108. package/dist/src/server.d.ts +7 -0
  109. package/dist/src/server.js +7 -0
  110. package/generated/pack-facts.json +101 -0
  111. package/ingest/spec/SOURCE.md +30 -0
  112. package/ingest/spec/authentication.mdx.gz +0 -0
  113. package/ingest/spec/compression.mdx.gz +0 -0
  114. package/ingest/spec/doc-examples.json +216 -0
  115. package/ingest/spec/envelope-items.mdx.gz +0 -0
  116. package/ingest/spec/envelopes.mdx.gz +0 -0
  117. package/ingest/spec/openapi.json +178 -0
  118. package/ingest/spec/rate-limiting.mdx.gz +0 -0
  119. package/ingest/spec/sources.json +25 -0
  120. package/ingest/spec/transport.mdx.gz +0 -0
  121. package/ingest/spec/write-spec.ts +192 -0
  122. package/ingest/src/fetch.ts +16 -0
  123. package/ingest/src/generated/surface.gen.json +1 -0
  124. package/ingest/src/generated/ui.gen.json +1 -0
  125. package/ingest/src/index.ts +3 -0
  126. package/ingest/src/manifest.ts +42 -0
  127. package/ingest/src/semantics/api.ts +188 -0
  128. package/ingest/src/semantics/around.ts +7 -0
  129. package/ingest/src/semantics/index.ts +8 -0
  130. package/ingest/src/semantics/shared.ts +7 -0
  131. package/ingest/src/semantics/states.ts +2 -0
  132. package/journeys/coverage.json +205 -0
  133. package/journeys/customer-life.json +921 -0
  134. package/journeys/customer-life.outline.md +51 -0
  135. package/journeys/decisions.json +170 -0
  136. package/journeys/demand.json +92 -0
  137. package/journeys/first-use.json +22 -0
  138. package/journeys/score.json +231 -0
  139. package/journeys/unreachable.json +29 -0
  140. package/journeys/vendor-examples.json +474 -0
  141. package/package.json +22 -47
  142. package/spec/sources.json +76 -0
  143. package/src/cli.ts +12 -24
  144. package/src/fetch.ts +10 -0
  145. package/src/index.ts +13 -143
  146. package/src/manifest.ts +41 -0
  147. package/src/semantics/shared.ts +439 -0
  148. package/src/server.ts +8 -0
  149. package/client/sentry-mirror.css +0 -99
  150. package/client/sentry-mirror.tsx +0 -352
  151. package/dist/client/sentry-mirror.bundle.js +0 -388
  152. package/dist/client/sentry-mirror.css +0 -99
  153. package/dist/client/sentry-mirror.d.ts +0 -17
  154. package/dist/client/sentry-mirror.js +0 -156
  155. package/dist/client/sentry-mirror.tsx +0 -352
  156. package/dist/src/sentry-budget.d.ts +0 -50
  157. package/dist/src/sentry-budget.js +0 -145
  158. package/dist/src/sentry-capabilities.d.ts +0 -3
  159. package/dist/src/sentry-capabilities.js +0 -1180
  160. package/dist/src/sentry-conformance.d.ts +0 -22
  161. package/dist/src/sentry-conformance.js +0 -97
  162. package/dist/src/sentry-connector.d.ts +0 -77
  163. package/dist/src/sentry-connector.js +0 -226
  164. package/dist/src/sentry-events.d.ts +0 -54
  165. package/dist/src/sentry-events.js +0 -132
  166. package/dist/src/sentry-ingest.d.ts +0 -137
  167. package/dist/src/sentry-ingest.js +0 -387
  168. package/dist/src/sentry-mirror-ui.d.ts +0 -38
  169. package/dist/src/sentry-mirror-ui.js +0 -162
  170. package/dist/src/sentry-perform-harness.d.ts +0 -9
  171. package/dist/src/sentry-perform-harness.js +0 -20
  172. package/dist/src/sentry-server.d.ts +0 -14
  173. package/dist/src/sentry-server.js +0 -27
  174. package/dist/src/sentry-twin.d.ts +0 -17
  175. package/dist/src/sentry-twin.js +0 -1739
  176. package/dist/test-fixtures/sentry-openapi-operations.SOURCE.md +0 -24
  177. package/dist/test-fixtures/sentry-openapi-operations.json +0 -1837
  178. package/src/sentry-budget.ts +0 -171
  179. package/src/sentry-capabilities.ts +0 -1222
  180. package/src/sentry-conformance.ts +0 -110
  181. package/src/sentry-connector.ts +0 -260
  182. package/src/sentry-events.ts +0 -172
  183. package/src/sentry-ingest.ts +0 -471
  184. package/src/sentry-mirror-ui.ts +0 -162
  185. package/src/sentry-perform-harness.ts +0 -19
  186. package/src/sentry-server.ts +0 -35
  187. package/src/sentry-twin.ts +0 -1615
  188. package/test-fixtures/sentry-openapi-operations.SOURCE.md +0 -24
  189. 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,2 @@
1
+ import { type HandlerContext } from '@volter/world-core';
2
+ export declare function around(ctx: HandlerContext, next: (request?: Request) => Promise<Response>): Promise<Response>;
@@ -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,2 @@
1
+ import type { Handler } from '@volter/world-core';
2
+ export declare const ingestSemantics: Record<string, Handler>;
@@ -0,0 +1,4 @@
1
+ import * as api from "./api.js";
2
+ export const ingestSemantics = {
3
+ sendEnvelope: api.sendEnvelope,
4
+ };
@@ -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,4 @@
1
+ import type { HandlerContext } from '@volter/world-core';
2
+ export * from '../../../src/semantics/shared.js';
3
+ export type { HandlerContext };
4
+ export { manifest as apiManifest } from '../../../api/src/manifest.js';
@@ -0,0 +1,2 @@
1
+ export * from "../../../src/semantics/shared.js";
2
+ export { manifest as apiManifest } from "../../../api/src/manifest.js";
@@ -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,2 @@
1
+ // ingest's state machines and state rulings, each transition cited (architecture, "State").
2
+ export const states = {};
@@ -0,0 +1,2 @@
1
+ // ingest's state machines and state rulings, each transition cited (architecture, "State").
2
+ export 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
+ }