@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
package/dist/cli/seed.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { createApiKey, createProject } from "../duckdb/projects.js";
|
|
4
|
+
import { createDuckdbClient } from "../duckdb/client.js";
|
|
5
|
+
import { migrateDuckdb } from "../duckdb/migrations.js";
|
|
6
|
+
import { readDbSettings } from "../env.js";
|
|
7
|
+
/**
|
|
8
|
+
* Walk up from a starting directory until a directory containing
|
|
9
|
+
* `pnpm-workspace.yaml` is found, i.e. the monorepo root. Returns `undefined`
|
|
10
|
+
* if no such directory exists.
|
|
11
|
+
*/
|
|
12
|
+
function findRepoRoot(start) {
|
|
13
|
+
let dir = start;
|
|
14
|
+
for (;;) {
|
|
15
|
+
if (existsSync(join(dir, "pnpm-workspace.yaml"))) {
|
|
16
|
+
return dir;
|
|
17
|
+
}
|
|
18
|
+
const parent = dirname(dir);
|
|
19
|
+
if (parent === dir) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
dir = parent;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Set (or append) a `KEY=value` line in a `.env` file body, preserving the rest
|
|
27
|
+
* of the file. Only the first occurrence of the key is updated.
|
|
28
|
+
*/
|
|
29
|
+
function setEnvVar(body, key, value) {
|
|
30
|
+
const line = `${key}=${value}`;
|
|
31
|
+
const pattern = new RegExp(`^${key}=.*$`, "m");
|
|
32
|
+
if (pattern.test(body)) {
|
|
33
|
+
return body.replace(pattern, line);
|
|
34
|
+
}
|
|
35
|
+
return body.endsWith("\n") || body === "" ? `${body}${line}\n` : `${body}\n${line}\n`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Write a set of `KEY=value` pairs into the root `.env` so the Babylon playground
|
|
39
|
+
* and dashboard pick them up automatically. No-op (with a hint) when no `.env`
|
|
40
|
+
* exists yet.
|
|
41
|
+
*/
|
|
42
|
+
function writeEnv(root, vars) {
|
|
43
|
+
const envPath = join(root, ".env");
|
|
44
|
+
if (!existsSync(envPath)) {
|
|
45
|
+
console.log(` (no .env at ${envPath} — copy .env.example to .env to auto-fill these)`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let body = readFileSync(envPath, "utf8");
|
|
49
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
50
|
+
body = setEnvVar(body, key, value);
|
|
51
|
+
}
|
|
52
|
+
writeFileSync(envPath, body);
|
|
53
|
+
console.log(`✓ wrote ${Object.keys(vars).join(", ")} to ${envPath}`);
|
|
54
|
+
}
|
|
55
|
+
/** The four built-in demo scenes (kept in sync with `examples/playground/scenes.json`). */
|
|
56
|
+
const LOBBY_SCENE = {
|
|
57
|
+
id: "lobby",
|
|
58
|
+
label: "Lobby (viewer)",
|
|
59
|
+
description: "An orbit / arc-rotate camera framing a model — how people inspect an object.",
|
|
60
|
+
cameraMode: "viewer",
|
|
61
|
+
engines: ["babylon", "babylon-lite", "three", "playcanvas", "r3f", "aframe"],
|
|
62
|
+
defaultEngine: "babylon",
|
|
63
|
+
builtin: true,
|
|
64
|
+
};
|
|
65
|
+
const ATRIUM_SCENE = {
|
|
66
|
+
id: "atrium",
|
|
67
|
+
label: "Atrium (first-person)",
|
|
68
|
+
description: "A walkable scene traversed with WASD + look — where people walk and what they approach.",
|
|
69
|
+
cameraMode: "first-person",
|
|
70
|
+
engines: ["babylon", "three", "playcanvas"],
|
|
71
|
+
defaultEngine: "babylon",
|
|
72
|
+
builtin: true,
|
|
73
|
+
};
|
|
74
|
+
const SHOWCASE_SCENE = {
|
|
75
|
+
id: "showcase",
|
|
76
|
+
label: "Showcase (real glTF viewer)",
|
|
77
|
+
description: "An orbit camera framing a real glTF model (Khronos ToyCar) — inspecting a detailed PBR asset.",
|
|
78
|
+
cameraMode: "viewer",
|
|
79
|
+
engines: ["babylon", "three", "playcanvas"],
|
|
80
|
+
defaultEngine: "babylon",
|
|
81
|
+
builtin: false,
|
|
82
|
+
};
|
|
83
|
+
const GALLERY_SCENE = {
|
|
84
|
+
id: "gallery",
|
|
85
|
+
label: "Gallery (walkable real models)",
|
|
86
|
+
description: "A first-person walkable room with real glTF models (Khronos ToyCar, Fox, GlamVelvetSofa) on pedestals — walk up and inspect or pick the exhibits.",
|
|
87
|
+
cameraMode: "first-person",
|
|
88
|
+
engines: ["babylon", "three", "playcanvas"],
|
|
89
|
+
defaultEngine: "babylon",
|
|
90
|
+
builtin: false,
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Upsert project entries into the local, gitignored registry the dashboard reads
|
|
94
|
+
* (`.uptimizr/projects.json`) so its project picker lists the seeded projects on
|
|
95
|
+
* first run without ever calling the collector.
|
|
96
|
+
*/
|
|
97
|
+
function writeRegistry(root, entries) {
|
|
98
|
+
const registryPath = join(root, ".uptimizr", "projects.json");
|
|
99
|
+
mkdirSync(dirname(registryPath), { recursive: true });
|
|
100
|
+
let list = [];
|
|
101
|
+
if (existsSync(registryPath)) {
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(readFileSync(registryPath, "utf8"));
|
|
104
|
+
if (Array.isArray(parsed))
|
|
105
|
+
list = parsed;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
list = [];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const ids = new Set(entries.map((e) => e.id));
|
|
112
|
+
// Also drop any prior entry bound to the same scene id, so re-seeding replaces a
|
|
113
|
+
// scene's project binding instead of stacking a duplicate card in the dashboard.
|
|
114
|
+
const sceneIds = new Set(entries.map((e) => e.scene?.id).filter((s) => typeof s === "string"));
|
|
115
|
+
const next = [
|
|
116
|
+
...list.filter((p) => p && !ids.has(p.id) && !(p.scene?.id != null && sceneIds.has(p.scene.id))),
|
|
117
|
+
...entries,
|
|
118
|
+
];
|
|
119
|
+
writeFileSync(registryPath, `${JSON.stringify(next, null, 2)}\n`);
|
|
120
|
+
console.log(`✓ recorded ${entries.length} project(s) in ${registryPath}`);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Seed the demo projects — a **viewer** (arc-rotate) and a **walkable**
|
|
124
|
+
* (first-person) project for the built-in lobby/atrium scenes, plus a
|
|
125
|
+
* **showcase** and **gallery** project for the real-glTF demo scenes — and issue
|
|
126
|
+
* an API key for each. One project per scene mirrors how a real deployment keeps
|
|
127
|
+
* distinct experiences separate, and lets the playground route each scene's
|
|
128
|
+
* sessions to its own project (ADR 0026). Keys are printed once (stored only as
|
|
129
|
+
* hashes). Run via `pnpm --filter @uptimizr/db seed -- "My Project"`.
|
|
130
|
+
*/
|
|
131
|
+
async function main() {
|
|
132
|
+
const args = process.argv.slice(2).filter((arg) => arg !== "--");
|
|
133
|
+
const baseName = args[0] ?? "Demo Project";
|
|
134
|
+
const db = await createDuckdbClient(readDbSettings().duckdb.path);
|
|
135
|
+
await migrateDuckdb(db);
|
|
136
|
+
const viewer = await createProject(db, `${baseName} (Viewer)`);
|
|
137
|
+
const viewerKey = (await createApiKey(db, viewer.id)).key;
|
|
138
|
+
const walkable = await createProject(db, `${baseName} (Walkable)`);
|
|
139
|
+
const walkableKey = (await createApiKey(db, walkable.id)).key;
|
|
140
|
+
const showcase = await createProject(db, `${baseName} (Showcase)`);
|
|
141
|
+
const showcaseKey = (await createApiKey(db, showcase.id)).key;
|
|
142
|
+
const gallery = await createProject(db, `${baseName} (Gallery)`);
|
|
143
|
+
const galleryKey = (await createApiKey(db, gallery.id)).key;
|
|
144
|
+
await db.close();
|
|
145
|
+
console.log(`✓ project created: ${viewer.id} (${viewer.name})`);
|
|
146
|
+
console.log(` API key (store securely, shown once): ${viewerKey}`);
|
|
147
|
+
console.log(`✓ project created: ${walkable.id} (${walkable.name})`);
|
|
148
|
+
console.log(` API key (store securely, shown once): ${walkableKey}`);
|
|
149
|
+
console.log(`✓ project created: ${showcase.id} (${showcase.name})`);
|
|
150
|
+
console.log(` API key (store securely, shown once): ${showcaseKey}`);
|
|
151
|
+
console.log(`✓ project created: ${gallery.id} (${gallery.name})`);
|
|
152
|
+
console.log(` API key (store securely, shown once): ${galleryKey}`);
|
|
153
|
+
const root = findRepoRoot(process.cwd());
|
|
154
|
+
if (!root)
|
|
155
|
+
return;
|
|
156
|
+
const createdAt = new Date().toISOString();
|
|
157
|
+
writeEnv(root, {
|
|
158
|
+
VITE_PROJECT_ID: viewer.id,
|
|
159
|
+
VITE_API_KEY: viewerKey,
|
|
160
|
+
NEXT_PUBLIC_API_KEY: viewerKey,
|
|
161
|
+
VITE_PROJECT_ID_WALKABLE: walkable.id,
|
|
162
|
+
VITE_API_KEY_WALKABLE: walkableKey,
|
|
163
|
+
});
|
|
164
|
+
writeRegistry(root, [
|
|
165
|
+
{ id: viewer.id, name: viewer.name, apiKey: viewerKey, createdAt, scene: LOBBY_SCENE },
|
|
166
|
+
{ id: walkable.id, name: walkable.name, apiKey: walkableKey, createdAt, scene: ATRIUM_SCENE },
|
|
167
|
+
{ id: showcase.id, name: showcase.name, apiKey: showcaseKey, createdAt, scene: SHOWCASE_SCENE },
|
|
168
|
+
{ id: gallery.id, name: gallery.name, apiKey: galleryKey, createdAt, scene: GALLERY_SCENE },
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
171
|
+
main().catch((err) => {
|
|
172
|
+
console.error(err);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
});
|
|
175
|
+
//# sourceMappingURL=seed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../../src/cli/seed.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DuckDB client — the OSS single-file store (ADR 0020).
|
|
3
|
+
*
|
|
4
|
+
* DuckDB is in-process and ACID, but a single connection is *not* safe for
|
|
5
|
+
* concurrent statements. We therefore expose one shared connection and serialize
|
|
6
|
+
* every operation through a promise chain ("single-writer discipline"). Reads and
|
|
7
|
+
* writes alike run one-at-a-time; for a self-hosted collector this is more than
|
|
8
|
+
* fast enough and removes an entire class of races.
|
|
9
|
+
*
|
|
10
|
+
* Values returned by the Neo client are post-processed by {@link convertValue}
|
|
11
|
+
* into plain JS so the rest of the codebase never sees DuckDB value wrappers.
|
|
12
|
+
*/
|
|
13
|
+
import { type DuckDBConnection } from "@duckdb/node-api";
|
|
14
|
+
/** A row of already-converted, plain-JS values. */
|
|
15
|
+
export type DuckdbRow = Record<string, unknown>;
|
|
16
|
+
export interface DuckdbClient {
|
|
17
|
+
/** Run a statement, ignoring any result set. */
|
|
18
|
+
run(sql: string, params?: Record<string, unknown>): Promise<void>;
|
|
19
|
+
/** Run a query and return all rows as plain JS objects. */
|
|
20
|
+
all<T = DuckdbRow>(sql: string, params?: Record<string, unknown>): Promise<T[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Acquire exclusive access to the underlying connection for a unit of work
|
|
23
|
+
* (e.g. a bulk append). No other client operation runs until `fn` settles.
|
|
24
|
+
*/
|
|
25
|
+
exclusive<T>(fn: (con: DuckDBConnection) => Promise<T>): Promise<T>;
|
|
26
|
+
/** Close the connection and release the instance. */
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Convert a single DuckDB Neo value into a plain JS value:
|
|
31
|
+
* - `bigint` (from BIGINT/COUNT) → `number`
|
|
32
|
+
* - list values (`{ items: [...] }`) → arrays (recursively converted)
|
|
33
|
+
* - timestamp/date values → naive-UTC string via `toString()`
|
|
34
|
+
* - everything else (string/number/boolean/null) passes through unchanged.
|
|
35
|
+
*/
|
|
36
|
+
export declare function convertValue(value: unknown): unknown;
|
|
37
|
+
/**
|
|
38
|
+
* Open (or create) a DuckDB store at `path`. Use `":memory:"` for tests.
|
|
39
|
+
* The returned client owns a single serialized connection.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createDuckdbClient(path: string): Promise<DuckdbClient>;
|
|
42
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/duckdb/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzE,mDAAmD;AACnD,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,2DAA2D;IAC3D,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAChF;;;OAGG;IACH,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,qDAAqD;IACrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAUpD;AAQD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CA8C5E"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DuckDB client — the OSS single-file store (ADR 0020).
|
|
3
|
+
*
|
|
4
|
+
* DuckDB is in-process and ACID, but a single connection is *not* safe for
|
|
5
|
+
* concurrent statements. We therefore expose one shared connection and serialize
|
|
6
|
+
* every operation through a promise chain ("single-writer discipline"). Reads and
|
|
7
|
+
* writes alike run one-at-a-time; for a self-hosted collector this is more than
|
|
8
|
+
* fast enough and removes an entire class of races.
|
|
9
|
+
*
|
|
10
|
+
* Values returned by the Neo client are post-processed by {@link convertValue}
|
|
11
|
+
* into plain JS so the rest of the codebase never sees DuckDB value wrappers.
|
|
12
|
+
*/
|
|
13
|
+
import { mkdir } from "node:fs/promises";
|
|
14
|
+
import { dirname } from "node:path";
|
|
15
|
+
import { DuckDBInstance } from "@duckdb/node-api";
|
|
16
|
+
/**
|
|
17
|
+
* Convert a single DuckDB Neo value into a plain JS value:
|
|
18
|
+
* - `bigint` (from BIGINT/COUNT) → `number`
|
|
19
|
+
* - list values (`{ items: [...] }`) → arrays (recursively converted)
|
|
20
|
+
* - timestamp/date values → naive-UTC string via `toString()`
|
|
21
|
+
* - everything else (string/number/boolean/null) passes through unchanged.
|
|
22
|
+
*/
|
|
23
|
+
export function convertValue(value) {
|
|
24
|
+
if (typeof value === "bigint")
|
|
25
|
+
return Number(value);
|
|
26
|
+
if (value == null || typeof value !== "object")
|
|
27
|
+
return value;
|
|
28
|
+
// DuckDBListValue exposes `.items`; arrays already plain.
|
|
29
|
+
if (Array.isArray(value))
|
|
30
|
+
return value.map(convertValue);
|
|
31
|
+
const obj = value;
|
|
32
|
+
if (Array.isArray(obj.items))
|
|
33
|
+
return obj.items.map(convertValue);
|
|
34
|
+
// DuckDBTimestampValue (`micros`) / DuckDBDateValue (`days`) → naive-UTC text.
|
|
35
|
+
if ("micros" in obj || "days" in obj)
|
|
36
|
+
return String(value);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function convertRow(row) {
|
|
40
|
+
const out = {};
|
|
41
|
+
for (const key of Object.keys(row))
|
|
42
|
+
out[key] = convertValue(row[key]);
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Open (or create) a DuckDB store at `path`. Use `":memory:"` for tests.
|
|
47
|
+
* The returned client owns a single serialized connection.
|
|
48
|
+
*/
|
|
49
|
+
export async function createDuckdbClient(path) {
|
|
50
|
+
// DuckDB opens/creates the file but not its parent directory; ensure it
|
|
51
|
+
// exists first so a fresh checkout (e.g. ./data/uptimizr.duckdb) doesn't fail
|
|
52
|
+
// with ENOENT. Skip for the in-memory store, which has no filesystem path.
|
|
53
|
+
if (path !== ":memory:") {
|
|
54
|
+
await mkdir(dirname(path), { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
const instance = await DuckDBInstance.create(path);
|
|
57
|
+
const connection = await instance.connect();
|
|
58
|
+
// Serialize every operation onto a single promise chain.
|
|
59
|
+
let tail = Promise.resolve();
|
|
60
|
+
function enqueue(fn) {
|
|
61
|
+
const result = tail.then(fn, fn);
|
|
62
|
+
// Keep the chain alive even if an op rejects.
|
|
63
|
+
tail = result.then(() => undefined, () => undefined);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
run(sql, params) {
|
|
68
|
+
return enqueue(async () => {
|
|
69
|
+
if (params)
|
|
70
|
+
await connection.run(sql, params);
|
|
71
|
+
else
|
|
72
|
+
await connection.run(sql);
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
all(sql, params) {
|
|
76
|
+
return enqueue(async () => {
|
|
77
|
+
const reader = params
|
|
78
|
+
? await connection.runAndReadAll(sql, params)
|
|
79
|
+
: await connection.runAndReadAll(sql);
|
|
80
|
+
return reader.getRowObjects().map(convertRow);
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
exclusive(fn) {
|
|
84
|
+
return enqueue(() => fn(connection));
|
|
85
|
+
},
|
|
86
|
+
close() {
|
|
87
|
+
return enqueue(async () => {
|
|
88
|
+
connection.closeSync();
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/duckdb/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type AnyEvent } from "@uptimizr/schema";
|
|
2
|
+
import type { DuckdbClient } from "./client.js";
|
|
3
|
+
import type { SessionMeta } from "../events.js";
|
|
4
|
+
export type { SessionMeta };
|
|
5
|
+
/**
|
|
6
|
+
* Batched insert of validated events via the DuckDB Appender — the bulk path
|
|
7
|
+
* that handles `DOUBLE[]` columns (including empty arrays) natively. Reuses the
|
|
8
|
+
* shared {@link toEventRow} mapper so the promoted columns match the ClickHouse
|
|
9
|
+
* store exactly; only `ts`/`inserted_at` are bound as native TIMESTAMP values.
|
|
10
|
+
*
|
|
11
|
+
* `node_transform` events (ADR 0027) are split out into the dedicated
|
|
12
|
+
* `node_samples` table rather than the wide `events` table; both writes happen
|
|
13
|
+
* under the same single-writer lock so a batch is never partially applied.
|
|
14
|
+
*
|
|
15
|
+
* Runs under the client's single-writer lock so the append is never interleaved
|
|
16
|
+
* with another statement.
|
|
17
|
+
*/
|
|
18
|
+
export declare function insertEvents(client: DuckdbClient, events: readonly AnyEvent[]): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Ordered read of a single session's events for replay/timeline. Returns
|
|
21
|
+
* fully-parsed, schema-validated events in `ts` order. Gated upstream by
|
|
22
|
+
* `ENABLE_RAW_SESSION_RETENTION` (ADR 0003) — this function does not enforce it.
|
|
23
|
+
*
|
|
24
|
+
* Merges the wide `events` table with the dedicated `node_samples` table
|
|
25
|
+
* (ADR 0027 §9) so scene-actor motion replays alongside the visitor's inputs.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getSessionEvents(client: DuckdbClient, projectId: string, sessionId: string): Promise<AnyEvent[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Streaming counterpart to {@link getSessionEvents}: yields one validated event
|
|
30
|
+
* at a time in `ts` order. For the self-hosted single-file store a session is
|
|
31
|
+
* read in order and yielded incrementally; malformed/invalid rows are skipped.
|
|
32
|
+
* This is the path behind the collector's NDJSON replay response (ADR 0015).
|
|
33
|
+
*
|
|
34
|
+
* Includes reconstructed `node_samples` (ADR 0027), merged into the wide-event
|
|
35
|
+
* stream by `ts` so the replay timeline stays complete and ordered.
|
|
36
|
+
*/
|
|
37
|
+
export declare function streamSessionEvents(client: DuckdbClient, projectId: string, sessionId: string): AsyncGenerator<AnyEvent>;
|
|
38
|
+
/**
|
|
39
|
+
* Read a session's stored metadata (`device`/`scene`/`user`) from its
|
|
40
|
+
* `session_start` event. Returns `null` when the session has no start event.
|
|
41
|
+
* Coarse descriptor — not gated by raw-session retention.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getSessionMeta(client: DuckdbClient, projectId: string, sessionId: string): Promise<SessionMeta | null>;
|
|
44
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/duckdb/events.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,QAAQ,EAA2B,MAAM,kBAAkB,CAAC;AAE1F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,YAAY,EAAE,WAAW,EAAE,CAAC;AAO5B;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,SAAS,QAAQ,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAuFf;AAuED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAcrB;AAED;;;;;;;;GAQG;AACH,wBAAuB,mBAAmB,CACxC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,cAAc,CAAC,QAAQ,CAAC,CAe1B;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAqB7B"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { DOUBLE, LIST, timestampValue } from "@duckdb/node-api";
|
|
2
|
+
import { anyEventSchema } from "@uptimizr/schema";
|
|
3
|
+
import { toEventRow, toNodeSampleRow, nodeSampleRowToEvent } from "../events.js";
|
|
4
|
+
/** Coerce an event's epoch-ms timestamp into a DuckDB TIMESTAMP value (UTC micros). */
|
|
5
|
+
function tsValue(epochMs) {
|
|
6
|
+
return timestampValue(BigInt(Math.trunc(epochMs)) * 1000n);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Batched insert of validated events via the DuckDB Appender — the bulk path
|
|
10
|
+
* that handles `DOUBLE[]` columns (including empty arrays) natively. Reuses the
|
|
11
|
+
* shared {@link toEventRow} mapper so the promoted columns match the ClickHouse
|
|
12
|
+
* store exactly; only `ts`/`inserted_at` are bound as native TIMESTAMP values.
|
|
13
|
+
*
|
|
14
|
+
* `node_transform` events (ADR 0027) are split out into the dedicated
|
|
15
|
+
* `node_samples` table rather than the wide `events` table; both writes happen
|
|
16
|
+
* under the same single-writer lock so a batch is never partially applied.
|
|
17
|
+
*
|
|
18
|
+
* Runs under the client's single-writer lock so the append is never interleaved
|
|
19
|
+
* with another statement.
|
|
20
|
+
*/
|
|
21
|
+
export async function insertEvents(client, events) {
|
|
22
|
+
if (events.length === 0)
|
|
23
|
+
return;
|
|
24
|
+
const wideEvents = events.filter((e) => e.type !== "node_transform");
|
|
25
|
+
const nodeSamples = events.filter((e) => e.type === "node_transform");
|
|
26
|
+
await client.exclusive(async (con) => {
|
|
27
|
+
const now = tsValue(Date.now());
|
|
28
|
+
if (wideEvents.length > 0) {
|
|
29
|
+
const appender = await con.createAppender("events");
|
|
30
|
+
for (const event of wideEvents) {
|
|
31
|
+
const row = toEventRow(event);
|
|
32
|
+
appender.appendVarchar(row.project_id);
|
|
33
|
+
appender.appendVarchar(row.session_id);
|
|
34
|
+
appender.appendVarchar(row.visitor_id);
|
|
35
|
+
appender.appendVarchar(row.event_type);
|
|
36
|
+
appender.appendTimestamp(tsValue(event.ts));
|
|
37
|
+
appender.appendVarchar(row.sdk_version);
|
|
38
|
+
appender.appendVarchar(row.url);
|
|
39
|
+
appender.appendVarchar(row.scene_id);
|
|
40
|
+
appender.appendVarchar(row.source);
|
|
41
|
+
appender.appendVarchar(row.handedness);
|
|
42
|
+
appender.appendVarchar(row.source_id);
|
|
43
|
+
appender.appendList(row.ray_origin, LIST(DOUBLE));
|
|
44
|
+
appender.appendList(row.ray_direction, LIST(DOUBLE));
|
|
45
|
+
appender.appendList(row.position, LIST(DOUBLE));
|
|
46
|
+
appender.appendList(row.direction, LIST(DOUBLE));
|
|
47
|
+
appender.appendList(row.hit_point, LIST(DOUBLE));
|
|
48
|
+
appender.appendList(row.screen, LIST(DOUBLE));
|
|
49
|
+
appender.appendVarchar(row.mesh);
|
|
50
|
+
appender.appendDouble(row.fps);
|
|
51
|
+
appender.appendVarchar(row.name);
|
|
52
|
+
appender.appendVarchar(row.payload);
|
|
53
|
+
appender.appendTimestamp(now);
|
|
54
|
+
// Columns added by later ALTER TABLE migrations are physically appended
|
|
55
|
+
// after `inserted_at`, so the mesh_visibility metrics (#37, migrations
|
|
56
|
+
// 0008–0010) are appended here, last, to match the table's column order.
|
|
57
|
+
appender.appendDouble(row.visible_ms);
|
|
58
|
+
appender.appendDouble(row.centered_ms);
|
|
59
|
+
appender.appendDouble(row.screen_fraction);
|
|
60
|
+
// resource_sample footprint columns (#44, migrations 0011–0015) follow,
|
|
61
|
+
// in table column order.
|
|
62
|
+
appender.appendDouble(row.texture_bytes);
|
|
63
|
+
appender.appendDouble(row.geometry_bytes);
|
|
64
|
+
appender.appendDouble(row.triangles);
|
|
65
|
+
appender.appendDouble(row.vertices);
|
|
66
|
+
appender.appendDouble(row.js_heap_bytes);
|
|
67
|
+
// capability_change tokens (#49, migrations 0016–0017) follow, in table
|
|
68
|
+
// column order. `kind` is carried by `name` above.
|
|
69
|
+
appender.appendVarchar(row.cap_from);
|
|
70
|
+
appender.appendVarchar(row.cap_to);
|
|
71
|
+
// frame_perf detail columns (#80, migrations 0020–0024) follow, in table
|
|
72
|
+
// column order. 0 on non-frame_perf rows (filtered out on read).
|
|
73
|
+
appender.appendDouble(row.frame_time_ms);
|
|
74
|
+
appender.appendDouble(row.frame_time_p95_ms);
|
|
75
|
+
appender.appendDouble(row.long_frames);
|
|
76
|
+
appender.appendDouble(row.dpr);
|
|
77
|
+
appender.appendDouble(row.render_scale);
|
|
78
|
+
appender.endRow();
|
|
79
|
+
}
|
|
80
|
+
appender.flushSync();
|
|
81
|
+
appender.closeSync();
|
|
82
|
+
}
|
|
83
|
+
if (nodeSamples.length > 0) {
|
|
84
|
+
// node_samples (ADR 0027 §9): transform-shaped table, column order matches
|
|
85
|
+
// migration 0018 — project, session, ts, sdk_version, scene_id, node_id,
|
|
86
|
+
// bone_id, position, rotation, scale, inserted_at. `child_path` (ADR 0033,
|
|
87
|
+
// migration 0025) is physically last, so it is appended after inserted_at.
|
|
88
|
+
const appender = await con.createAppender("node_samples");
|
|
89
|
+
for (const event of nodeSamples) {
|
|
90
|
+
const row = toNodeSampleRow(event);
|
|
91
|
+
appender.appendVarchar(row.project_id);
|
|
92
|
+
appender.appendVarchar(row.session_id);
|
|
93
|
+
appender.appendTimestamp(tsValue(event.ts));
|
|
94
|
+
appender.appendVarchar(row.sdk_version);
|
|
95
|
+
appender.appendVarchar(row.scene_id);
|
|
96
|
+
appender.appendVarchar(row.node_id);
|
|
97
|
+
appender.appendVarchar(row.bone_id);
|
|
98
|
+
appender.appendList(row.position, LIST(DOUBLE));
|
|
99
|
+
appender.appendList(row.rotation, LIST(DOUBLE));
|
|
100
|
+
appender.appendList(row.scale, LIST(DOUBLE));
|
|
101
|
+
appender.appendTimestamp(now);
|
|
102
|
+
appender.appendVarchar(row.child_path);
|
|
103
|
+
appender.endRow();
|
|
104
|
+
}
|
|
105
|
+
appender.flushSync();
|
|
106
|
+
appender.closeSync();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Read a session's stored `node_transform` samples (ADR 0027), reconstructed
|
|
112
|
+
* into replay-complete events in `ts` order. Split from {@link getSessionEvents}
|
|
113
|
+
* because they live in a dedicated table; the two ordered streams are merged by
|
|
114
|
+
* the callers below.
|
|
115
|
+
*/
|
|
116
|
+
async function readSessionNodeSamples(client, projectId, sessionId) {
|
|
117
|
+
const rows = await client.all(`SELECT epoch_ms(ts) AS ts_ms, sdk_version, scene_id, node_id, bone_id,
|
|
118
|
+
child_path, position, rotation, scale
|
|
119
|
+
FROM node_samples
|
|
120
|
+
WHERE project_id = $projectId AND session_id = $sessionId
|
|
121
|
+
ORDER BY ts ASC`, { projectId, sessionId });
|
|
122
|
+
return rows.map((row) => nodeSampleRowToEvent({
|
|
123
|
+
project_id: projectId,
|
|
124
|
+
session_id: sessionId,
|
|
125
|
+
sdk_version: row.sdk_version,
|
|
126
|
+
scene_id: row.scene_id,
|
|
127
|
+
node_id: row.node_id,
|
|
128
|
+
bone_id: row.bone_id,
|
|
129
|
+
child_path: row.child_path,
|
|
130
|
+
position: row.position ?? [],
|
|
131
|
+
rotation: row.rotation ?? [],
|
|
132
|
+
scale: row.scale ?? [],
|
|
133
|
+
}, row.ts_ms));
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Merge two `ts`-ordered event streams into one ascending stream. Stable on
|
|
137
|
+
* ties (wide events before node samples at the same `ts`), so replay sees a
|
|
138
|
+
* single ordered timeline (ADR 0027 §8).
|
|
139
|
+
*/
|
|
140
|
+
function mergeByTs(wide, nodes) {
|
|
141
|
+
const merged = [];
|
|
142
|
+
let i = 0;
|
|
143
|
+
let j = 0;
|
|
144
|
+
while (i < wide.length && j < nodes.length) {
|
|
145
|
+
if (nodes[j].ts < wide[i].ts)
|
|
146
|
+
merged.push(nodes[j++]);
|
|
147
|
+
else
|
|
148
|
+
merged.push(wide[i++]);
|
|
149
|
+
}
|
|
150
|
+
while (i < wide.length)
|
|
151
|
+
merged.push(wide[i++]);
|
|
152
|
+
while (j < nodes.length)
|
|
153
|
+
merged.push(nodes[j++]);
|
|
154
|
+
return merged;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Ordered read of a single session's events for replay/timeline. Returns
|
|
158
|
+
* fully-parsed, schema-validated events in `ts` order. Gated upstream by
|
|
159
|
+
* `ENABLE_RAW_SESSION_RETENTION` (ADR 0003) — this function does not enforce it.
|
|
160
|
+
*
|
|
161
|
+
* Merges the wide `events` table with the dedicated `node_samples` table
|
|
162
|
+
* (ADR 0027 §9) so scene-actor motion replays alongside the visitor's inputs.
|
|
163
|
+
*/
|
|
164
|
+
export async function getSessionEvents(client, projectId, sessionId) {
|
|
165
|
+
const rows = await client.all(`SELECT payload FROM events
|
|
166
|
+
WHERE project_id = $projectId AND session_id = $sessionId
|
|
167
|
+
ORDER BY ts ASC`, { projectId, sessionId });
|
|
168
|
+
const events = [];
|
|
169
|
+
for (const row of rows) {
|
|
170
|
+
const parsed = anyEventSchema.safeParse(JSON.parse(row.payload));
|
|
171
|
+
if (parsed.success)
|
|
172
|
+
events.push(parsed.data);
|
|
173
|
+
}
|
|
174
|
+
const nodes = await readSessionNodeSamples(client, projectId, sessionId);
|
|
175
|
+
return nodes.length > 0 ? mergeByTs(events, nodes) : events;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Streaming counterpart to {@link getSessionEvents}: yields one validated event
|
|
179
|
+
* at a time in `ts` order. For the self-hosted single-file store a session is
|
|
180
|
+
* read in order and yielded incrementally; malformed/invalid rows are skipped.
|
|
181
|
+
* This is the path behind the collector's NDJSON replay response (ADR 0015).
|
|
182
|
+
*
|
|
183
|
+
* Includes reconstructed `node_samples` (ADR 0027), merged into the wide-event
|
|
184
|
+
* stream by `ts` so the replay timeline stays complete and ordered.
|
|
185
|
+
*/
|
|
186
|
+
export async function* streamSessionEvents(client, projectId, sessionId) {
|
|
187
|
+
const rows = await client.all(`SELECT payload FROM events
|
|
188
|
+
WHERE project_id = $projectId AND session_id = $sessionId
|
|
189
|
+
ORDER BY ts ASC`, { projectId, sessionId });
|
|
190
|
+
const wide = [];
|
|
191
|
+
for (const row of rows) {
|
|
192
|
+
const parsed = anyEventSchema.safeParse(JSON.parse(row.payload));
|
|
193
|
+
if (parsed.success)
|
|
194
|
+
wide.push(parsed.data);
|
|
195
|
+
}
|
|
196
|
+
const nodes = await readSessionNodeSamples(client, projectId, sessionId);
|
|
197
|
+
const ordered = nodes.length > 0 ? mergeByTs(wide, nodes) : wide;
|
|
198
|
+
for (const event of ordered)
|
|
199
|
+
yield event;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Read a session's stored metadata (`device`/`scene`/`user`) from its
|
|
203
|
+
* `session_start` event. Returns `null` when the session has no start event.
|
|
204
|
+
* Coarse descriptor — not gated by raw-session retention.
|
|
205
|
+
*/
|
|
206
|
+
export async function getSessionMeta(client, projectId, sessionId) {
|
|
207
|
+
const rows = await client.all(`SELECT payload, CAST(ts AS VARCHAR) AS ts FROM events
|
|
208
|
+
WHERE project_id = $projectId AND session_id = $sessionId
|
|
209
|
+
AND event_type = 'session_start'
|
|
210
|
+
ORDER BY ts ASC
|
|
211
|
+
LIMIT 1`, { projectId, sessionId });
|
|
212
|
+
const row = rows[0];
|
|
213
|
+
if (!row)
|
|
214
|
+
return null;
|
|
215
|
+
const parsed = anyEventSchema.safeParse(JSON.parse(row.payload));
|
|
216
|
+
const event = parsed.success && parsed.data.type === "session_start" ? parsed.data : undefined;
|
|
217
|
+
return {
|
|
218
|
+
sessionId,
|
|
219
|
+
startedAt: row.ts,
|
|
220
|
+
device: event?.device,
|
|
221
|
+
scene: event?.scene,
|
|
222
|
+
user: event?.user,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/duckdb/events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DuckdbClient } from "./client.js";
|
|
2
|
+
/**
|
|
3
|
+
* Ordered, forward-only DuckDB migrations for the OSS single-file store
|
|
4
|
+
* (ADR 0020 / ADR 0007). Append new statements; never edit a shipped one. All
|
|
5
|
+
* statements are idempotent (`IF NOT EXISTS` / `CREATE OR REPLACE VIEW`), so
|
|
6
|
+
* {@link migrateDuckdb} is safe to run on every boot.
|
|
7
|
+
*
|
|
8
|
+
* This store is **single-tenant**: there is no `org_id` and no tenant isolation
|
|
9
|
+
* (those live only in the proprietary scale layer). Events and metadata share
|
|
10
|
+
* one file so a self-hosted collector needs no external services.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DUCKDB_MIGRATIONS: ReadonlyArray<{
|
|
13
|
+
id: string;
|
|
14
|
+
sql: string;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Apply all DuckDB migrations in order. Idempotent — safe to run on every boot.
|
|
18
|
+
*/
|
|
19
|
+
export declare function migrateDuckdb(client: DuckdbClient): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=migrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/duckdb/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CA2SxE,CAAC;AAEF;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAQvE"}
|