@volter/twin-sentry 0.1.0 → 0.1.2
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/dist/client/sentry-mirror.bundle.js +243 -176
- package/dist/src/sentry-events.js +5 -4
- package/dist/src/sentry-ingest.js +4 -4
- package/dist/src/sentry-mirror-ui.js +3 -3
- package/package.json +3 -3
- package/src/sentry-events.ts +4 -3
- package/src/sentry-ingest.ts +4 -4
- package/src/sentry-mirror-ui.ts +3 -3
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Delivery is an INJECTED seam (the auth-boundary): tests pass a fake deliverer, live runs
|
|
12
12
|
// pass the HTTP deliverer. verify() in the manifest stays fully offline.
|
|
13
|
-
import { applyTwinWrite, projectResources, nodeBuiltin } from '@volter/world-core';
|
|
13
|
+
import { applyTwinWrite, projectResources, nodeBuiltin, deliveryTraceHeaders } from '@volter/world-core';
|
|
14
|
+
import { appDestination, appFetch } from '@volter/world-core/app-route';
|
|
14
15
|
import { worldEgressRefusal } from '@volter/world-core/network-policy';
|
|
15
16
|
const SERVICE = 'sentry';
|
|
16
17
|
function nodeCrypto() {
|
|
@@ -68,10 +69,10 @@ export function listAlertRules(projectId, root) {
|
|
|
68
69
|
}
|
|
69
70
|
// ── Webhook emission on issue lifecycle ──────────────────────────────────────────
|
|
70
71
|
const httpDelivery = async (url, payload, headers) => {
|
|
71
|
-
if (worldEgressRefusal(url) !== null)
|
|
72
|
-
return; // the World's egress rule: refused like an unreachable endpoint
|
|
72
|
+
if (!appDestination(url) && worldEgressRefusal(url) !== null)
|
|
73
|
+
return; // the World's egress rule, the application's own hostnames reaching it inside the World: refused like an unreachable endpoint
|
|
73
74
|
try {
|
|
74
|
-
await
|
|
75
|
+
await appFetch(url, { method: 'POST', headers: { 'content-type': 'application/json', ...headers, ...deliveryTraceHeaders() }, body: payload });
|
|
75
76
|
}
|
|
76
77
|
catch {
|
|
77
78
|
/* fire-and-forget, like Sentry */
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// query param, or embedded in the envelope header's `dsn`. The twin validates that a
|
|
15
15
|
// project with that public key exists (faithful 401 otherwise). The store/envelope path
|
|
16
16
|
// is unauthenticated by the SECRET key (that's the modern DSN model — public key only).
|
|
17
|
-
import { applyTwinWrite, projectResources } from '@volter/world-core';
|
|
17
|
+
import { applyTwinWrite, projectResources, worldNow } from '@volter/world-core';
|
|
18
18
|
const SERVICE = 'sentry';
|
|
19
19
|
// ── DSN public key extraction (auth) ─────────────────────────────────────────────
|
|
20
20
|
// The SDK sends the public key in one of three places. We accept all three, mirroring
|
|
@@ -192,7 +192,7 @@ function genEventId(provided) {
|
|
|
192
192
|
* Pure local writes via the kernel — no network. `occurredAt` keeps it deterministic.
|
|
193
193
|
*/
|
|
194
194
|
export async function ingestEvent(key, event, opts = {}) {
|
|
195
|
-
const occurredAt = opts.occurredAt ??
|
|
195
|
+
const occurredAt = opts.occurredAt ?? worldNow();
|
|
196
196
|
const eventTs = toIso(event.timestamp, occurredAt);
|
|
197
197
|
const eventId = genEventId(event.event_id);
|
|
198
198
|
const issueId = issueIdFor(key.projectId, event);
|
|
@@ -320,7 +320,7 @@ export function envelopeSessions(env) {
|
|
|
320
320
|
* lifecycle, the final status wins). This is fully reproducible offline — crash-free counts
|
|
321
321
|
* are exact tallies over the persisted sessions (NOT a fabricated metrics engine). */
|
|
322
322
|
export async function persistSessions(projectId, sessions, opts = {}) {
|
|
323
|
-
const occurredAt = opts.occurredAt ??
|
|
323
|
+
const occurredAt = opts.occurredAt ?? worldNow();
|
|
324
324
|
for (const s of sessions) {
|
|
325
325
|
if (!s.sid)
|
|
326
326
|
continue;
|
|
@@ -336,7 +336,7 @@ export async function persistSessions(projectId, sessions, opts = {}) {
|
|
|
336
336
|
}
|
|
337
337
|
/** Persist an event's attachments (metadata only) as `event_attachment` resources. */
|
|
338
338
|
export async function persistEventAttachments(eventId, projectId, attachments, opts = {}) {
|
|
339
|
-
const occurredAt = opts.occurredAt ??
|
|
339
|
+
const occurredAt = opts.occurredAt ?? worldNow();
|
|
340
340
|
let i = rows('event_attachment', opts.root).length;
|
|
341
341
|
for (const a of attachments) {
|
|
342
342
|
i += 1;
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
// The pure format/level helpers live here so they can be unit-tested AND shared by the
|
|
11
11
|
// React client (Bun tree-shakes the server-only exports out of the browser bundle).
|
|
12
12
|
import { readFile } from 'node:fs/promises';
|
|
13
|
-
import { bundleClient, fileResponse } from '@volter/world-core';
|
|
13
|
+
import { bundleClient, fileResponse, filePathOf } from '@volter/world-core';
|
|
14
14
|
import { serveHttp } from '@volter/world-core';
|
|
15
15
|
import { createSentryTwinFetch } from "./sentry-server.js";
|
|
16
16
|
import { DEFAULT_ORG, DEFAULT_PROJECT } from "./sentry-ingest.js";
|
|
17
|
-
const CLIENT_ENTRY = () => new URL('../client/sentry-mirror.tsx', import.meta.url)
|
|
18
|
-
const CLIENT_CSS = () => new URL('../client/sentry-mirror.css', import.meta.url)
|
|
17
|
+
const CLIENT_ENTRY = () => filePathOf(new URL('../client/sentry-mirror.tsx', import.meta.url)); // lazy: workerd rejects top-level relative URL from import.meta.url (bundled via pack index)
|
|
18
|
+
const CLIENT_CSS = () => filePathOf(new URL('../client/sentry-mirror.css', import.meta.url)); // lazy: workerd rejects top-level relative URL from import.meta.url (bundled via pack index)
|
|
19
19
|
/** Map a Sentry level to a UI tone class. */
|
|
20
20
|
export function levelTone(level) {
|
|
21
21
|
const l = String(level ?? '').toLowerCase();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/twin-sentry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Local Sentry twin — a faithful, stateful local Sentry error-monitoring API your real `@sentry/node` SDK talks to unmodified. Ingest captureException events into grouped issues, browse the Web API + dashboard mirror, simulate, and fork. Built on @volter/world-core.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
"react-dom": "^19.2.7"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@volter/world-core": "2.0.
|
|
60
|
+
"@volter/world-core": "2.0.2"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@volter/world-core": "2.0.
|
|
63
|
+
"@volter/world-core": "2.0.2",
|
|
64
64
|
"@volter/world-tooling": "0.1.0",
|
|
65
65
|
"@sentry/node": "^10.64.0",
|
|
66
66
|
"@types/bun": "^1.2.20",
|
package/src/sentry-events.ts
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Delivery is an INJECTED seam (the auth-boundary): tests pass a fake deliverer, live runs
|
|
12
12
|
// pass the HTTP deliverer. verify() in the manifest stays fully offline.
|
|
13
|
-
import { applyTwinWrite, projectResources, nodeBuiltin } from '@volter/world-core';
|
|
13
|
+
import { applyTwinWrite, projectResources, nodeBuiltin, deliveryTraceHeaders } from '@volter/world-core';
|
|
14
14
|
import type { TwinResource } from '@volter/world-core';
|
|
15
|
+
import { appDestination, appFetch } from '@volter/world-core/app-route';
|
|
15
16
|
import { worldEgressRefusal } from '@volter/world-core/network-policy';
|
|
16
17
|
|
|
17
18
|
const SERVICE = 'sentry';
|
|
@@ -106,9 +107,9 @@ export function listAlertRules(projectId: string | undefined, root?: string): Tw
|
|
|
106
107
|
|
|
107
108
|
// ── Webhook emission on issue lifecycle ──────────────────────────────────────────
|
|
108
109
|
const httpDelivery: SentryWebhookDelivery = async (url, payload, headers) => {
|
|
109
|
-
if (worldEgressRefusal(url) !== null) return; // the World's egress rule: refused like an unreachable endpoint
|
|
110
|
+
if (!appDestination(url) && worldEgressRefusal(url) !== null) return; // the World's egress rule, the application's own hostnames reaching it inside the World: refused like an unreachable endpoint
|
|
110
111
|
try {
|
|
111
|
-
await
|
|
112
|
+
await appFetch(url, { method: 'POST', headers: { 'content-type': 'application/json', ...headers, ...deliveryTraceHeaders() }, body: payload });
|
|
112
113
|
} catch {
|
|
113
114
|
/* fire-and-forget, like Sentry */
|
|
114
115
|
}
|
package/src/sentry-ingest.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// query param, or embedded in the envelope header's `dsn`. The twin validates that a
|
|
15
15
|
// project with that public key exists (faithful 401 otherwise). The store/envelope path
|
|
16
16
|
// is unauthenticated by the SECRET key (that's the modern DSN model — public key only).
|
|
17
|
-
import { applyTwinWrite, projectResources } from '@volter/world-core';
|
|
17
|
+
import { applyTwinWrite, projectResources, worldNow } from '@volter/world-core';
|
|
18
18
|
import type { TwinResource } from '@volter/world-core';
|
|
19
19
|
|
|
20
20
|
const SERVICE = 'sentry';
|
|
@@ -245,7 +245,7 @@ export async function ingestEvent(
|
|
|
245
245
|
event: SentryEventPayload,
|
|
246
246
|
opts: { root?: string; occurredAt?: string } = {},
|
|
247
247
|
): Promise<IngestOutcome> {
|
|
248
|
-
const occurredAt = opts.occurredAt ??
|
|
248
|
+
const occurredAt = opts.occurredAt ?? worldNow();
|
|
249
249
|
const eventTs = toIso(event.timestamp, occurredAt);
|
|
250
250
|
const eventId = genEventId(event.event_id);
|
|
251
251
|
const issueId = issueIdFor(key.projectId, event);
|
|
@@ -396,7 +396,7 @@ export async function persistSessions(
|
|
|
396
396
|
sessions: EnvelopeSession[],
|
|
397
397
|
opts: { root?: string; occurredAt?: string } = {},
|
|
398
398
|
): Promise<void> {
|
|
399
|
-
const occurredAt = opts.occurredAt ??
|
|
399
|
+
const occurredAt = opts.occurredAt ?? worldNow();
|
|
400
400
|
for (const s of sessions) {
|
|
401
401
|
if (!s.sid) continue;
|
|
402
402
|
await applyTwinWrite(SERVICE, {
|
|
@@ -417,7 +417,7 @@ export async function persistEventAttachments(
|
|
|
417
417
|
attachments: EnvelopeAttachment[],
|
|
418
418
|
opts: { root?: string; occurredAt?: string } = {},
|
|
419
419
|
): Promise<void> {
|
|
420
|
-
const occurredAt = opts.occurredAt ??
|
|
420
|
+
const occurredAt = opts.occurredAt ?? worldNow();
|
|
421
421
|
let i = rows('event_attachment', opts.root).length;
|
|
422
422
|
for (const a of attachments) {
|
|
423
423
|
i += 1;
|
package/src/sentry-mirror-ui.ts
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
// The pure format/level helpers live here so they can be unit-tested AND shared by the
|
|
11
11
|
// React client (Bun tree-shakes the server-only exports out of the browser bundle).
|
|
12
12
|
import { readFile } from 'node:fs/promises';
|
|
13
|
-
import { bundleClient, fileResponse } from '@volter/world-core';
|
|
13
|
+
import { bundleClient, fileResponse, filePathOf } from '@volter/world-core';
|
|
14
14
|
import { serveHttp } from '@volter/world-core';
|
|
15
15
|
import { createSentryTwinFetch } from './sentry-server.ts';
|
|
16
16
|
import { DEFAULT_ORG, DEFAULT_PROJECT } from './sentry-ingest.ts';
|
|
17
17
|
|
|
18
|
-
const CLIENT_ENTRY = () => new URL('../client/sentry-mirror.tsx', import.meta.url)
|
|
19
|
-
const CLIENT_CSS = () => new URL('../client/sentry-mirror.css', import.meta.url)
|
|
18
|
+
const CLIENT_ENTRY = () => filePathOf(new URL('../client/sentry-mirror.tsx', import.meta.url)); // lazy: workerd rejects top-level relative URL from import.meta.url (bundled via pack index)
|
|
19
|
+
const CLIENT_CSS = () => filePathOf(new URL('../client/sentry-mirror.css', import.meta.url)); // lazy: workerd rejects top-level relative URL from import.meta.url (bundled via pack index)
|
|
20
20
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
// Pure, dependency-free render/format helpers (plain data in → plain data out).
|