/// // FIXTURE: labeled SQLite message aggregates expanded into reactive bubbles. import { action, cfSqlite, type Confidential, handler, lift, NAME, pattern, type ReadonlyCell, sqliteDatabase, type SqliteDb, UI, Writable, } from "commonfabric"; interface ThreadRow { id: number; packed: string; } interface ParseRow extends ThreadRow { packed: Confidential; } interface Bubble { text: string; me: boolean; } const seed = handler<{ count: number }, { db: SqliteDb }>( ({ count }, { db }) => { for (let index = 0; index < count; index++) { db.exec( "INSERT INTO messages (id, thread_id, payload) VALUES (?, ?, ?)", [ index, 1, JSON.stringify({ text: `Message ${index}`, me: index % 2 === 0 }), ], ); } }, ); const selectThread = lift< { rows: ReadonlyCell[]; selected: boolean }, ReadonlyCell[] >(({ rows, selected }) => selected && rows?.length ? [rows[0]] : []); const parseThread = lift<{ rows: ParseRow[] }, Bubble[]>( ({ rows }) => rows.length ? JSON.parse(rows[0].packed) : [], ); export default pattern(() => { const { table } = cfSqlite; const db = sqliteDatabase({ tables: { messages: table({ id: "integer primary key", thread_id: "integer", payload: { type: "string", ifc: { confidentiality: ["cfc-prepare-messages"] }, }, }), }, }); // Concatenation keeps the JSON aggregate textual across the SQLite driver. const thread = db.query( "SELECT thread_id AS id, (json_group_array(json(payload) ORDER BY id) || '') AS packed FROM messages GROUP BY thread_id", { reactOn: db }, ); const selected = new Writable.perSession(false); const rows = thread.result ?? []; const selection = selectThread({ rows, selected }); const bubbles = parseThread({ rows: selection }); const elements = bubbles.map((message) => (
{message.text}
)); const open = action(() => selected.set(true)); return { [NAME]: "CFC preparation ladder", [UI]: (
Open thread
{elements}
), db, thread, selected, selection, bubbles, elements, open, seed: seed({ db }), }; });