From e6f5c7b6dd08fac1dbd46f77b749d73e6598fa68 Mon Sep 17 00:00:00 2001 From: ajnart Date: Sun, 30 Aug 2026 21:56:18 +0200 Subject: [PATCH] fix(custom-widgets): split preview evidence module --- .../src/server/preview-session-evidence.ts | 39 +++++++++++++ .../src/server/preview-sessions.ts | 58 ++++++------------- 2 files changed, 57 insertions(+), 40 deletions(-) create mode 100644 packages/custom-widgets/src/server/preview-session-evidence.ts diff --git a/packages/custom-widgets/src/server/preview-session-evidence.ts b/packages/custom-widgets/src/server/preview-session-evidence.ts new file mode 100644 index 000000000..99d18c6f8 --- /dev/null +++ b/packages/custom-widgets/src/server/preview-session-evidence.ts @@ -0,0 +1,39 @@ +import { z } from "zod/v4"; + +export const previewJournalEntrySchema = z.object({ + id: z.string(), + requestId: z.string(), + kind: z.enum(["query", "action"]), + method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]), + path: z.string(), + status: z.number().nullable(), + durationMs: z.number().nonnegative(), + simulated: z.boolean(), + sessionRevision: z.number().int().nonnegative(), + timestamp: z.number(), +}); + +export type CustomWidgetPreviewJournalEntry = z.infer; + +export const isVerifiedPreviewEvidence = (entry: CustomWidgetPreviewJournalEntry) => { + if (entry.kind === "query") return entry.status !== null && entry.status >= 200 && entry.status < 300; + return entry.simulated || (entry.status !== null && entry.status >= 200 && entry.status < 300); +}; + +export const getPreviewEvidenceKey = (entry: CustomWidgetPreviewJournalEntry) => + `${entry.sessionRevision}:${entry.kind}:${entry.requestId}`; + +export const parsePreviewJournalEntries = (entries: readonly unknown[]) => + entries.flatMap((entry) => { + const parsed = previewJournalEntrySchema.safeParse(parsePreviewStoredValue(entry)); + return parsed.success ? [parsed.data] : []; + }); + +export const parsePreviewStoredValue = (value: unknown): unknown => { + if (typeof value !== "string") return value; + try { + return JSON.parse(value) as unknown; + } catch { + return null; + } +}; diff --git a/packages/custom-widgets/src/server/preview-sessions.ts b/packages/custom-widgets/src/server/preview-sessions.ts index 929bf3b54..47a4c6536 100644 --- a/packages/custom-widgets/src/server/preview-sessions.ts +++ b/packages/custom-widgets/src/server/preview-sessions.ts @@ -9,6 +9,14 @@ import { } from "../core"; import type { CustomJsxRequest, CustomWidgetOptions, CustomWidgetSource } from "../core"; import { CustomWidgetDomainError } from "./errors"; +import { + getPreviewEvidenceKey, + isVerifiedPreviewEvidence, + parsePreviewJournalEntries, + parsePreviewStoredValue, + previewJournalEntrySchema, +} from "./preview-session-evidence"; +import type { CustomWidgetPreviewJournalEntry } from "./preview-session-evidence"; import { validatePreviewTemplateRevision } from "./preview-session-template"; const SESSION_TTL_MS = 10 * 60_000; @@ -40,19 +48,7 @@ const sessionSchema = z.object({ }); export type CustomWidgetPreviewSession = z.infer; -const journalEntrySchema = z.object({ - id: z.string(), - requestId: z.string(), - kind: z.enum(["query", "action"]), - method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]), - path: z.string(), - status: z.number().nullable(), - durationMs: z.number().nonnegative(), - simulated: z.boolean(), - sessionRevision: z.number().int().nonnegative(), - timestamp: z.number(), -}); -export type CustomWidgetPreviewJournalEntry = z.infer; +export type { CustomWidgetPreviewJournalEntry } from "./preview-session-evidence"; export interface CreatePreviewSessionInput { userId: string; @@ -128,7 +124,7 @@ export class CustomWidgetPreviewSessionService { public async get(id: string, userId: string): Promise { const candidate = this.options.store ? await this.options.store.getSession(id) : this.sessions.get(id); - const parsed = sessionSchema.safeParse(parseStoredValue(candidate)); + const parsed = sessionSchema.safeParse(parsePreviewStoredValue(candidate)); if (!parsed.success || parsed.data.expiresAt <= this.now()) { if (this.options.store) await this.options.store.deleteSession(id); else { @@ -169,24 +165,21 @@ export class CustomWidgetPreviewSessionService { session: CustomWidgetPreviewSession, input: Omit, ): Promise { - const value = journalEntrySchema.parse({ ...input, id: this.options.createId(), timestamp: this.now() }); + const value = previewJournalEntrySchema.parse({ ...input, id: this.options.createId(), timestamp: this.now() }); const ttlMs = Math.max(1, session.expiresAt - this.now()); - const isVerified = - (value.kind === "query" && value.status !== null && value.status >= 200 && value.status < 300) || - (value.kind === "action" && - (value.simulated || (value.status !== null && value.status >= 200 && value.status < 300))); - const evidenceKey = `${value.sessionRevision}:${value.kind}:${value.requestId}`; if (this.options.store) { const writes = [this.options.store.appendJournal(session.id, value, MAX_JOURNAL_ENTRIES, ttlMs)]; - if (isVerified) writes.push(this.options.store.saveEvidence(session.id, evidenceKey, value, ttlMs)); + if (isVerifiedPreviewEvidence(value)) { + writes.push(this.options.store.saveEvidence(session.id, getPreviewEvidenceKey(value), value, ttlMs)); + } await Promise.all(writes); return; } this.journals.set(session.id, [value, ...(this.journals.get(session.id) ?? [])].slice(0, MAX_JOURNAL_ENTRIES)); - if (!isVerified) return; + if (!isVerifiedPreviewEvidence(value)) return; const evidence = this.evidence.get(session.id) ?? new Map(); - evidence.set(evidenceKey, value); + evidence.set(getPreviewEvidenceKey(value), value); this.evidence.set(session.id, evidence); } @@ -195,10 +188,7 @@ export class CustomWidgetPreviewSessionService { const entries = this.options.store ? await this.options.store.getJournal(id, MAX_JOURNAL_ENTRIES) : (this.journals.get(id) ?? []); - return entries.flatMap((entry) => { - const parsed = journalEntrySchema.safeParse(parseStoredValue(entry)); - return parsed.success ? [parsed.data] : []; - }); + return parsePreviewJournalEntries(entries); } public async getEvidence(id: string, userId: string): Promise { @@ -206,10 +196,7 @@ export class CustomWidgetPreviewSessionService { const entries = this.options.store ? await this.options.store.getEvidence(id) : [...(this.evidence.get(id)?.values() ?? [])]; - return entries.flatMap((entry) => { - const parsed = journalEntrySchema.safeParse(parseStoredValue(entry)); - return parsed.success ? [parsed.data] : []; - }); + return parsePreviewJournalEntries(entries); } public getSecrets(session: CustomWidgetPreviewSession, sourceId: string) { @@ -311,12 +298,3 @@ export class CustomWidgetPreviewSessionService { else this.sessions.set(session.id, session); } } - -function parseStoredValue(value: unknown): unknown { - if (typeof value !== "string") return value; - try { - return JSON.parse(value) as unknown; - } catch { - return null; - } -}