@uptimizr/db 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +109 -0
- package/LICENSE +201 -0
- package/README.md +89 -0
- package/dist/cli/createProject.d.ts +3 -0
- package/dist/cli/createProject.d.ts.map +1 -0
- package/dist/cli/createProject.js +41 -0
- package/dist/cli/createProject.js.map +1 -0
- package/dist/cli/migrate.d.ts +3 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +21 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli/seed.d.ts +2 -0
- package/dist/cli/seed.d.ts.map +1 -0
- package/dist/cli/seed.js +175 -0
- package/dist/cli/seed.js.map +1 -0
- package/dist/duckdb/client.d.ts +42 -0
- package/dist/duckdb/client.d.ts.map +1 -0
- package/dist/duckdb/client.js +93 -0
- package/dist/duckdb/client.js.map +1 -0
- package/dist/duckdb/events.d.ts +44 -0
- package/dist/duckdb/events.d.ts.map +1 -0
- package/dist/duckdb/events.js +225 -0
- package/dist/duckdb/events.js.map +1 -0
- package/dist/duckdb/migrations.d.ts +20 -0
- package/dist/duckdb/migrations.d.ts.map +1 -0
- package/dist/duckdb/migrations.js +323 -0
- package/dist/duckdb/migrations.js.map +1 -0
- package/dist/duckdb/projects.d.ts +23 -0
- package/dist/duckdb/projects.d.ts.map +1 -0
- package/dist/duckdb/projects.js +55 -0
- package/dist/duckdb/projects.js.map +1 -0
- package/dist/duckdb/queries.d.ts +10 -0
- package/dist/duckdb/queries.d.ts.map +1 -0
- package/dist/duckdb/queries.js +10 -0
- package/dist/duckdb/queries.js.map +1 -0
- package/dist/duckdb/sceneRegistry.d.ts +15 -0
- package/dist/duckdb/sceneRegistry.d.ts.map +1 -0
- package/dist/duckdb/sceneRegistry.js +98 -0
- package/dist/duckdb/sceneRegistry.js.map +1 -0
- package/dist/env.d.ts +25 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +55 -0
- package/dist/env.js.map +1 -0
- package/dist/events.d.ts +107 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +153 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata.d.ts +84 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +23 -0
- package/dist/metadata.js.map +1 -0
- package/dist/parity/cases.d.ts +31 -0
- package/dist/parity/cases.d.ts.map +1 -0
- package/dist/parity/cases.js +524 -0
- package/dist/parity/cases.js.map +1 -0
- package/dist/parity/compare.d.ts +47 -0
- package/dist/parity/compare.d.ts.map +1 -0
- package/dist/parity/compare.js +90 -0
- package/dist/parity/compare.js.map +1 -0
- package/dist/parity/fixtures.d.ts +45 -0
- package/dist/parity/fixtures.d.ts.map +1 -0
- package/dist/parity/fixtures.js +157 -0
- package/dist/parity/fixtures.js.map +1 -0
- package/dist/parity/index.d.ts +12 -0
- package/dist/parity/index.d.ts.map +1 -0
- package/dist/parity/index.js +12 -0
- package/dist/parity/index.js.map +1 -0
- package/dist/query/aggregations.d.ts +393 -0
- package/dist/query/aggregations.d.ts.map +1 -0
- package/dist/query/aggregations.js +1412 -0
- package/dist/query/aggregations.js.map +1 -0
- package/dist/query/dialect.d.ts +100 -0
- package/dist/query/dialect.d.ts.map +1 -0
- package/dist/query/dialect.js +88 -0
- package/dist/query/dialect.js.map +1 -0
- package/dist/query/duckdbDialect.d.ts +27 -0
- package/dist/query/duckdbDialect.d.ts.map +1 -0
- package/dist/query/duckdbDialect.js +83 -0
- package/dist/query/duckdbDialect.js.map +1 -0
- package/dist/query/index.d.ts +22 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +30 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/types.d.ts +388 -0
- package/dist/query/types.d.ts.map +1 -0
- package/dist/query/types.js +11 -0
- package/dist/query/types.js.map +1 -0
- package/llms.txt +21 -0
- package/package.json +71 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordered, forward-only DuckDB migrations for the OSS single-file store
|
|
3
|
+
* (ADR 0020 / ADR 0007). Append new statements; never edit a shipped one. All
|
|
4
|
+
* statements are idempotent (`IF NOT EXISTS` / `CREATE OR REPLACE VIEW`), so
|
|
5
|
+
* {@link migrateDuckdb} is safe to run on every boot.
|
|
6
|
+
*
|
|
7
|
+
* This store is **single-tenant**: there is no `org_id` and no tenant isolation
|
|
8
|
+
* (those live only in the proprietary scale layer). Events and metadata share
|
|
9
|
+
* one file so a self-hosted collector needs no external services.
|
|
10
|
+
*/
|
|
11
|
+
export const DUCKDB_MIGRATIONS = [
|
|
12
|
+
// --- Events ---------------------------------------------------------------
|
|
13
|
+
// Hot, queryable fields are promoted to columns (mirroring the ClickHouse
|
|
14
|
+
// `events` table); the full validated event is preserved in `payload` (JSON)
|
|
15
|
+
// so reads stay replay-complete. Vectors are `DOUBLE[]`; `ts` is a naive-UTC
|
|
16
|
+
// TIMESTAMP (epoch handling matches the ClickHouse store).
|
|
17
|
+
{
|
|
18
|
+
id: "0001_events",
|
|
19
|
+
sql: /* sql */ `
|
|
20
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
21
|
+
project_id VARCHAR NOT NULL,
|
|
22
|
+
session_id VARCHAR NOT NULL,
|
|
23
|
+
visitor_id VARCHAR NOT NULL DEFAULT '',
|
|
24
|
+
event_type VARCHAR NOT NULL,
|
|
25
|
+
ts TIMESTAMP NOT NULL,
|
|
26
|
+
sdk_version VARCHAR NOT NULL DEFAULT '',
|
|
27
|
+
url VARCHAR NOT NULL DEFAULT '',
|
|
28
|
+
scene_id VARCHAR NOT NULL DEFAULT 'default',
|
|
29
|
+
source VARCHAR NOT NULL DEFAULT 'mouse',
|
|
30
|
+
handedness VARCHAR NOT NULL DEFAULT '',
|
|
31
|
+
source_id VARCHAR NOT NULL DEFAULT '',
|
|
32
|
+
ray_origin DOUBLE[],
|
|
33
|
+
ray_direction DOUBLE[],
|
|
34
|
+
position DOUBLE[],
|
|
35
|
+
direction DOUBLE[],
|
|
36
|
+
hit_point DOUBLE[],
|
|
37
|
+
screen DOUBLE[],
|
|
38
|
+
mesh VARCHAR NOT NULL DEFAULT '',
|
|
39
|
+
fps DOUBLE NOT NULL DEFAULT 0,
|
|
40
|
+
name VARCHAR NOT NULL DEFAULT '',
|
|
41
|
+
payload VARCHAR NOT NULL,
|
|
42
|
+
inserted_at TIMESTAMP NOT NULL DEFAULT now()
|
|
43
|
+
);
|
|
44
|
+
`,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "0002_events_idx",
|
|
48
|
+
sql: /* sql */ `
|
|
49
|
+
CREATE INDEX IF NOT EXISTS events_project_type_ts_idx
|
|
50
|
+
ON events (project_id, event_type, ts);
|
|
51
|
+
`,
|
|
52
|
+
},
|
|
53
|
+
// --- Metadata (re-homed from Postgres for the single-file store) -----------
|
|
54
|
+
// Single-tenant: `projects` has no `org_id`. API keys are stored as SHA-256
|
|
55
|
+
// hashes (never plaintext), consistent with the Postgres store.
|
|
56
|
+
{
|
|
57
|
+
id: "0003_projects",
|
|
58
|
+
sql: /* sql */ `
|
|
59
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
60
|
+
id VARCHAR PRIMARY KEY,
|
|
61
|
+
name VARCHAR NOT NULL,
|
|
62
|
+
created_at TIMESTAMP NOT NULL DEFAULT now()
|
|
63
|
+
);
|
|
64
|
+
`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "0004_api_keys",
|
|
68
|
+
sql: /* sql */ `
|
|
69
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
70
|
+
id VARCHAR PRIMARY KEY,
|
|
71
|
+
project_id VARCHAR NOT NULL,
|
|
72
|
+
key_hash VARCHAR NOT NULL UNIQUE,
|
|
73
|
+
key_prefix VARCHAR NOT NULL,
|
|
74
|
+
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
|
75
|
+
revoked_at TIMESTAMP
|
|
76
|
+
);
|
|
77
|
+
`,
|
|
78
|
+
},
|
|
79
|
+
// One representation per (project, scene): a developer-supplied label plus an
|
|
80
|
+
// optional engine-agnostic proxy (ADR 0010/0014). `bounds`/`proxy` are stored
|
|
81
|
+
// as JSON text (parsed by the row mapper).
|
|
82
|
+
{
|
|
83
|
+
id: "0005_scene_representations",
|
|
84
|
+
sql: /* sql */ `
|
|
85
|
+
CREATE TABLE IF NOT EXISTS scene_representations (
|
|
86
|
+
project_id VARCHAR NOT NULL,
|
|
87
|
+
scene_id VARCHAR NOT NULL,
|
|
88
|
+
label VARCHAR,
|
|
89
|
+
kind VARCHAR NOT NULL DEFAULT 'none',
|
|
90
|
+
up_axis VARCHAR NOT NULL DEFAULT 'y',
|
|
91
|
+
unit_scale DOUBLE NOT NULL DEFAULT 1,
|
|
92
|
+
bounds VARCHAR,
|
|
93
|
+
proxy VARCHAR,
|
|
94
|
+
asset_url VARCHAR,
|
|
95
|
+
content_hash VARCHAR,
|
|
96
|
+
proxy_version INTEGER,
|
|
97
|
+
captured_at TIMESTAMP,
|
|
98
|
+
updated_at TIMESTAMP NOT NULL DEFAULT now(),
|
|
99
|
+
PRIMARY KEY (project_id, scene_id)
|
|
100
|
+
);
|
|
101
|
+
`,
|
|
102
|
+
},
|
|
103
|
+
// --- Rollup views ---------------------------------------------------------
|
|
104
|
+
// DuckDB has no incremental materialized views, so the daily rollups read by
|
|
105
|
+
// `buildPerfDaily`/`buildEventsDaily` are plain views that pre-group by
|
|
106
|
+
// `(project_id, …, day)`. Column names match the ClickHouse rollup tables so
|
|
107
|
+
// the shared read queries work unchanged; each read GROUP BY then sees exactly
|
|
108
|
+
// one source row per group (its `-Merge` combinators pass the value through).
|
|
109
|
+
{
|
|
110
|
+
id: "0006_perf_daily_view",
|
|
111
|
+
sql: /* sql */ `
|
|
112
|
+
CREATE OR REPLACE VIEW perf_daily AS
|
|
113
|
+
SELECT
|
|
114
|
+
project_id,
|
|
115
|
+
CAST(ts AS DATE) AS day,
|
|
116
|
+
count(*) AS samples_state,
|
|
117
|
+
avg(fps) AS avg_fps_state,
|
|
118
|
+
min(fps) AS min_fps,
|
|
119
|
+
quantile_cont(fps, 0.5) AS p50_fps_state
|
|
120
|
+
FROM events
|
|
121
|
+
WHERE event_type = 'frame_perf'
|
|
122
|
+
GROUP BY project_id, day;
|
|
123
|
+
`,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "0007_events_daily_view",
|
|
127
|
+
sql: /* sql */ `
|
|
128
|
+
CREATE OR REPLACE VIEW events_daily AS
|
|
129
|
+
SELECT
|
|
130
|
+
project_id,
|
|
131
|
+
event_type,
|
|
132
|
+
CAST(ts AS DATE) AS day,
|
|
133
|
+
count(*) AS events
|
|
134
|
+
FROM events
|
|
135
|
+
GROUP BY project_id, event_type, day;
|
|
136
|
+
`,
|
|
137
|
+
},
|
|
138
|
+
// --- mesh_visibility dwell metrics (#37) ----------------------------------
|
|
139
|
+
// Promote the per-object attention fields to columns for the dwell
|
|
140
|
+
// aggregation (mirroring `fps` for `frame_perf`). The appender always writes a
|
|
141
|
+
// value (0 on non-visibility events); the full event stays in `payload`.
|
|
142
|
+
// Forward-only, additive, idempotent. No DEFAULT/NOT NULL: DuckDB's ALTER ADD
|
|
143
|
+
// COLUMN rejects NOT NULL and a stored DEFAULT trips a reopen crash, so the
|
|
144
|
+
// columns are plain nullable DOUBLE (SUM/MAX ignore the NULLs on old rows).
|
|
145
|
+
{
|
|
146
|
+
id: "0008_events_visible_ms",
|
|
147
|
+
sql: /* sql */ `
|
|
148
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS visible_ms DOUBLE;
|
|
149
|
+
`,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: "0009_events_centered_ms",
|
|
153
|
+
sql: /* sql */ `
|
|
154
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS centered_ms DOUBLE;
|
|
155
|
+
`,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "0010_events_screen_fraction",
|
|
159
|
+
sql: /* sql */ `
|
|
160
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS screen_fraction DOUBLE;
|
|
161
|
+
`,
|
|
162
|
+
},
|
|
163
|
+
// resource_sample (#44) GPU / memory footprint metrics. Same additive,
|
|
164
|
+
// nullable-DOUBLE pattern: old rows read NULL, which SUM/AVG/MAX ignore.
|
|
165
|
+
{
|
|
166
|
+
id: "0011_events_texture_bytes",
|
|
167
|
+
sql: /* sql */ `
|
|
168
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS texture_bytes DOUBLE;
|
|
169
|
+
`,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "0012_events_geometry_bytes",
|
|
173
|
+
sql: /* sql */ `
|
|
174
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS geometry_bytes DOUBLE;
|
|
175
|
+
`,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "0013_events_triangles",
|
|
179
|
+
sql: /* sql */ `
|
|
180
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS triangles DOUBLE;
|
|
181
|
+
`,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "0014_events_vertices",
|
|
185
|
+
sql: /* sql */ `
|
|
186
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS vertices DOUBLE;
|
|
187
|
+
`,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "0015_events_js_heap_bytes",
|
|
191
|
+
sql: /* sql */ `
|
|
192
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS js_heap_bytes DOUBLE;
|
|
193
|
+
`,
|
|
194
|
+
},
|
|
195
|
+
// capability_change (#49) fallback/recovery transition. `kind` reuses the
|
|
196
|
+
// shared `name` column (same precedent as compile_stall's phase); the
|
|
197
|
+
// from/to capability tokens get their own additive, nullable VARCHAR columns.
|
|
198
|
+
// `reason` stays in the JSON payload only (free-form, not a group key).
|
|
199
|
+
{
|
|
200
|
+
id: "0016_events_cap_from",
|
|
201
|
+
sql: /* sql */ `
|
|
202
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS cap_from VARCHAR;
|
|
203
|
+
`,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "0017_events_cap_to",
|
|
207
|
+
sql: /* sql */ `
|
|
208
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS cap_to VARCHAR;
|
|
209
|
+
`,
|
|
210
|
+
},
|
|
211
|
+
// --- Scene-actor transforms (node_transform, ADR 0027) --------------------
|
|
212
|
+
// `node_transform` is the highest-cardinality signal (actors × rate, × bones
|
|
213
|
+
// for Tier 2), so it gets its own transform-shaped table instead of bloating
|
|
214
|
+
// the wide `events` table with quaternion/bone columns that are null for every
|
|
215
|
+
// other type. Rows are reconstructed back into replay-complete `node_transform`
|
|
216
|
+
// events on read and merged with the `events` stream by `ts` (ADR 0027 §8/§9).
|
|
217
|
+
// Ordered by (project, session, node_id, ts) to mirror the ClickHouse scale-tier
|
|
218
|
+
// sort key and give cheap per-actor reads. `bone_id` is '' for the Tier-1
|
|
219
|
+
// node/root tier; `scale` is empty when it never left identity.
|
|
220
|
+
{
|
|
221
|
+
id: "0018_node_samples",
|
|
222
|
+
sql: /* sql */ `
|
|
223
|
+
CREATE TABLE IF NOT EXISTS node_samples (
|
|
224
|
+
project_id VARCHAR NOT NULL,
|
|
225
|
+
session_id VARCHAR NOT NULL,
|
|
226
|
+
ts TIMESTAMP NOT NULL,
|
|
227
|
+
sdk_version VARCHAR NOT NULL DEFAULT '',
|
|
228
|
+
scene_id VARCHAR NOT NULL DEFAULT 'default',
|
|
229
|
+
node_id VARCHAR NOT NULL,
|
|
230
|
+
bone_id VARCHAR NOT NULL DEFAULT '',
|
|
231
|
+
position DOUBLE[],
|
|
232
|
+
rotation DOUBLE[],
|
|
233
|
+
scale DOUBLE[],
|
|
234
|
+
inserted_at TIMESTAMP NOT NULL DEFAULT now()
|
|
235
|
+
);
|
|
236
|
+
`,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: "0019_node_samples_idx",
|
|
240
|
+
sql: /* sql */ `
|
|
241
|
+
CREATE INDEX IF NOT EXISTS node_samples_session_node_ts_idx
|
|
242
|
+
ON node_samples (project_id, session_id, node_id, ts);
|
|
243
|
+
`,
|
|
244
|
+
},
|
|
245
|
+
// --- frame_perf percentile / jank / resolution detail (#80) ---------------
|
|
246
|
+
// Promote the percentile, jank, and resolution fields so the per-session perf
|
|
247
|
+
// aggregations (ADR 0028) read columns instead of JSON. Same additive,
|
|
248
|
+
// nullable-DOUBLE pattern as the resource columns: old rows read NULL (ignored
|
|
249
|
+
// by SUM/AVG/MAX); new non-frame_perf rows read 0 (filtered out by event_type).
|
|
250
|
+
{
|
|
251
|
+
id: "0020_events_frame_time_ms",
|
|
252
|
+
sql: /* sql */ `
|
|
253
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS frame_time_ms DOUBLE;
|
|
254
|
+
`,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: "0021_events_frame_time_p95_ms",
|
|
258
|
+
sql: /* sql */ `
|
|
259
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS frame_time_p95_ms DOUBLE;
|
|
260
|
+
`,
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: "0022_events_long_frames",
|
|
264
|
+
sql: /* sql */ `
|
|
265
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS long_frames DOUBLE;
|
|
266
|
+
`,
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
id: "0023_events_dpr",
|
|
270
|
+
sql: /* sql */ `
|
|
271
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS dpr DOUBLE;
|
|
272
|
+
`,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: "0024_events_render_scale",
|
|
276
|
+
sql: /* sql */ `
|
|
277
|
+
ALTER TABLE events ADD COLUMN IF NOT EXISTS render_scale DOUBLE;
|
|
278
|
+
`,
|
|
279
|
+
},
|
|
280
|
+
// --- node_transform Tier-1 subtree children (ADR 0033) --------------------
|
|
281
|
+
// `child_path` is the descendant's engine node path relative to `node_id`
|
|
282
|
+
// (e.g. 'Body/Arm_L/Hand'); '' for the declared root and for Tier-2 bone rows.
|
|
283
|
+
// Added after `inserted_at` (physically last), so the appender binds it last.
|
|
284
|
+
// DuckDB's `ALTER TABLE ADD COLUMN` rejects NOT NULL/DEFAULT constraints, so
|
|
285
|
+
// the column is plain nullable VARCHAR; the appender always writes '' (never
|
|
286
|
+
// null) for new rows and the reader treats any falsy value as "no childPath".
|
|
287
|
+
{
|
|
288
|
+
id: "0025_node_samples_child_path",
|
|
289
|
+
sql: /* sql */ `
|
|
290
|
+
ALTER TABLE node_samples ADD COLUMN IF NOT EXISTS child_path VARCHAR;
|
|
291
|
+
`,
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "0026_node_samples_child_idx",
|
|
295
|
+
sql: /* sql */ `
|
|
296
|
+
CREATE INDEX IF NOT EXISTS node_samples_session_node_child_ts_idx
|
|
297
|
+
ON node_samples (project_id, session_id, node_id, child_path, ts);
|
|
298
|
+
`,
|
|
299
|
+
},
|
|
300
|
+
// API-key capability scope (`ingest` | `query`). Public ingestion is keyless,
|
|
301
|
+
// so issued keys are for reads — existing keys are grandfathered to `query`
|
|
302
|
+
// and the column default is `query`. Enforced at the read boundaries
|
|
303
|
+
// (query + live token exchange).
|
|
304
|
+
{
|
|
305
|
+
id: "0027_api_keys_capability",
|
|
306
|
+
sql: /* sql */ `
|
|
307
|
+
ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS capability VARCHAR DEFAULT 'query';
|
|
308
|
+
`,
|
|
309
|
+
},
|
|
310
|
+
];
|
|
311
|
+
/**
|
|
312
|
+
* Apply all DuckDB migrations in order. Idempotent — safe to run on every boot.
|
|
313
|
+
*/
|
|
314
|
+
export async function migrateDuckdb(client) {
|
|
315
|
+
for (const migration of DUCKDB_MIGRATIONS) {
|
|
316
|
+
await client.run(migration.sql);
|
|
317
|
+
}
|
|
318
|
+
// Flush the WAL into the main file. `ALTER TABLE ... ADD COLUMN` entries crash
|
|
319
|
+
// DuckDB's WAL replay on the next open, so checkpointing here keeps a
|
|
320
|
+
// file-backed store reopenable after a schema change. No-op for `:memory:`.
|
|
321
|
+
await client.run("CHECKPOINT;");
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/duckdb/migrations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { apiKeyPrefix, generateApiKey, hashApiKey, type ApiKeyCapability, type ApiKeyRecord, type Project, type ResolvedApiKey } from "../metadata.js";
|
|
2
|
+
import type { DuckdbClient } from "./client.js";
|
|
3
|
+
export type { Project, ApiKeyRecord };
|
|
4
|
+
export { hashApiKey, apiKeyPrefix, generateApiKey };
|
|
5
|
+
/** Create a project and return it. */
|
|
6
|
+
export declare function createProject(client: DuckdbClient, name: string): Promise<Project>;
|
|
7
|
+
/** Fetch a project by id, or `null` if it does not exist. */
|
|
8
|
+
export declare function getProject(client: DuckdbClient, id: string): Promise<Project | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Issue a new API key for a project. Returns both the record and the plaintext
|
|
11
|
+
* key — the plaintext is shown to the caller exactly once and never stored.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createApiKey(client: DuckdbClient, projectId: string, capability?: ApiKeyCapability): Promise<{
|
|
14
|
+
key: string;
|
|
15
|
+
record: ApiKeyRecord;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a plaintext API key to its (non-revoked) project id and capability, or
|
|
19
|
+
* `null` when the key is unknown or revoked. The collector uses this to
|
|
20
|
+
* authenticate and scope read requests at the boundary.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveApiKey(client: DuckdbClient, plaintext: string): Promise<ResolvedApiKey | null>;
|
|
23
|
+
//# sourceMappingURL=projects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/duckdb/projects.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,cAAc,EACd,UAAU,EACV,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC;AAiBpD,sCAAsC;AACtC,wBAAsB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASxF;AAED,6DAA6D;AAC7D,wBAAsB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAS1F;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,UAAU,GAAE,gBAA0B,GACrC,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC,CAiChD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAOhC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { apiKeyPrefix, generateApiKey, hashApiKey, } from "../metadata.js";
|
|
3
|
+
export { hashApiKey, apiKeyPrefix, generateApiKey };
|
|
4
|
+
/** Create a project and return it. */
|
|
5
|
+
export async function createProject(client, name) {
|
|
6
|
+
const id = randomUUID();
|
|
7
|
+
await client.run(`INSERT INTO projects (id, name) VALUES ($id, $name)`, { id, name });
|
|
8
|
+
const rows = await client.all(`SELECT id, name, epoch_ms(created_at) AS created_at_ms FROM projects WHERE id = $id`, { id });
|
|
9
|
+
const row = rows[0];
|
|
10
|
+
return { id: row.id, name: row.name, orgId: null, createdAt: new Date(row.created_at_ms) };
|
|
11
|
+
}
|
|
12
|
+
/** Fetch a project by id, or `null` if it does not exist. */
|
|
13
|
+
export async function getProject(client, id) {
|
|
14
|
+
const rows = await client.all(`SELECT id, name, epoch_ms(created_at) AS created_at_ms FROM projects WHERE id = $id`, { id });
|
|
15
|
+
const row = rows[0];
|
|
16
|
+
return row
|
|
17
|
+
? { id: row.id, name: row.name, orgId: null, createdAt: new Date(row.created_at_ms) }
|
|
18
|
+
: null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Issue a new API key for a project. Returns both the record and the plaintext
|
|
22
|
+
* key — the plaintext is shown to the caller exactly once and never stored.
|
|
23
|
+
*/
|
|
24
|
+
export async function createApiKey(client, projectId, capability = "query") {
|
|
25
|
+
const key = generateApiKey();
|
|
26
|
+
const id = randomUUID();
|
|
27
|
+
await client.run(`INSERT INTO api_keys (id, project_id, key_hash, key_prefix, capability)
|
|
28
|
+
VALUES ($id, $projectId, $keyHash, $keyPrefix, $capability)`, { id, projectId, keyHash: hashApiKey(key), keyPrefix: apiKeyPrefix(key), capability });
|
|
29
|
+
const rows = await client.all(`SELECT id, project_id, key_prefix, epoch_ms(created_at) AS created_at_ms,
|
|
30
|
+
epoch_ms(revoked_at) AS revoked_at_ms, capability
|
|
31
|
+
FROM api_keys WHERE id = $id`, { id });
|
|
32
|
+
const row = rows[0];
|
|
33
|
+
return {
|
|
34
|
+
key,
|
|
35
|
+
record: {
|
|
36
|
+
id: row.id,
|
|
37
|
+
projectId: row.project_id,
|
|
38
|
+
keyPrefix: row.key_prefix,
|
|
39
|
+
createdAt: new Date(row.created_at_ms),
|
|
40
|
+
revokedAt: row.revoked_at_ms === null ? null : new Date(row.revoked_at_ms),
|
|
41
|
+
capability: row.capability,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a plaintext API key to its (non-revoked) project id and capability, or
|
|
47
|
+
* `null` when the key is unknown or revoked. The collector uses this to
|
|
48
|
+
* authenticate and scope read requests at the boundary.
|
|
49
|
+
*/
|
|
50
|
+
export async function resolveApiKey(client, plaintext) {
|
|
51
|
+
const rows = await client.all(`SELECT project_id, capability FROM api_keys WHERE key_hash = $keyHash AND revoked_at IS NULL`, { keyHash: hashApiKey(plaintext) });
|
|
52
|
+
const row = rows[0];
|
|
53
|
+
return row ? { projectId: row.project_id, capability: row.capability } : null;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../../src/duckdb/projects.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { QuerySpec } from "../query/types.js";
|
|
2
|
+
import type { DuckdbClient } from "./client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Execute a dialect-agnostic {@link QuerySpec} (rendered with `duckdbDialect`)
|
|
5
|
+
* against the DuckDB store and return typed rows. Values are already converted to
|
|
6
|
+
* plain JS by the client (bigint→number, list→array, timestamp→string), so rows
|
|
7
|
+
* match the shapes produced by the ClickHouse `runQuery`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runDuckdbQuery<T>(client: DuckdbClient, spec: QuerySpec): Promise<T[]>;
|
|
10
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/duckdb/queries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAE3F"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute a dialect-agnostic {@link QuerySpec} (rendered with `duckdbDialect`)
|
|
3
|
+
* against the DuckDB store and return typed rows. Values are already converted to
|
|
4
|
+
* plain JS by the client (bigint→number, list→array, timestamp→string), so rows
|
|
5
|
+
* match the shapes produced by the ClickHouse `runQuery`.
|
|
6
|
+
*/
|
|
7
|
+
export async function runDuckdbQuery(client, spec) {
|
|
8
|
+
return client.all(spec.query, spec.query_params);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/duckdb/queries.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAI,MAAoB,EAAE,IAAe;IAC3E,OAAO,MAAM,CAAC,GAAG,CAAI,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,YAAuC,CAAC,CAAC;AACjF,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SceneProxy } from "@uptimizr/schema";
|
|
2
|
+
import type { SceneRepresentation, SceneRepresentationKind, SceneRepresentationSummary } from "../metadata.js";
|
|
3
|
+
import type { DuckdbClient } from "./client.js";
|
|
4
|
+
export type { SceneRepresentation, SceneRepresentationKind, SceneRepresentationSummary };
|
|
5
|
+
/**
|
|
6
|
+
* Insert or replace the scene proxy for `(projectId, proxy.sceneId)`. Stores the
|
|
7
|
+
* full proxy as JSON text and promotes bounds/hash/version/captured-at to columns
|
|
8
|
+
* for cheap listing. An optional `label` is merged in (kept if not provided).
|
|
9
|
+
*/
|
|
10
|
+
export declare function upsertSceneProxy(client: DuckdbClient, projectId: string, proxy: SceneProxy, label?: string): Promise<SceneRepresentation>;
|
|
11
|
+
/** Fetch one scene representation (including the proxy blob), or `null`. */
|
|
12
|
+
export declare function getSceneRepresentation(client: DuckdbClient, projectId: string, sceneId: string): Promise<SceneRepresentation | null>;
|
|
13
|
+
/** List a project's scene representations (without proxy blobs), newest first. */
|
|
14
|
+
export declare function listSceneRepresentations(client: DuckdbClient, projectId: string): Promise<SceneRepresentationSummary[]>;
|
|
15
|
+
//# sourceMappingURL=sceneRegistry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sceneRegistry.d.ts","sourceRoot":"","sources":["../../src/duckdb/sceneRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,EAC3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,CAAC;AA4DzF;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,CAAC,CAkC9B;AAED,4EAA4E;AAC5E,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAWrC;AAED,kFAAkF;AAClF,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,0BAA0B,EAAE,CAAC,CAmBvC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { toDuckdbTimestamp } from "../query/duckdbDialect.js";
|
|
2
|
+
function toKind(value) {
|
|
3
|
+
return value === "proxy" || value === "asset" ? value : "none";
|
|
4
|
+
}
|
|
5
|
+
function toUpAxis(value) {
|
|
6
|
+
return value === "z" ? "z" : "y";
|
|
7
|
+
}
|
|
8
|
+
function parseJson(value) {
|
|
9
|
+
if (value == null)
|
|
10
|
+
return null;
|
|
11
|
+
return JSON.parse(value);
|
|
12
|
+
}
|
|
13
|
+
function rowToRepresentation(row) {
|
|
14
|
+
return {
|
|
15
|
+
projectId: row.project_id,
|
|
16
|
+
sceneId: row.scene_id,
|
|
17
|
+
label: row.label,
|
|
18
|
+
kind: toKind(row.kind),
|
|
19
|
+
upAxis: toUpAxis(row.up_axis),
|
|
20
|
+
unitScale: Number(row.unit_scale),
|
|
21
|
+
bounds: parseJson(row.bounds),
|
|
22
|
+
proxy: parseJson(row.proxy),
|
|
23
|
+
assetUrl: row.asset_url,
|
|
24
|
+
contentHash: row.content_hash,
|
|
25
|
+
proxyVersion: row.proxy_version === null ? null : Number(row.proxy_version),
|
|
26
|
+
capturedAt: row.captured_at_ms === null ? null : new Date(row.captured_at_ms),
|
|
27
|
+
updatedAt: new Date(row.updated_at_ms),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const RETURNING = `RETURNING project_id, scene_id, label, kind, up_axis, unit_scale, bounds, proxy,
|
|
31
|
+
asset_url, content_hash, proxy_version,
|
|
32
|
+
epoch_ms(captured_at) AS captured_at_ms, epoch_ms(updated_at) AS updated_at_ms`;
|
|
33
|
+
/**
|
|
34
|
+
* Insert or replace the scene proxy for `(projectId, proxy.sceneId)`. Stores the
|
|
35
|
+
* full proxy as JSON text and promotes bounds/hash/version/captured-at to columns
|
|
36
|
+
* for cheap listing. An optional `label` is merged in (kept if not provided).
|
|
37
|
+
*/
|
|
38
|
+
export async function upsertSceneProxy(client, projectId, proxy, label) {
|
|
39
|
+
const rows = await client.all(`INSERT INTO scene_representations
|
|
40
|
+
(project_id, scene_id, label, kind, up_axis, unit_scale, bounds, proxy,
|
|
41
|
+
asset_url, content_hash, proxy_version, captured_at, updated_at)
|
|
42
|
+
VALUES ($projectId, $sceneId, $label, 'proxy', $upAxis, $unitScale, $bounds, $proxy,
|
|
43
|
+
NULL, $contentHash, $proxyVersion, $capturedAt::TIMESTAMP, now())
|
|
44
|
+
ON CONFLICT (project_id, scene_id) DO UPDATE SET
|
|
45
|
+
label = COALESCE(EXCLUDED.label, scene_representations.label),
|
|
46
|
+
kind = 'proxy',
|
|
47
|
+
up_axis = EXCLUDED.up_axis,
|
|
48
|
+
unit_scale = EXCLUDED.unit_scale,
|
|
49
|
+
bounds = EXCLUDED.bounds,
|
|
50
|
+
proxy = EXCLUDED.proxy,
|
|
51
|
+
asset_url = NULL,
|
|
52
|
+
content_hash = EXCLUDED.content_hash,
|
|
53
|
+
proxy_version = EXCLUDED.proxy_version,
|
|
54
|
+
captured_at = EXCLUDED.captured_at,
|
|
55
|
+
updated_at = now()
|
|
56
|
+
${RETURNING}`, {
|
|
57
|
+
projectId,
|
|
58
|
+
sceneId: proxy.sceneId,
|
|
59
|
+
label: label ?? null,
|
|
60
|
+
upAxis: proxy.upAxis,
|
|
61
|
+
unitScale: proxy.unitScale,
|
|
62
|
+
bounds: JSON.stringify(proxy.bounds),
|
|
63
|
+
proxy: JSON.stringify(proxy),
|
|
64
|
+
contentHash: proxy.contentHash,
|
|
65
|
+
proxyVersion: proxy.version,
|
|
66
|
+
capturedAt: toDuckdbTimestamp(proxy.capturedAt),
|
|
67
|
+
});
|
|
68
|
+
return rowToRepresentation(rows[0]);
|
|
69
|
+
}
|
|
70
|
+
/** Fetch one scene representation (including the proxy blob), or `null`. */
|
|
71
|
+
export async function getSceneRepresentation(client, projectId, sceneId) {
|
|
72
|
+
const rows = await client.all(`SELECT project_id, scene_id, label, kind, up_axis, unit_scale, bounds, proxy,
|
|
73
|
+
asset_url, content_hash, proxy_version,
|
|
74
|
+
epoch_ms(captured_at) AS captured_at_ms, epoch_ms(updated_at) AS updated_at_ms
|
|
75
|
+
FROM scene_representations
|
|
76
|
+
WHERE project_id = $projectId AND scene_id = $sceneId`, { projectId, sceneId });
|
|
77
|
+
const row = rows[0];
|
|
78
|
+
return row ? rowToRepresentation(row) : null;
|
|
79
|
+
}
|
|
80
|
+
/** List a project's scene representations (without proxy blobs), newest first. */
|
|
81
|
+
export async function listSceneRepresentations(client, projectId) {
|
|
82
|
+
const rows = await client.all(`SELECT scene_id, label, kind, up_axis, unit_scale, bounds, asset_url,
|
|
83
|
+
content_hash, proxy_version,
|
|
84
|
+
epoch_ms(captured_at) AS captured_at_ms, epoch_ms(updated_at) AS updated_at_ms
|
|
85
|
+
FROM scene_representations
|
|
86
|
+
WHERE project_id = $projectId
|
|
87
|
+
ORDER BY updated_at DESC`, { projectId });
|
|
88
|
+
return rows.map((row) => ({
|
|
89
|
+
sceneId: row.scene_id,
|
|
90
|
+
label: row.label,
|
|
91
|
+
kind: toKind(row.kind),
|
|
92
|
+
bounds: parseJson(row.bounds),
|
|
93
|
+
contentHash: row.content_hash,
|
|
94
|
+
capturedAt: row.captured_at_ms === null ? null : new Date(row.captured_at_ms),
|
|
95
|
+
updatedAt: new Date(row.updated_at_ms),
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=sceneRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sceneRegistry.js","sourceRoot":"","sources":["../../src/duckdb/sceneRegistry.ts"],"names":[],"mappings":"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"}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection settings for the OSS DuckDB store, read from the environment
|
|
3
|
+
* (ADR 0020).
|
|
4
|
+
*
|
|
5
|
+
* Names mirror `.env.example`. Everything has a local-dev default so the package
|
|
6
|
+
* is usable out of the box (a single persisted `.duckdb` file).
|
|
7
|
+
*/
|
|
8
|
+
/** DuckDB (OSS single-file store, ADR 0020) connection settings. */
|
|
9
|
+
export interface DuckdbSettings {
|
|
10
|
+
/** Path to the persisted `.duckdb` file, or `:memory:` for an ephemeral store. */
|
|
11
|
+
path: string;
|
|
12
|
+
}
|
|
13
|
+
export interface DbSettings {
|
|
14
|
+
duckdb: DuckdbSettings;
|
|
15
|
+
/** Opt-in raw per-session retention for replay (ADR 0003). */
|
|
16
|
+
enableRawSessionRetention: boolean;
|
|
17
|
+
}
|
|
18
|
+
type Env = Record<string, string | undefined>;
|
|
19
|
+
/**
|
|
20
|
+
* Build {@link DbSettings} from an env-like record (defaults to `process.env`).
|
|
21
|
+
* Passing an explicit record keeps this pure and testable.
|
|
22
|
+
*/
|
|
23
|
+
export declare function readDbSettings(env?: Env): DbSettings;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,cAAc,CAAC;IACvB,8DAA8D;IAC9D,yBAAyB,EAAE,OAAO,CAAC;CACpC;AAED,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAmC9C;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,GAAiB,GAAG,UAAU,CAOjE"}
|