File size: 4,415 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
import {
  BrowserSQLiteStorage,
  BrowserSQLiteTeaQLClient,
} from 'teaql-ts/sql/browser-sqlite';
import {
  DelegatingMutationPolicyApprovalProvider,
  DelegatingMutationPolicyRegistry,
  UserContext,
} from 'teaql-ts';
import { Q } from '../../conformance/src/generated/Q';
import { WorkItem } from '../../conformance/src/generated/models/WorkItem';
import { GENERATED_RUNTIME_MODULE } from '../../conformance/src/runtime-module';
import { ENTITY_SCHEMAS } from '../../conformance/src/teaql-node-sql';
import './work-items.css';

const status = document.querySelector<HTMLParagraphElement>('#status')!;
const result = document.querySelector<HTMLPreElement>('#result')!;
const persist = document.querySelector<HTMLInputElement>('#persist')!;
const reset = document.querySelector<HTMLButtonElement>('#reset')!;

type Runtime = { client: BrowserSQLiteTeaQLClient; context: UserContext };
let runtime: Runtime | undefined;
const storagePreference = localStorage.getItem('teaql-browser-storage') === 'opfs' ? 'opfs' : 'memory';
persist.checked = storagePreference === 'opfs';

async function openRuntime(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-example.sqlite3',
  });
  client.install(GENERATED_RUNTIME_MODULE);
  const policyIdentity = Object.freeze({
    policyId: 'browser-demo-seed',
    version: '1',
    fingerprint: 'sha256:browser-demo-seed-v1',
  });
  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 };
}

async function seedDemoData({ context }: Runtime): Promise<void> {
  const existing = await Q.workItemsWithMinimalFields()
    .comment('what: detect existing browser demo records')
    .purpose('why: keep generated mutation bootstrap idempotent')
    .executeForList(context);
  if (existing.length) return;
  for (const title of ['Evaluate semantic model', 'Generate typed library', 'Verify browser runtime']) {
    await new WorkItem()
      .updateTitle(title)
      .updatePlatform('1')
      .auditAs('seed deterministic browser demo data')
      .save(context);
  }
}

async function showData(active: Runtime): Promise<void> {
  const rows = await Q.workItems()
    .selectTitle()
    .orderByIdAscending()
    .limit(20)
    .comment('what: load browser-local work items')
    .purpose('why: prove the generated Q API runs without a backend')
    .executeForList(active.context);
  result.textContent = JSON.stringify(rows, null, 2);
  status.textContent = `${active.client.storage!.toUpperCase()} 路 ${rows.length} rows 路 policy installed 路 ready`;
}

async function replaceRuntime(storage: BrowserSQLiteStorage): Promise<void> {
  status.textContent = `Opening ${storage.toUpperCase()} SQLite/WASM鈥;
  const previous = runtime;
  runtime = undefined;
  await previous?.client.close();
  const next = await openRuntime(storage);
  runtime = next;
  await seedDemoData(next);
  await showData(next);
}

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 showData(runtime);
  } catch (error) { showError(error); }
  finally { reset.disabled = false; }
});

function showError(value: unknown): void {
  const error = value instanceof Error ? value : new Error(String(value));
  status.textContent = `${error.name}: ${error.message}`;
}

replaceRuntime(storagePreference).catch(showError);