summaryrefslogtreecommitdiffhomepage
path: root/src/features/surface-host/logic/table.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/surface-host/logic/table.ts')
-rw-r--r--src/features/surface-host/logic/table.ts54
1 files changed, 54 insertions, 0 deletions
diff --git a/src/features/surface-host/logic/table.ts b/src/features/surface-host/logic/table.ts
new file mode 100644
index 0000000..027553c
--- /dev/null
+++ b/src/features/surface-host/logic/table.ts
@@ -0,0 +1,54 @@
+/**
+ * Pure parser for the `rendererId: "table"` custom-field payload.
+ *
+ * This is the FRONTEND-side renderer contract for tabular custom fields: a
+ * backend that wants a table emits a `custom` field with `rendererId: "table"`
+ * and a payload of `{ columns: string[]; rows: (string|number)[][] }`. Cells are
+ * coerced to strings. Anything that does not match the shape returns `null`, so
+ * the renderer gracefully skips it (never throws on hostile/partial data).
+ */
+
+export interface TableData {
+ readonly columns: readonly string[];
+ readonly rows: readonly (readonly string[])[];
+}
+
+function isStringArray(v: unknown): v is unknown[] {
+ return Array.isArray(v);
+}
+
+function coerceCell(v: unknown): string | null {
+ if (typeof v === "string") return v;
+ if (typeof v === "number" && Number.isFinite(v)) return String(v);
+ if (typeof v === "boolean") return String(v);
+ return null;
+}
+
+export function parseTablePayload(payload: unknown): TableData | null {
+ if (typeof payload !== "object" || payload === null) return null;
+ const obj = payload as Record<string, unknown>;
+
+ const rawColumns = obj.columns;
+ const rawRows = obj.rows;
+ if (!isStringArray(rawColumns) || !isStringArray(rawRows)) return null;
+
+ const columns: string[] = [];
+ for (const col of rawColumns) {
+ if (typeof col !== "string") return null;
+ columns.push(col);
+ }
+
+ const rows: string[][] = [];
+ for (const row of rawRows) {
+ if (!Array.isArray(row)) return null;
+ const cells: string[] = [];
+ for (const cell of row) {
+ const c = coerceCell(cell);
+ if (c === null) return null;
+ cells.push(c);
+ }
+ rows.push(cells);
+ }
+
+ return { columns, rows };
+}