Spaces:
Running
Running
File size: 9,763 Bytes
aa89cb5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | import { BrowserSQLiteTeaQLClient, type BrowserSQLiteStorage } from 'teaql-ts/sql/browser-sqlite';
import { DelegatingMutationPolicyApprovalProvider, DelegatingMutationPolicyRegistry, UserContext } from 'teaql-ts';
import { SQLExecutionEvidenceStore, type SQLExecutionMetadata } from 'teaql-ts/sql/core';
import { Q } from '../orders-lib/src/generated/Q';
import { E } from '../orders-lib/src/generated/E';
import { OrderStatus } from '../orders-lib/src/generated/models/OrderStatus';
import { GENERATED_RUNTIME_MODULE } from '../orders-lib/src/runtime-module';
import { ENTITY_SCHEMAS } from '../orders-lib/src/teaql-schemas';
import './style.css';
const status = document.querySelector<HTMLParagraphElement>('#status')!;
const output = document.querySelector<HTMLPreElement>('#result')!;
const trace = document.querySelector<HTMLPreElement>('#trace')!;
const sql = document.querySelector<HTMLPreElement>('#sql')!;
const evidenceRows = document.querySelector<HTMLTableSectionElement>('#evidence-rows')!;
const persist = document.querySelector<HTMLInputElement>('#persist')!;
const logging = document.querySelector<HTMLInputElement>('#logging')!;
const reset = document.querySelector<HTMLButtonElement>('#reset')!;
const run = document.querySelector<HTMLButtonElement>('#query')!;
const search = document.querySelector<HTMLTextAreaElement>('#search')!;
const preset = {filter: {orderNumber: {$contains: 'KEEP'}}};
type Runtime = {client: BrowserSQLiteTeaQLClient; context: UserContext; evidence: SQLExecutionEvidenceStore; diagnostics: SQLExecutionMetadata[]};
let active: Runtime | undefined;
persist.checked = localStorage.getItem('teaql-browser-order-storage') === 'opfs';
async function open(storage: BrowserSQLiteStorage): Promise<Runtime> {
const worker = new Worker(new URL('./sqlite.worker.ts', import.meta.url), {type: 'module'});
const client = await BrowserSQLiteTeaQLClient.open(worker, {...ENTITY_SCHEMAS}, {
storage, databaseName: 'teaql-browser-orders.sqlite3',
});
client.install(GENERATED_RUNTIME_MODULE);
client.setDiagnosticSQLLogSink(undefined).setMutationLoggingEnabled(false);
const identity = Object.freeze({policyId: 'browser-order-seed', version: '1', fingerprint: 'sha256:browser-order-seed-v1'});
const context = new UserContext().insertResource('dataService', client)
.withMutationPolicyRegistry(new DelegatingMutationPolicyRegistry(() => ({identity,
review: (_context, plan) => plan.operations.length > 0 ? {verdict: 'allow'}
: {verdict: 'deny', code: 'EMPTY_GRAPH', message: 'empty mutation graph'},
})))
.withMutationPolicyApprovalProvider(new DelegatingMutationPolicyApprovalProvider(
policy => ({policy, approvedBy: 'browser-demo-owner', approvedAt: new Date()}),
));
client.setUserContext(context);
await context.ensureSchema();
await context.ensureSchema();
return {client, context, evidence: new SQLExecutionEvidenceStore(), diagnostics: []};
}
async function seed({context}: Runtime): Promise<void> {
const existing = await Q.customerOrdersWithMinimalFields().limit(1)
.comment('detect existing order demo').purpose('keep mutation seeding idempotent').executeForList(context);
if (existing.length) return;
const platform = (await Q.platforms().limit(2).comment('load bootstrap platform').purpose('prepare order demo').executeForList(context))[0];
const customer = Q.customers().comment('initialize demo customer').purpose('prepare order demo').newEntity(context);
customer.updateName('North Star Retail').updatePlatform(platform.id);
await customer.auditAs('seed browser order customer').save(context);
for (let index = 1; index <= 4; index++) {
const order = Q.customerOrders().comment('initialize demo order').purpose('prepare order demo').newEntity(context);
order.updateCustomer(customer.id).updateOrderNumber(index < 4 ? `KEEP-00${index}` : 'DROP-004')
.updateOrderDate(`2026-10-0${index}`).updateTotalAmount(index < 4 ? index * 100 : 900);
if (index === 1) order.updateStatusToNew(); else order.updateStatusToPaid();
await order.auditAs('seed browser order').save(context);
for (let item = 1; item <= 5; item++) {
const line = Q.orderItems().comment('initialize demo item').purpose('prepare item previews').newEntity(context);
line.updateCustomerOrder(order.id).updateProductName(`Box ${item}`).updateQuantity(item).updateUnitPrice(20);
await line.auditAs('seed browser order item').save(context);
}
}
}
async function show(runtime: Runtime): Promise<void> {
const searchParams: unknown = JSON.parse(search.value);
const request = Q.customerOrdersWithMinimalFields().filterByJson(searchParams);
runtime.evidence.enableAll(); runtime.diagnostics.length = 0;
runtime.client.setRuntimeTelemetrySink(runtime.evidence).setQueryLoggingEnabled(logging.checked)
.setDiagnosticSQLLogSink({write: entry => runtime.diagnostics.push(entry)});
const page = await request.countAs('totalOrders')
.facetByStatusAs('statusCounts', Q.orderStatusesWithMinimalFields().selectName().selectColor()
.countAs('orderCount').sumTotalAmountOfCustomerOrdersAs('orderTotalAmount', Q.customerOrdersWithMinimalFields())
.orderByDisplayOrderAscending().comment('load status facets'))
.selectOrderNumber().selectOrderDate()
.selectStatusWith(Q.orderStatusesWithMinimalFields().selectName().selectColor().comment('load status'))
.selectOrderItemListWith(Q.orderItemsWithMinimalFields().selectProductName().selectQuantity()
.orderByIdAscending().limit(3).comment('load item previews'))
.countOrderItemsAs('itemCount').orderByOrderDateDescending().orderByIdDescending()
.offset(1).limit(1).comment('load order list').purpose('render order list page').executeForList(runtime.context);
const response = {
data: page.data.map(row => {
const expression = E.customerOrder(row);
const previewSize = expression.orderItemList().size().eval();
if (previewSize == null) throw new Error('Selected item list unexpectedly has a null size');
return {id: row.id, version: row.version, orderNumber: expression.orderNumber().eval(), orderDate: expression.orderDate().eval(),
status: {name: expression.status().name().eval(), color: expression.status().color().eval()},
items: Array.from({length: previewSize}, (_, index) => ({
productName: expression.orderItemList().get(index).productName().eval(), quantity: expression.orderItemList().get(index).quantity().eval(),
})), itemCount: (row as unknown as Record<string, unknown>).itemCount};
}),
metadata: {totalCount: page.totalCount, pageOffset: 1, pageSize: 1, aggregations: page.data.aggregations, searchWarnings: request.dynamicSearchWarnings},
facets: {statusCounts: page.data.facets.statusCounts.map(value => ({
id: value.id, name: E.orderStatus(OrderStatus.fromRecord(value)).name().eval(),
color: E.orderStatus(OrderStatus.fromRecord(value)).color().eval(), orderCount: value.orderCount, orderTotalAmount: value.orderTotalAmount,
}))},
};
output.textContent = JSON.stringify(response, null, 2);
const statements = runtime.evidence.snapshot();
// Rendering only: ownership and comments come from the actual runtime metadata.
const rows = statements.map(entry => {
const chain = entry.traceChain ?? [];
const purposes = chain.filter(node => node.kind === 'purpose');
const businessTrace = purposes.length === 1
? `${purposes[0].detail}: ${chain.filter(node => node.kind === 'comment').map(node => node.detail).join(' → ')}`
: 'TraceChain unavailable';
const row = document.createElement('tr');
for (const value of [`${entry.elapsedMicros} µs`,
entry.tracePath.find(node => node.kind === 'request')?.name ?? '—', businessTrace, entry.debugSQL]) {
const cell = document.createElement('td');
cell.textContent = value;
row.append(cell);
}
return {row, businessTrace};
});
evidenceRows.replaceChildren(...rows.map(value => value.row));
trace.textContent = rows.map(value => value.businessTrace).join('\n');
sql.textContent = JSON.stringify(statements, null, 2);
status.textContent = `${runtime.client.storage!.toUpperCase()} · ${page.totalCount} matches · ${statements.length} SQL statements · ${runtime.diagnostics.length} diagnostics · policy installed · ready`;
}
async function replace(storage: BrowserSQLiteStorage): Promise<void> {
status.textContent = `Opening ${storage.toUpperCase()} SQLite/WASM…`;
const previous = active; active = undefined;
await previous?.client.close();
const next = await open(storage); active = next;
await seed(next); await show(next);
}
function fail(value: unknown): void {
status.textContent = value instanceof Error ? `${value.name}: ${value.message}` : String(value);
}
run.addEventListener('click', async () => {
if (!active) return;
run.disabled = true;
try { await show(active); } catch (error) { fail(error); } finally { run.disabled = false; }
});
document.querySelector('#preset')!.addEventListener('click', () => {
search.value = JSON.stringify(preset, null, 2); run.click();
});
logging.addEventListener('change', () => { if (active) show(active).catch(fail); });
persist.addEventListener('change', () => {
const storage = persist.checked ? 'opfs' : 'memory';
localStorage.setItem('teaql-browser-order-storage', storage); replace(storage).catch(fail);
});
reset.addEventListener('click', async () => {
if (!active) return;
reset.disabled = true;
try { await active.client.reset(active.context); await seed(active); await show(active); }
catch (error) { fail(error); } finally { reset.disabled = false; }
});
replace(persist.checked ? 'opfs' : 'memory').catch(fail);
|