import { BrowserSQLiteStorage, BrowserSQLiteTeaQLClient, } from 'teaql-ts/sql/browser-sqlite'; import { SQLExecutionEvidenceStore, SQLExecutionMetadata, } from 'teaql-ts/sql/core'; import { DelegatingMutationPolicyApprovalProvider, DelegatingMutationPolicyRegistry, MutationIntent, RuntimeModule, UserContext, } from 'teaql-ts'; import { ORDER_SCHEMAS, ORDER_SEED, ORDER_STATUS, ORDER_STATUS_CONSTANTS } from './order-model'; import { Q } from './generated/Q'; import './style.css'; const status = document.querySelector('#status')!; const persist = document.querySelector('#persist')!; const reset = document.querySelector('#reset')!; const queryCatalog = document.querySelector('#query-catalog')!; const result = document.querySelector('#result')!; const resultTitle = document.querySelector('#result-title')!; const resultDescription = document.querySelector('#result-description')!; const resultMeta = document.querySelector('#result-meta')!; const queryCode = document.querySelector('#query-code')!; const queryParamsBlock = document.querySelector('#query-params-block')!; const queryParamsCode = document.querySelector('#query-params-code')!; const resultGrid = document.querySelector('#result-grid')!; const resultViewLabel = document.querySelector('#result-view-label')!; const sqlCount = document.querySelector('#sql-count')!; const sqlOutput = document.querySelector('#sql-output')!; const expandAll = document.querySelector('#expand-all')!; const collapseAll = document.querySelector('#collapse-all')!; const viewTable = document.querySelector('#view-table')!; const viewTree = document.querySelector('#view-tree')!; const languageButtons = Array.from(document.querySelectorAll('[data-locale]')); type Runtime = { client: BrowserSQLiteTeaQLClient; context: UserContext; evidence: SQLExecutionEvidenceStore; }; type QueryView = 'tree' | 'dashboard'; type ResultView = 'table' | 'tree'; type QueryExample = Readonly<{ id: string; title: string; description: string; code: string; view?: QueryView; run(active: Runtime): Promise; }>; type QueryGroup = Readonly<{ label: string; description: string; queries: readonly QueryExample[]; }>; type Locale = 'en' | 'zh'; type LocalizedCopy = Readonly<{ en: string; zh: string }>; const UI_COPY: Readonly> = { heroTitle: { en: 'Understand TeaQL queries through a familiar order model.', zh: '用一个熟悉的订单模型,看懂 TeaQL 查询。' }, heroIntro: { en: 'Platform, customer, order, item, payment, and shipment data all run in the browser. Order status is backed by a constant model. Choose a query below to inspect real SQLite/WASM results.', zh: '平台、用户、订单、明细、支付和物流数据都在浏览器中运行;订单状态由常数模型提供。选择下面的查询,查看真实的 SQLite/WASM 结果。' }, runtimeCoverage: { en: 'RUNTIME COVERAGE', zh: 'RUNTIME 覆盖' }, runtimeTitle: { en: 'This demo runs TypeScript in your browser.', zh: '这个演示在浏览器中运行 TypeScript。' }, runtimeIntro: { en: 'The same model-driven Q and TraceChain contract is supported by seven TeaQL runtimes. Select a language to inspect its runtime source.', zh: '同一套模型驱动的 Q 与 TraceChain 契约支持七种 TeaQL runtime。选择一种语言,可以查看对应的 runtime 源码。' }, currentBrowserRuntime: { en: 'Current browser runtime', zh: '当前浏览器 runtime' }, supportedRuntime: { en: 'Supported runtime', zh: '已支持 runtime' }, observabilityTitle: { en: 'TraceChain in OpenObserve', zh: 'OpenObserve 中的 TraceChain' }, observabilityIntro: { en: 'A captured Order query rendered as business lineage: page purpose, nested request comments, and the actual SQL executions remain connected.', zh: '一条真实采集的订单查询被呈现为业务谱系:页面目的、嵌套 request 的 comment 与实际执行的 SQL 始终保持连接。' }, observabilityCaption: { en: 'OpenObserve dashboard showing one verified trace and its eight actual SQL executions.', zh: 'OpenObserve 仪表盘展示一条已验证的 trace,以及它实际执行的八条 SQL。' }, platform: { en: 'Platform', zh: '平台' }, customer: { en: 'Customer', zh: '用户' }, order: { en: 'Order', zh: '订单' }, item: { en: 'Item', zh: '明细' }, payment: { en: 'Payment', zh: '支付' }, shipment: { en: 'Shipment', zh: '物流' }, constantModel: { en: 'Constant model', zh: '常数模型' }, persist: { en: 'Persist locally with OPFS', zh: '使用 OPFS 保存在本地' }, reset: { en: 'Reset demo data', zh: '重置演示数据' }, chooseQuery: { en: 'Choose a query', zh: '选择一个查询' }, queryIntro: { en: 'Queries are grouped by purpose and share the same order dataset.', zh: '查询按用途分组,并共用同一份订单数据。' }, table: { en: 'Table', zh: '表格' }, resultTree: { en: 'Result Tree', zh: '结果树' }, expandAll: { en: 'Expand all', zh: '全部展开' }, collapseAll: { en: 'Collapse all', zh: '全部折叠' }, waiting: { en: 'Waiting for query…', zh: '等待查询…' }, queryNote: { en: 'The Q expression stays fixed; search, status cards, and pagination only update dynamic parameters. The underlying SelectQuery remains inside the generated request implementation.', zh: 'Q 表达式保持不变;搜索、状态卡片和分页只更新动态参数。底层 SelectQuery 仍封装在生成的请求实现中。' }, executionEvidence: { en: 'EXECUTION EVIDENCE', zh: '执行证据' }, evidenceTime: { en: 'Time', zh: '时间' }, identity: { en: 'Identity', zh: '身份' }, traceChain: { en: 'Trace chain', zh: '追踪链' }, sqlNote: { en: 'One plain-text line per statement: time, identity, trace chain, and the SQL SQLite executed.', zh: '每条语句使用一行纯文本:时间、身份、追踪链,以及 SQLite 实际执行的 SQL。' }, localData: { en: 'Data stays in this browser', zh: '数据只保留在当前浏览器中' }, }; const EN_TEXT: Readonly> = { '基本查询': 'Basic queries', '从订单、用户和关联对象开始': 'Start with orders, customers, and related objects', '汇总查询': 'Aggregations', '分组、计数、求和与平均值': 'Grouping, counts, sums, and averages', '过滤查询': 'Filtered queries', '精确、范围与组合条件': 'Exact, range, and combined predicates', '动态查询': 'Dynamic queries', '由页面条件安全生成,不使用 eval': 'Safely generated from page criteria without eval', '综合:订单管理中心': 'Comprehensive: Order management center', '一个真实的综合查询页面:状态指标、搜索、商品预览和分页都由 TeaQL 查询驱动。': 'A real composite query page: status metrics, search, product previews, and pagination are all driven by TeaQL.', '最近订单': 'Recent orders', '按下单日期倒序浏览全部订单,并带出所属平台与用户。': 'Browse all orders by date descending, including their platform and customer.', '完整订单详情': 'Complete order details', '查看一个订单及其用户、商品明细、支付和物流,体现完整对象图。': 'Inspect one order with its customer, items, payments, and shipments as a complete object graph.', '用户与订单数': 'Customers with order counts', '列出用户,并通过关联汇总显示每位用户的订单数量。': 'List customers and show each customer’s order count through a relation aggregate.', '订单状态汇总': 'Orders by status', '按币种与订单状态统计订单数、总额和平均客单价。': 'Aggregate order count, gross amount, and average order value by currency and status.', '商品销售汇总': 'Product sales summary', '按商品汇总销量与销售额,找出最受欢迎的订单明细。': 'Aggregate units and revenue by product to identify the most popular order items.', '支付渠道对账': 'Payment reconciliation', '按币种、支付渠道和状态汇总笔数与金额。': 'Aggregate transaction count and amount by currency, payment provider, and status.', '待支付订单': 'Orders awaiting payment', '筛选尚未完成付款的订单,并带出对应支付记录。': 'Find orders that have not completed payment and include their payment records.', '国内大额订单': 'High-value domestic orders', '组合平台与金额条件,找出 TeaQL 商城中金额不低于 500 元的订单。': 'Combine platform and amount filters to find TeaQL Store orders worth at least CNY 500.', '运输中的包裹': 'Active shipments', '筛选待揽收和运输中的物流单,并关联订单信息。': 'Find shipments that are ready or in transit and include their order information.', '动态:已付款国内订单': 'Dynamic: paid domestic orders', '从动态条件对象生成查询:人民币、金额至少 250,且状态为已付款或已发货。': 'Build a query from dynamic criteria: CNY, at least 250, and status PAID or SHIPPED.', '动态:九月新订单': 'Dynamic: new September orders', '运行时传入日期范围与排序方式,查询 9 月 15 日之后的订单。': 'Pass a date range and sort order at runtime to find orders after September 15.', '动态:字段白名单': 'Dynamic: field allowlist', '未知字段会被报告并忽略;合法条件仍可安全执行。': 'Unknown fields are reported and ignored while valid predicates still execute safely.', }; let locale: Locale = localStorage.getItem('teaql-browser-locale') === 'zh' ? 'zh' : 'en'; function copy(key: string): string { return UI_COPY[key]?.[locale] ?? key; } function localize(value: string): string { return locale === 'en' ? EN_TEXT[value] ?? value : value; } let runtime: Runtime | undefined; let activeQueryId = window.location.hash.slice(1) || 'order-center'; if (!window.location.hash) history.replaceState(null, '', '#order-center'); let activeResultView: ResultView = 'tree'; let currentResult: { data: unknown; query: QueryExample; active: Runtime } | undefined; let lastElapsed = 0; let lastExecutedAt: Date | undefined; const dashboardState = { search: '', page: 0, pageSize: 4 }; const storagePreference = localStorage.getItem('teaql-browser-storage') === 'opfs' ? 'opfs' : 'memory'; persist.checked = storagePreference === 'opfs'; async function dynamicOrderQuery( active: Runtime, input: unknown, ): Promise<{ request: unknown; warnings: readonly unknown[]; data: unknown[] }> { const request = Q.customerOrders() .filterByJson(input) .limit(20) .comment('what: execute a validated browser search') .purpose('why: demonstrate safe dynamic order discovery'); const data = await request.executeForList(active.context); return { request: input, warnings: request.warnings(), data }; } type DashboardData = Readonly<{ orders: ReadonlyArray>; totalOrders: number; filteredOrders: number; statusFacets: ReadonlyArray>; search: string; page: number; pageSize: number; queryExpression: string; }>; const statusCodeById = Object.fromEntries( Object.entries(ORDER_STATUS).map(([code, id]) => [id, code]), ) as Readonly>; function statusCodeFor(status: Record): string { const selectedCode = String(status.code ?? ''); if (selectedCode && selectedCode in ORDER_STATUS) return selectedCode; if (!('color' in status)) return ''; return statusCodeById[String(status.id ?? '')] ?? ''; } function orderCenterSearchParams(search: string): Record { const value = search.trim(); if (!value) return { filter: {} }; const upper = value.toUpperCase(); const status = ['PENDING_PAYMENT', 'PAID', 'SHIPPED', 'DELIVERED', 'CANCELLED'] .find(candidate => candidate.includes(upper)); if (status) return { filter: { 'status.code': status } }; if (/^(TQL-|\d)/i.test(value)) return { filter: { orderNumber: { $contains: upper } } }; return { filter: { 'itemList.productName': { $contains: value } } }; } async function runOrderCenter(active: Runtime): Promise { const searchParams = orderCenterSearchParams(dashboardState.search); const request = Q.customerOrdersWithMinimalFields() .filterByJson(searchParams) .countAs('totalOrders') .facetByStatusAs('statusCounts', Q.orderStatuses().selectName().selectColor().countAs('orderCount').sumOrderTotalAmountAs('orderTotalAmount').orderByDisplayOrderAscending().comment('load status facets')) .selectOrderNumber() .selectOrderDate() .selectStatusWith(Q.orderStatuses().selectName().selectColor().comment('load status')) .selectItemListWith(Q.orderItems().selectProductName().selectQuantity().orderByIdAscending().limit(3).comment('load item previews')) .countItemListAs('itemCount') .orderByOrderDateDescending() .orderByIdDescending() .comment('load order list') .purpose('render order list page'); const queryExpression = request.toQExpression('offset(pageOffset)\n .limit(pageSize)\n .executeForList(ctx);'); const orders = await request .offset(dashboardState.page * dashboardState.pageSize) .limit(dashboardState.pageSize) .executeForList(active.context); const statusRows = orders.facet('statusCounts') ?? []; return { orders, totalOrders: Number(orders.aggregations.totalOrders ?? orders.totalCount), filteredOrders: orders.totalCount ?? 0, statusFacets: statusRows.map(row => ({ code: statusCodeFor(row), name: String(row.name ?? ''), color: String(row.color ?? ''), orderCount: Number(row.orderCount ?? 0), orderTotalAmount: Number(row.orderTotalAmount ?? 0), })).filter(status => status.code && status.name), search: dashboardState.search, page: dashboardState.page, pageSize: dashboardState.pageSize, queryExpression, }; } const QUERY_GROUPS: readonly QueryGroup[] = [ { label: '基本查询', description: '从订单、用户和关联对象开始', queries: [ { id: 'order-center', title: '综合:订单管理中心', description: '一个真实的综合查询页面:状态指标、搜索、商品预览和分页都由 TeaQL 查询驱动。', code: '', view: 'dashboard', run: runOrderCenter, }, { id: 'recent-orders', title: '最近订单', description: '按下单日期倒序浏览全部订单,并带出所属平台与用户。', code: `await Q.customerOrders() .selectPlatformWith(Q.platforms().selectName()) .selectCustomerWith(Q.customers().selectName().selectCity().selectTier()) .selectStatusWith(Q.orderStatuses().selectName().selectColor()) .orderByOrderDateDescending() .orderByIdDescending() .limit(8) .comment('what: list recent commerce orders') .purpose('why: provide the default order overview') .executeForList(ctx)`, run: active => Q.customerOrders() .selectPlatformWith(Q.platforms().selectName()) .selectCustomerWith(Q.customers().selectName().selectCity().selectTier()) .selectStatusWith(Q.orderStatuses().selectName().selectColor()) .orderByOrderDateDescending() .orderByIdDescending() .limit(8) .comment('what: list recent commerce orders') .purpose('why: provide the default order overview') .executeForList(active.context), }, { id: 'order-detail', title: '完整订单详情', description: '查看一个订单及其用户、商品明细、支付和物流,体现完整对象图。', code: `await Q.customerOrders() .withOrderNumberIs('TQL-2026-1003') .selectPlatformWith(Q.platforms()) .selectCustomerWith(Q.customers()) .selectStatusWith(Q.orderStatuses().selectName().selectColor()) .selectItemListWith(Q.orderItems().orderByIdAscending()) .selectPaymentListWith(Q.payments().orderByIdAscending()) .selectShipmentListWith(Q.shipments().orderByIdAscending()) .limit(1) .comment('what: load one complete order graph') .purpose('why: inspect items, payment and fulfillment together') .executeForList(ctx)`, run: active => Q.customerOrders() .withOrderNumberIs('TQL-2026-1003') .selectPlatformWith(Q.platforms()) .selectCustomerWith(Q.customers()) .selectStatusWith(Q.orderStatuses().selectName().selectColor()) .selectItemListWith(Q.orderItems().orderByIdAscending()) .selectPaymentListWith(Q.payments().orderByIdAscending()) .selectShipmentListWith(Q.shipments().orderByIdAscending()) .limit(1) .comment('what: load one complete order graph') .purpose('why: inspect items, payment and fulfillment together') .executeForList(active.context), }, { id: 'customers-with-order-count', title: '用户与订单数', description: '列出用户,并通过关联汇总显示每位用户的订单数量。', code: `await Q.customers() .selectPlatformWith(Q.platforms().selectName()) .countOrderListAs('orderCount') .orderByNameAscending() .comment('what: list customers with related order counts') .purpose('why: compare customer activity at a glance') .executeForList(ctx)`, run: active => Q.customers() .selectPlatformWith(Q.platforms().selectName()) .countOrderListAs('orderCount') .orderByNameAscending() .comment('what: list customers with related order counts') .purpose('why: compare customer activity at a glance') .executeForList(active.context), }, ], }, { label: '汇总查询', description: '分组、计数、求和与平均值', queries: [ { id: 'orders-by-status', title: '订单状态汇总', description: '按币种与订单状态统计订单数、总额和平均客单价。', code: `await Q.customerOrders() .groupByCurrency().groupByStatus() .countAs('orderCount') .sumTotalAmountAs('grossAmount') .averageTotalAmountAs('averageOrderValue') .comment('what: aggregate orders by currency and status') .purpose('why: show the current order funnel') .executeForRows(ctx)`, run: active => Q.customerOrders() .groupByCurrency().groupByStatus() .countAs('orderCount') .sumTotalAmountAs('grossAmount') .averageTotalAmountAs('averageOrderValue') .comment('what: aggregate orders by currency and status') .purpose('why: show the current order funnel') .executeForRows(active.context), }, { id: 'product-sales', title: '商品销售汇总', description: '按商品汇总销量与销售额,找出最受欢迎的订单明细。', code: `await Q.orderItems() .groupByProductName() .sumQuantityAs('units') .sumLineAmountAs('revenue') .comment('what: aggregate sold units and line revenue') .purpose('why: identify the strongest products in the demo data') .executeForRows(ctx)`, run: active => Q.orderItems() .groupByProductName() .sumQuantityAs('units') .sumLineAmountAs('revenue') .comment('what: aggregate sold units and line revenue') .purpose('why: identify the strongest products in the demo data') .executeForRows(active.context), }, { id: 'payment-reconciliation', title: '支付渠道对账', description: '按币种、支付渠道和状态汇总笔数与金额。', code: `await Q.payments() .groupByCurrency().groupByProvider().groupByStatus() .countAs('paymentCount').sumAmountAs('paymentAmount') .orderByCurrencyAscending().orderByProviderAscending() .comment('what: reconcile payment totals by provider') .purpose('why: compare captured, pending and refunded money') .executeForRows(ctx)`, run: active => Q.payments() .groupByCurrency().groupByProvider().groupByStatus() .countAs('paymentCount').sumAmountAs('paymentAmount') .orderByCurrencyAscending().orderByProviderAscending() .comment('what: reconcile payment totals by provider') .purpose('why: compare captured, pending and refunded money') .executeForRows(active.context), }, ], }, { label: '过滤查询', description: '精确、范围与组合条件', queries: [ { id: 'pending-payment', title: '待支付订单', description: '筛选尚未完成付款的订单,并带出对应支付记录。', code: `await Q.customerOrders() .withStatusIsPendingPayment() .selectStatusWith(Q.orderStatuses().selectName().selectColor()) .selectCustomerWith(Q.customers().selectName()) .selectPaymentListWith(Q.payments()) .comment('what: find orders awaiting payment') .purpose('why: support payment follow-up') .executeForList(ctx)`, run: active => Q.customerOrders() .withStatusIsPendingPayment() .selectStatusWith(Q.orderStatuses().selectName().selectColor()) .selectCustomerWith(Q.customers().selectName()) .selectPaymentListWith(Q.payments()) .comment('what: find orders awaiting payment') .purpose('why: support payment follow-up') .executeForList(active.context), }, { id: 'large-cn-orders', title: '国内大额订单', description: '组合平台与金额条件,找出 TeaQL 商城中金额不低于 500 元的订单。', code: `await Q.customerOrders() .withPlatformIdIs('1') .withTotalAmountGreaterThanOrEqualTo(500) .selectCustomerWith(Q.customers().selectName().selectCity()) .orderByTotalAmountDescending() .comment('what: find high-value domestic orders') .purpose('why: prioritize valuable fulfillment work') .executeForList(ctx)`, run: active => Q.customerOrders() .withPlatformIdIs('1') .withTotalAmountGreaterThanOrEqualTo(500) .selectCustomerWith(Q.customers().selectName().selectCity()) .orderByTotalAmountDescending() .comment('what: find high-value domestic orders') .purpose('why: prioritize valuable fulfillment work') .executeForList(active.context), }, { id: 'open-shipments', title: '运输中的包裹', description: '筛选待揽收和运输中的物流单,并关联订单信息。', code: `await Q.shipments() .withStatusIn('READY', 'IN_TRANSIT') .selectOrderWith(Q.customerOrders().selectOrderNumber().selectOrderDate().selectStatusWith(Q.orderStatuses().selectName().selectColor()).selectTotalAmount().selectCurrency()) .orderByStatusAscending().orderByIdAscending() .comment('what: find shipments that are not delivered') .purpose('why: monitor active fulfillment') .executeForList(ctx)`, run: active => Q.shipments() .withStatusIn('READY', 'IN_TRANSIT') .selectOrderWith(Q.customerOrders() .selectOrderNumber().selectOrderDate() .selectStatusWith(Q.orderStatuses().selectName().selectColor()) .selectTotalAmount().selectCurrency()) .orderByStatusAscending().orderByIdAscending() .comment('what: find shipments that are not delivered') .purpose('why: monitor active fulfillment') .executeForList(active.context), }, ], }, { label: '动态查询', description: '由页面条件安全生成,不使用 eval', queries: [ { id: 'dynamic-paid-cn', title: '动态:已付款国内订单', description: '从动态条件对象生成查询:人民币、金额至少 250,且状态为已付款或已发货。', code: `const input = { "filter": { "currency": "CNY", "totalAmount": { "$gte": 250 }, "status": { "$in": ["PAID", "SHIPPED"] } }, "orderBy": [{ "field": "totalAmount", "direction": "desc" }] }; const data = await Q.customerOrders() .filterByJson(input) .limit(20) .comment('what: execute a validated browser search') .purpose('why: demonstrate safe dynamic order discovery') .executeForList(ctx);`, run: active => dynamicOrderQuery(active, { filter: { currency: 'CNY', totalAmount: { $gte: 250 }, status: { $in: ['PAID', 'SHIPPED'] }, }, orderBy: [{ field: 'totalAmount', direction: 'desc' }], }), }, { id: 'dynamic-date-range', title: '动态:九月新订单', description: '运行时传入日期范围与排序方式,查询 9 月 15 日之后的订单。', code: `const input = { "filter": { "orderDate": { "$gte": "2026-09-15" } }, "orderBy": [{ "field": "orderDate", "direction": "asc" }] }; const data = await Q.customerOrders() .filterByJson(input) .limit(20) .comment('what: execute a validated browser search') .purpose('why: demonstrate safe dynamic order discovery') .executeForList(ctx);`, run: active => dynamicOrderQuery(active, { filter: { orderDate: { $gte: '2026-09-15' } }, orderBy: [{ field: 'orderDate', direction: 'asc' }], }), }, { id: 'dynamic-safe-fields', title: '动态:字段白名单', description: '未知字段会被报告并忽略;合法条件仍可安全执行。', code: `const input = { "filter": { "status": "DELIVERED", "internalNote": { "$contains": "not queryable" } } }; const request = Q.customerOrders().filterByJson(input); const data = await request .limit(20) .comment('what: execute a validated browser search') .purpose('why: demonstrate safe dynamic order discovery') .executeForList(ctx); const warnings = request.warnings();`, run: active => dynamicOrderQuery(active, { filter: { status: 'DELIVERED', internalNote: { $contains: 'not queryable' }, }, }), }, ], }, ] as const; const allQueries = QUERY_GROUPS.flatMap(group => group.queries); async function openRuntime(storage: BrowserSQLiteStorage): Promise { const worker = new Worker(new URL('./sqlite.worker.ts', import.meta.url), { type: 'module' }); const client = await BrowserSQLiteTeaQLClient.open(worker, { ...ORDER_SCHEMAS }, { storage, databaseName: 'teaql-order-playground-v4.sqlite3', }); const evidence = new SQLExecutionEvidenceStore().enableSelect(); client.install(new RuntimeModule(ORDER_SCHEMAS, {}, { constants: ORDER_STATUS_CONSTANTS, })); client.setRuntimeTelemetrySink(evidence).setDiagnosticSQLLogSink(undefined); const policyIdentity = Object.freeze({ policyId: 'browser-order-demo-seed', version: '1', fingerprint: 'sha256:browser-order-demo-seed-v2', }); const context = new UserContext() .insertResource('dataService', client) .withMutationPolicyRegistry(new DelegatingMutationPolicyRegistry(() => ({ identity: policyIdentity, review: (_context, plan) => plan.operations.length > 0 ? { verdict: 'allow' } : { verdict: 'deny', code: 'EMPTY_GRAPH', message: 'empty mutation graph' }, }))) .withMutationPolicyApprovalProvider(new DelegatingMutationPolicyApprovalProvider( identity => ({ policy: identity, approvedBy: 'browser-demo-owner', approvedAt: new Date() }), )); client.setUserContext(context); await context.ensureSchema(); return { client, context, evidence }; } async function seedDemoData(active: Runtime): Promise { const existing = await Q.customerOrders() .selectId() .limit(1) .comment('what: detect existing order playground data') .purpose('why: keep deterministic browser bootstrap idempotent') .executeForList(active.context); if (existing.length) return; const intent = new MutationIntent('Seed deterministic order playground data'); await active.client.executeGraphSave(intent, async graph => { const requests = ORDER_SEED.map(record => graph.request({ entity: record.entity, action: 'Create', id: record.id, payload: record.payload, })); for (const request of requests) active.client.preflightMutation(request); for (const request of requests) await active.client.executeMutation(request); }); } function renderQueryCatalog(): void { queryCatalog.replaceChildren(); for (const group of QUERY_GROUPS) { const section = document.createElement('section'); section.className = 'query-group'; const heading = document.createElement('div'); heading.className = 'query-group-heading'; const title = document.createElement('h2'); title.textContent = localize(group.label); const description = document.createElement('p'); description.textContent = localize(group.description); heading.append(title, description); const links = document.createElement('div'); links.className = 'query-links'; for (const query of group.queries) { const link = document.createElement('a'); link.href = `#${query.id}`; link.dataset.queryId = query.id; link.textContent = localize(query.title); link.addEventListener('click', event => { event.preventDefault(); activeQueryId = query.id; history.replaceState(null, '', `#${query.id}`); updateActiveLink(); if (runtime) runQuery(query, runtime).catch(showError); }); links.append(link); } section.append(heading, links); queryCatalog.append(section); } updateActiveLink(); } function updateActiveLink(): void { queryCatalog.querySelectorAll('a[data-query-id]').forEach(link => { const active = link.dataset.queryId === activeQueryId; link.classList.toggle('active', active); if (active) link.setAttribute('aria-current', 'page'); else link.removeAttribute('aria-current'); }); } async function runQuery(query: QueryExample, active: Runtime): Promise { const startedAt = performance.now(); const executedAt = new Date(); active.evidence.enableSelect(); result.classList.add('loading'); result.setAttribute('aria-busy', 'true'); resultTitle.textContent = localize(query.title); resultDescription.textContent = localize(query.description); queryCode.textContent = query.code; queryParamsBlock.classList.toggle('hidden', query.view !== 'dashboard'); queryParamsCode.textContent = query.view === 'dashboard' ? JSON.stringify({ searchParams: orderCenterSearchParams(dashboardState.search), pageOffset: dashboardState.page * dashboardState.pageSize, pageSize: dashboardState.pageSize, }, null, 2) : ''; resultGrid.classList.toggle('dashboard-mode', query.view === 'dashboard'); result.classList.toggle('dashboard-result', query.view === 'dashboard'); expandAll.closest('.tree-actions')?.classList.toggle('hidden', query.view === 'dashboard'); resultViewLabel.textContent = query.view === 'dashboard' ? (locale === 'en' ? 'Order management center' : '订单管理中心') : (locale === 'en' ? 'Result tree' : '结果树'); resultMeta.textContent = locale === 'en' ? 'Running query…' : '查询中…'; try { const data = await query.run(active); if (query.view === 'dashboard') { queryCode.textContent = (data as DashboardData).queryExpression; } currentResult = { data, query, active }; activeResultView = query.view === 'dashboard' || isTabularResult(data) ? 'table' : 'tree'; renderCurrentResult(); const elapsed = Math.max(1, Math.round(performance.now() - startedAt)); lastElapsed = elapsed; lastExecutedAt = executedAt; const statements = active.evidence.snapshot(); renderSql(statements, executedAt); updateResultMeta(data, query, elapsed); } finally { result.classList.remove('loading'); result.removeAttribute('aria-busy'); } } function renderSql(statements: readonly SQLExecutionMetadata[], executedAt = lastExecutedAt): void { sqlOutput.replaceChildren(); sqlCount.textContent = locale === 'en' ? `${statements.length} statement${statements.length === 1 ? '' : 's'}` : `${statements.length} 条语句`; statements.forEach((statement, index) => { const row = document.createElement('tr'); row.className = 'evidence-row'; const traceIdentity = [...statement.tracePath].reverse() .find(frame => frame.kind === 'relation' || frame.kind === 'request' || frame.kind === 'entity'); const timeCell = document.createElement('td'); timeCell.className = 'evidence-time'; timeCell.dataset.executedAt = executedAt?.toISOString() ?? ''; timeCell.textContent = `${executedAt?.toISOString() ?? '—'} · ${Math.max(0.01, statement.elapsedMicros / 1000).toFixed(2)} ms`; const identityCell = document.createElement('td'); identityCell.textContent = `${currentResult?.query.id ?? 'query'} · #${index + 1} · ${traceIdentity?.name ?? 'query'}`; const traceCell = document.createElement('td'); traceCell.className = 'evidence-trace'; if (statement.traceChain?.length) { const purpose = statement.traceChain.find(node => node.kind === 'purpose'); const comments = statement.traceChain .filter(node => node.kind === 'comment') .map(node => node.detail || node.name); traceCell.textContent = `${purpose?.detail || purpose?.name || ''}: ${comments.join(' → ')}`; } else { traceCell.textContent = statement.tracePath.map(frame => frame.detail || frame.name).join(' → '); } const sqlCell = document.createElement('td'); sqlCell.className = 'evidence-sql'; const bindings = statement.parameters.length ? statement.parameters.map(value => String(value)).join(', ') : 'none'; sqlCell.textContent = `${statement.parameterizedSQL} · bindings: ${bindings}`; row.append(timeCell, identityCell, traceCell, sqlCell); sqlOutput.append(row); }); } function isTabularResult(value: unknown): value is ReadonlyArray> { return Array.isArray(value) && value.every(row => row !== null && typeof row === 'object' && !Array.isArray(row)); } function renderCurrentResult(): void { if (!currentResult) return; const { data, query, active } = currentResult; const tableAvailable = query.view === 'dashboard' || isTabularResult(data); if (!tableAvailable) activeResultView = 'tree'; viewTable.disabled = !tableAvailable; viewTable.classList.toggle('active', activeResultView === 'table'); viewTree.classList.toggle('active', activeResultView === 'tree'); viewTable.setAttribute('aria-pressed', String(activeResultView === 'table')); viewTree.setAttribute('aria-pressed', String(activeResultView === 'tree')); expandAll.closest('.tree-actions')?.classList.toggle('hidden', activeResultView !== 'tree'); if (activeResultView === 'table' && query.view === 'dashboard') { resultViewLabel.textContent = locale === 'en' ? 'Result table' : '结果表格'; renderDashboard(data as DashboardData, active, query); return; } if (activeResultView === 'table' && isTabularResult(data)) { resultViewLabel.textContent = locale === 'en' ? 'Result table' : '结果表格'; renderTable(data); return; } resultViewLabel.textContent = locale === 'en' ? 'Result tree' : '结果树'; renderTree(query.view === 'dashboard' ? dashboardTreeData(data as DashboardData) : data); } function dashboardTreeData(data: DashboardData): Record { return { orders: data.orders, totalOrders: data.totalOrders, filteredOrders: data.filteredOrders, statusFacets: data.statusFacets, search: data.search, page: data.page, pageSize: data.pageSize, }; } function renderTable(rows: ReadonlyArray>): void { const shell = document.createElement('div'); shell.className = 'result-table-shell'; if (!rows.length) { const empty = document.createElement('p'); empty.className = 'result-table-empty'; empty.textContent = locale === 'en' ? 'The query returned no records.' : '查询没有返回记录。'; shell.append(empty); result.replaceChildren(shell); return; } const columns = [...new Set(rows.flatMap(row => Object.keys(row)))]; const table = document.createElement('table'); table.className = 'result-table'; const head = document.createElement('thead'); const headRow = document.createElement('tr'); for (const column of columns) { const cell = document.createElement('th'); cell.scope = 'col'; cell.textContent = column; headRow.append(cell); } head.append(headRow); const body = document.createElement('tbody'); for (const row of rows) { const tableRow = document.createElement('tr'); for (const column of columns) { const cell = document.createElement('td'); cell.textContent = tableCellText(row[column]); tableRow.append(cell); } body.append(tableRow); } table.append(head, body); shell.append(table); result.replaceChildren(shell); } function tableCellText(value: unknown): string { if (value === null || value === undefined) return '—'; if (Array.isArray(value)) return locale === 'en' ? `${value.length} items` : `${value.length} 项`; if (value instanceof Date) return value.toISOString(); if (typeof value === 'object') { const record = value as Record; const code = statusCodeFor(record); if (code) return statusLabel(code); const identity = record.name ?? record.orderNumber ?? record.code ?? record.id; return identity === undefined ? (locale === 'en' ? `${Object.keys(record).length} fields` : `${Object.keys(record).length} 个字段`) : String(identity); } return String(value); } function renderDashboard(data: DashboardData, active: Runtime, query: QueryExample): void { const center = document.createElement('div'); center.className = 'order-center'; const toolbar = document.createElement('div'); toolbar.className = 'order-center-toolbar'; const heading = document.createElement('div'); const title = document.createElement('h3'); title.textContent = locale === 'en' ? 'Order Management Center' : '订单管理中心'; const subtitle = document.createElement('p'); subtitle.textContent = locale === 'en' ? 'One generated Q expression describes the entire page' : '一个生成式 Q 表达式描述整个页面'; heading.append(title, subtitle); const searchForm = document.createElement('form'); searchForm.className = 'order-search'; const searchInput = document.createElement('input'); searchInput.type = 'search'; searchInput.placeholder = locale === 'en' ? 'Order number, status, or product…' : '订单号、状态或商品名称…'; searchInput.value = data.search; searchInput.setAttribute('aria-label', locale === 'en' ? 'Search orders' : '搜索订单'); const searchButton = document.createElement('button'); searchButton.type = 'submit'; searchButton.className = 'order-search-submit'; searchButton.setAttribute('aria-label', locale === 'en' ? 'Run order query' : '执行订单查询'); searchButton.title = locale === 'en' ? 'Search' : '查询'; const searchIcon = document.createElement('span'); searchIcon.className = 'search-icon'; searchIcon.setAttribute('aria-hidden', 'true'); searchButton.append(searchIcon); searchForm.append(searchInput, searchButton); searchForm.addEventListener('submit', event => { event.preventDefault(); dashboardState.search = searchInput.value.trim(); dashboardState.page = 0; runQuery(query, active).catch(showError); }); toolbar.append(heading, searchForm); const metrics = document.createElement('div'); metrics.className = 'order-metrics'; metrics.append(metricCard(locale === 'en' ? 'All orders' : '全部订单', data.totalOrders, 'total', '#2B66D9', !data.search, () => { dashboardState.search = ''; dashboardState.page = 0; runQuery(query, active).catch(showError); })); for (const status of data.statusFacets) { metrics.append(metricCard( status.name, status.orderCount, status.code.toLowerCase(), status.color, data.search.toUpperCase() === status.code, () => { dashboardState.search = status.code; dashboardState.page = 0; runQuery(query, active).catch(showError); }, `${locale === 'en' ? 'Total' : '总额'} ${formatFacetAmount(status.orderTotalAmount)}`, )); } const table = document.createElement('div'); table.className = 'order-table'; const tableHeader = document.createElement('div'); tableHeader.className = 'order-row order-row-head'; const columnLabels = locale === 'en' ? ['Order number', 'Order date', 'Status', 'Line Items'] : ['订单号', '订单日期', '状态', '订单明细']; for (const label of columnLabels) { const cell = document.createElement('span'); cell.textContent = label; tableHeader.append(cell); } table.append(tableHeader); if (!data.orders.length) { const empty = document.createElement('div'); empty.className = 'order-empty'; empty.textContent = locale === 'en' ? 'No matching orders. Try an order number, status code, or product name.' : '没有匹配的订单,请尝试订单号、状态英文或商品名称。'; table.append(empty); } for (const order of data.orders) table.append(renderOrderRow(order)); const footer = document.createElement('div'); footer.className = 'order-center-footer'; const pageInfo = document.createElement('p'); const first = data.filteredOrders ? data.page * data.pageSize + 1 : 0; const last = Math.min(data.filteredOrders, (data.page + 1) * data.pageSize); pageInfo.textContent = locale === 'en' ? `Showing ${first}–${last} of ${data.filteredOrders} orders` : `显示 ${first}–${last},共 ${data.filteredOrders} 条订单`; const pagination = document.createElement('div'); pagination.className = 'pagination'; const pageCount = Math.max(1, Math.ceil(data.filteredOrders / data.pageSize)); pagination.append(pageButton('‹', data.page - 1, data.page === 0, active, query)); for (let page = 0; page < pageCount; page += 1) { const button = pageButton(String(page + 1), page, false, active, query); button.classList.toggle('active', page === data.page); pagination.append(button); } pagination.append(pageButton('›', data.page + 1, data.page >= pageCount - 1, active, query)); footer.append(pageInfo, pagination); center.append(toolbar, metrics, table, footer); result.replaceChildren(center); } function metricCard( label: string, count: number, tone: string, color: string, active: boolean, select: () => void, detail?: string, ): HTMLButtonElement { const button = document.createElement('button'); button.type = 'button'; button.className = `metric-card metric-${tone}`; if (color) button.style.setProperty('--metric-color', color); button.classList.toggle('active', active); const name = document.createElement('span'); name.textContent = label; const value = document.createElement('strong'); value.textContent = String(count); button.append(name, value); if (detail) { const secondary = document.createElement('small'); secondary.textContent = detail; button.append(secondary); } button.addEventListener('click', select); return button; } function formatFacetAmount(value: number): string { return new Intl.NumberFormat(locale === 'en' ? 'en-US' : 'zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(value); } function renderOrderRow(order: Record): HTMLElement { const row = document.createElement('div'); row.className = 'order-row'; const status = (order.status ?? {}) as Record; const items = Array.isArray(order.itemList) ? order.itemList as Array> : []; const values = [ String(order.orderNumber ?? ''), String(order.orderDate ?? ''), ]; values.forEach(value => { const cell = document.createElement('span'); cell.className = 'order-primary'; cell.textContent = value; row.append(cell); }); const statusCell = document.createElement('span'); const badge = document.createElement('span'); const statusCode = statusCodeFor(status); badge.className = `status-badge status-${statusCode.toLowerCase()}`; badge.textContent = statusLabel(statusCode) || String(status.name ?? statusCode); const statusColor = String(status.color ?? ''); if (statusColor) { badge.style.color = statusColor; badge.style.backgroundColor = `${statusColor}18`; } statusCell.append(badge); row.append(statusCell); const products = document.createElement('span'); products.className = 'product-preview'; items.forEach((item, index) => { const thumb = document.createElement('span'); thumb.className = `product-thumb product-tone-${index % 3}`; thumb.textContent = String(item.productName ?? '?').slice(0, 1); thumb.title = `${String(item.productName ?? '')} × ${String(item.quantity ?? 1)}`; products.append(thumb); }); const totalItems = Number(order.itemCount ?? items.length); if (totalItems > items.length) { const count = document.createElement('small'); count.className = 'line-item-count'; count.textContent = locale === 'en' ? `${totalItems} items` : `共 ${totalItems} 项`; count.title = locale === 'en' ? `Showing ${items.length} of ${totalItems} line items` : `显示 ${items.length} 项,共 ${totalItems} 项订单明细`; products.append(count); } row.append(products); return row; } function pageButton( label: string, page: number, disabled: boolean, active: Runtime, query: QueryExample, ): HTMLButtonElement { const button = document.createElement('button'); button.type = 'button'; button.textContent = label; button.disabled = disabled; button.addEventListener('click', () => { dashboardState.page = page; runQuery(query, active).catch(showError); }); return button; } function statusLabel(value: string): string { const labels = locale === 'en' ? { PENDING_PAYMENT: 'Pending payment', PAID: 'Paid', SHIPPED: 'Shipped', DELIVERED: 'Delivered', CANCELLED: 'Cancelled' } : { PENDING_PAYMENT: '待支付', PAID: '已支付', SHIPPED: '运输中', DELIVERED: '已完成', CANCELLED: '已取消' }; return (labels as Record)[value] ?? value; } function renderTree(value: unknown): void { result.replaceChildren(createTreeNode(value, 'result', 0)); } function createTreeNode(value: unknown, key: string, depth: number): HTMLElement { if (value !== null && typeof value === 'object') { const entries = Array.isArray(value) ? value.map((item, index) => [String(index), item] as const) : Object.entries(value as Record); const details = document.createElement('details'); details.className = 'tree-branch'; details.open = depth === 0; const summary = document.createElement('summary'); const keyLabel = document.createElement('span'); keyLabel.className = 'tree-key'; keyLabel.textContent = key; const preview = document.createElement('span'); preview.className = 'tree-preview'; preview.textContent = objectPreview(value, entries.length); summary.append(keyLabel, preview); details.append(summary); const children = document.createElement('div'); children.className = 'tree-children'; for (const [childKey, childValue] of entries) { children.append(createTreeNode(childValue, Array.isArray(value) ? `#${Number(childKey) + 1}` : childKey, depth + 1)); } details.append(children); return details; } const leaf = document.createElement('div'); leaf.className = 'tree-leaf'; const keyLabel = document.createElement('span'); keyLabel.className = 'tree-key'; keyLabel.textContent = key; const scalar = document.createElement('span'); scalar.className = `tree-value tree-${value === null ? 'null' : typeof value}`; scalar.textContent = value === null ? 'null' : typeof value === 'string' ? `“${value}”` : String(value); leaf.append(keyLabel, scalar); return leaf; } function objectPreview(value: object, size: number): string { if (Array.isArray(value)) return locale === 'en' ? `${size} items` : `${size} 项`; const record = value as Record; const identity = record.orderNumber ?? record.name ?? record.productName ?? record.transactionId ?? record.trackingNumber; return identity ? String(identity) : (locale === 'en' ? `${size} fields` : `${size} 个字段`); } function formatExecutedAt(value: Date): string { return new Intl.DateTimeFormat(locale === 'en' ? 'en-US' : 'zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }).format(value); } function resultRowCount(data: unknown): number { if (Array.isArray(data)) return data.length; return Array.isArray((data as { data?: unknown[] })?.data) ? (data as { data: unknown[] }).data.length : 1; } function updateResultMeta(data: unknown, query: QueryExample, elapsed: number): void { const count = query.view === 'dashboard' ? (data as DashboardData).filteredOrders : resultRowCount(data); const rows = locale === 'en' ? `${count} ${query.view === 'dashboard' ? `matching order${count === 1 ? '' : 's'}` : `result${count === 1 ? '' : 's'}`}` : `${count} 条${query.view === 'dashboard' ? '匹配订单' : '结果'}`; resultMeta.textContent = `${rows} · ${elapsed} ms · ${locale === 'en' ? 'in-browser SQLite' : '浏览器内 SQLite'}`; } function updateRuntimeStatus(storage: BrowserSQLiteStorage): void { const orders = ORDER_SEED.filter(record => record.entity === 'CustomerOrder').length; status.textContent = locale === 'en' ? `${storage.toUpperCase()} · ${orders} orders · 7 entities · policy installed · ready` : `${storage.toUpperCase()} · ${orders} 个订单 · 7 个实体 · 已安装策略 · 就绪`; } function applyLocale(): void { document.documentElement.lang = locale === 'en' ? 'en' : 'zh-CN'; document.title = locale === 'en' ? 'TeaQL Order Query Playground' : 'TeaQL 订单查询演练场'; const description = document.querySelector('meta[name="description"]'); if (description) description.content = locale === 'en' ? 'A TeaQL order-query playground running entirely in the browser' : '完全运行在浏览器中的 TeaQL 订单查询演练场'; document.querySelectorAll('[data-i18n]').forEach(element => { const key = element.dataset.i18n; if (key) element.textContent = copy(key); }); languageButtons.forEach(button => { const active = button.dataset.locale === locale; button.classList.toggle('active', active); button.setAttribute('aria-pressed', String(active)); }); document.querySelector('.language-switch')?.setAttribute('aria-label', locale === 'en' ? 'Language' : '语言'); document.querySelector('.brand')?.setAttribute('aria-label', locale === 'en' ? 'TeaQL Playground home' : 'TeaQL 演练场首页'); document.querySelector('.model-map')?.setAttribute('aria-label', locale === 'en' ? 'Order domain model' : '订单领域模型'); document.querySelector('.runtime-language-selector')?.setAttribute('aria-label', locale === 'en' ? 'TeaQL runtimes' : 'TeaQL 运行时'); document.querySelector('.runtime-bar')?.setAttribute('aria-label', locale === 'en' ? 'Runtime settings' : '运行时设置'); queryCatalog.setAttribute('aria-label', locale === 'en' ? 'Example queries' : '示例查询'); document.querySelector('.result-view-switch')?.setAttribute('aria-label', locale === 'en' ? 'Result views' : '结果视图'); document.querySelector('.tree-actions')?.setAttribute('aria-label', locale === 'en' ? 'Result tree actions' : '结果树操作'); renderQueryCatalog(); if (currentResult) { const { data, query, active } = currentResult; resultTitle.textContent = localize(query.title); resultDescription.textContent = localize(query.description); renderCurrentResult(); renderSql(active.evidence.snapshot(), lastExecutedAt); updateResultMeta(data, query, lastElapsed); updateRuntimeStatus(persist.checked ? 'opfs' : 'memory'); } else { status.textContent = locale === 'en' ? 'Starting SQLite/WASM…' : '正在启动 SQLite/WASM…'; } } function selectedQuery(): QueryExample { return allQueries.find(query => query.id === activeQueryId) ?? allQueries[0]; } async function replaceRuntime(storage: BrowserSQLiteStorage): Promise { status.textContent = locale === 'en' ? `Opening ${storage.toUpperCase()} SQLite/WASM…` : `正在打开 ${storage.toUpperCase()} SQLite/WASM…`; const previous = runtime; runtime = undefined; await previous?.client.close(); const next = await openRuntime(storage); runtime = next; await seedDemoData(next); await runQuery(selectedQuery(), next); updateRuntimeStatus(storage); } persist.addEventListener('change', () => { const storage = persist.checked ? 'opfs' : 'memory'; localStorage.setItem('teaql-browser-storage', storage); replaceRuntime(storage).catch(showError); }); reset.addEventListener('click', async () => { if (!runtime) return; reset.disabled = true; try { await runtime.client.reset(runtime.context); await seedDemoData(runtime); await runQuery(selectedQuery(), runtime); } catch (error) { showError(error); } finally { reset.disabled = false; } }); expandAll.addEventListener('click', () => { result.querySelectorAll('details').forEach(branch => { branch.open = true; }); }); viewTable.addEventListener('click', () => { if (viewTable.disabled) return; activeResultView = 'table'; renderCurrentResult(); }); viewTree.addEventListener('click', () => { activeResultView = 'tree'; renderCurrentResult(); }); collapseAll.addEventListener('click', () => { result.querySelectorAll('details').forEach((branch, index) => { branch.open = index === 0; }); }); languageButtons.forEach(button => { button.addEventListener('click', () => { locale = button.dataset.locale === 'zh' ? 'zh' : 'en'; localStorage.setItem('teaql-browser-locale', locale); applyLocale(); }); }); function showError(value: unknown): void { const error = value instanceof Error ? value : new Error(String(value)); status.textContent = `${error.name}: ${error.message}`; resultMeta.textContent = locale === 'en' ? 'Query failed' : '查询失败'; renderTree({ error: error.name, message: error.message }); } applyLocale(); activeQueryId = selectedQuery().id; updateActiveLink(); replaceRuntime(storagePreference).catch(showError);