@bod.ee/db 0.12.8 → 0.13.1
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/CLAUDE.md +2 -1
- package/admin/admin.ts +23 -3
- package/admin/bun.lock +248 -0
- package/admin/index.html +12 -0
- package/admin/package.json +22 -0
- package/admin/src/App.tsx +23 -0
- package/admin/src/client/ZuzClient.ts +183 -0
- package/admin/src/client/types.ts +28 -0
- package/admin/src/components/MetricsBar.tsx +167 -0
- package/admin/src/components/Sparkline.tsx +72 -0
- package/admin/src/components/TreePane.tsx +287 -0
- package/admin/src/components/tabs/Advanced.tsx +222 -0
- package/admin/src/components/tabs/AuthRules.tsx +104 -0
- package/admin/src/components/tabs/Cache.tsx +113 -0
- package/admin/src/components/tabs/KeyAuth.tsx +462 -0
- package/admin/src/components/tabs/MessageQueue.tsx +237 -0
- package/admin/src/components/tabs/Query.tsx +75 -0
- package/admin/src/components/tabs/ReadWrite.tsx +177 -0
- package/admin/src/components/tabs/Replication.tsx +94 -0
- package/admin/src/components/tabs/Streams.tsx +329 -0
- package/admin/src/components/tabs/StressTests.tsx +209 -0
- package/admin/src/components/tabs/Subscriptions.tsx +69 -0
- package/admin/src/components/tabs/TabPane.tsx +151 -0
- package/admin/src/components/tabs/VFS.tsx +435 -0
- package/admin/src/components/tabs/View.tsx +14 -0
- package/admin/src/components/tabs/utils.ts +25 -0
- package/admin/src/context/DbContext.tsx +33 -0
- package/admin/src/context/StatsContext.tsx +56 -0
- package/admin/src/main.tsx +10 -0
- package/admin/src/styles.css +96 -0
- package/admin/tsconfig.app.json +21 -0
- package/admin/tsconfig.json +7 -0
- package/admin/tsconfig.node.json +15 -0
- package/admin/vite.config.ts +42 -0
- package/deploy/base.yaml +1 -1
- package/deploy/prod-il.config.ts +5 -2
- package/deploy/prod.config.ts +5 -2
- package/package.json +4 -1
- package/src/server/BodDB.ts +62 -5
- package/src/server/ReplicationEngine.ts +148 -35
- package/src/server/StreamEngine.ts +2 -2
- package/src/server/Transport.ts +17 -0
- package/tests/replication.test.ts +162 -1
- package/admin/ui.html +0 -3562
package/CLAUDE.md
CHANGED
|
@@ -61,6 +61,7 @@ config.ts — demo instance config (open rules, indexes, fts, v
|
|
|
61
61
|
- **Perf**: `snapshotExisting` uses `existsMany()` (single SQL query with OR conditions). `notify` skipped when no subscriptions active. `exists()` uses `LIMIT 1`. Subscription callbacks wrapped in try-catch (one bad callback can't break others).
|
|
62
62
|
- **Query SQL push-down**: For push rows (single JSON values), queries use `json_extract(value, '$.field')` in SQL WHERE/ORDER/LIMIT. Auto-detected via sampling first row. Flattened rows fall back to JS-based filtering.
|
|
63
63
|
- **Broadcast groups**: Transport shares one DB subscription per path across all WS clients. `JSON.stringify()` once, `ws.send(buffer)` for all clients. Reduces serialization from O(N) to O(1) per notification.
|
|
64
|
+
- **Stats publisher**: `BodDB.startStatsPublisher()` runs a 1s interval publishing to `_admin/stats`. Collection is **subscriber-gated**: all syscalls (`cpuUsage`, `memoryUsage`, `os.cpus`, `statSync`) are skipped when no WS client is subscribed. CPU baselines reset on resume to avoid a bogus spike. REST `GET /db/_admin/stats` always gets a one-shot snapshot via `_collectStats()` (no CPU delta). Heavy calls (SQL COUNT, statSync, os.cpus delta) run every 5 active ticks.
|
|
64
65
|
- **Async batched notifications**: `SubscriptionEngine.queueNotify()` coalesces multiple writes in same tick via `queueMicrotask()`. Opt-in via `asyncNotify: true` in `SubscriptionEngineOptions`.
|
|
65
66
|
- **Batch re-subscribe**: `{ op: 'batch-sub', subscriptions: [{path, event}] }` — single message to register N subscriptions. Client uses this on reconnect. Fallback to individual subs if server returns error.
|
|
66
67
|
- **Cursor-based materialize**: `stream.materialize(topic, { batchSize, cursor })` returns `{ data, nextCursor? }` for paginated materialization of large streams.
|
|
@@ -71,7 +72,7 @@ config.ts — demo instance config (open rules, indexes, fts, v
|
|
|
71
72
|
- **BodClientCached**: two-tier cache wrapper around BodClient. Memory (Map, LRU eviction) + IndexedDB persistence. Stale-while-revalidate: subscribed paths always fresh, unsubscribed return stale + background refetch. Writes (`set/update/delete`) invalidate path + ancestors. `init()` opens IDB + sweeps expired. `warmup(paths[])` bulk-loads from IDB. Passthrough for `push/batch/query/search/mq/stream/vfs` via `cachedClient.client`.
|
|
72
73
|
- **MCP**: `MCPAdapter` wraps a `BodClient` as a JSON-RPC MCP server (stdio + HTTP). Connects to a running BodDB instance over WebSocket — no embedded DB. Entry point: `mcp.ts`. Tools: CRUD (6), FTS (2), vectors (2), streams (4), MQ (7) = 21 tools. Use `--stdio` for Claude Code/Desktop, `--http` for remote agents.
|
|
73
74
|
- **VFS (Virtual File System)**: `VFSEngine` — files stored outside SQLite via pluggable `VFSBackend` interface. `LocalBackend` stores at `<storageRoot>/<fileId>` using `Bun.file`/`Bun.write`. Metadata at `_vfs/<virtualPath>/` (size, mime, mtime, fileId, isDir) — gets subs/rules/replication for free. `fileId = pushId` so move/rename is metadata-only. REST: `POST/GET/DELETE /files/<path>`, `?stat=1`, `?list=1`, `?mkdir=1`, `PUT ?move=<dst>`. WS chunked fallback: base64-encoded `vfs-upload-init/chunk/done`, `vfs-download-init` → `vfs-download-chunk` push messages. Client: `VFSClient` via `client.vfs()` — `upload/download` (REST) + `uploadWS/downloadWS` (WS) + `stat/list/mkdir/delete/move`.
|
|
74
|
-
- **Replication**: `ReplicationEngine` — single primary + N read replicas + multi-source feed subscriptions. Star topology. Primary emits write events to `_repl` stream via `onWrite` hooks. Replicas bootstrap via cursor-based `streamMaterialize('_repl', { keepKey: 'path', batchSize: 200 })` pagination (avoids huge single WS frame), then subscribe for ongoing events. Auto-compact on write threshold (`autoCompactThreshold`, default 500) + on startup keeps `_repl` bounded. Write proxy: replica forwards writes to primary via BodClient, primary applies + emits, replica consumes. `_replaying` flag prevents re-emission loops. `_emitting` guard prevents recursion from `db.push('_repl')`. Updates flattened to per-path set events for correct compaction keying. Sweep delete events replicated. Excluded prefixes: `_repl`, `_streams`, `_mq`, `_admin`, `_auth`. **Sources**: `ReplicationSource[]` — subscribe to specific paths from multiple remote DBs. Each source is an independent BodClient that filters `_repl` events by path prefix, with optional `localPrefix` remapping (e.g. remote `users/u1` → local `db-a/users/u1`). Sources connect in parallel; individual failures don't block others. Sources are independent of role — a DB can be primary AND consume sources. **Per-path topology**: `PathTopologyRouter` — when `paths` config is set, each path prefix gets an independent mode: `primary` (local authoritative, emits), `replica` (remote authoritative, proxies writes), `sync` (bidirectional, both emit+apply), `readonly` (pull-only, rejects writes), `writeonly` (push-only, ignores remote). Longest-prefix match resolves mode. `writeProxy: 'proxy'|'reject'` overrides replica write behavior. Bootstrap skips sync paths (ongoing stream only). Auth/rules checked before proxy in all handlers. `shouldProxyPath(path)`/`shouldRejectPath(path)` replace `isReplica` checks. `emitsToRepl`/`pullsFromPrimary` getters for compact/bootstrap decisions. Stable `replicaId` from config hash. Falls back to `role` when `paths` absent (backward compat).
|
|
75
|
+
- **Replication**: `ReplicationEngine` — single primary + N read replicas + multi-source feed subscriptions. Star topology. Primary emits write events to `_repl` stream via `onWrite` hooks. Replicas bootstrap via cursor-based `streamMaterialize('_repl', { keepKey: 'path', batchSize: 200 })` pagination (avoids huge single WS frame), then subscribe for ongoing events. Auto-compact on write threshold (`autoCompactThreshold`, default 500) + on startup keeps `_repl` bounded. Write proxy: replica forwards writes to primary via BodClient, primary applies + emits, replica consumes. `_replaying` flag prevents re-emission loops. `_emitting` guard prevents recursion from `db.push('_repl')`. Updates flattened to per-path set events for correct compaction keying. Sweep delete events replicated. Excluded prefixes: `_repl`, `_repl_scoped`, `_streams`, `_mq`, `_admin`, `_auth`. **Scoped streams**: `scopedPrefixes` config (or auto-derived from `paths`/`sources`) creates `_repl_scoped/<prefix>` streams. Primary dual-emits to both `_repl` and matching scoped stream. Sources with `scopedStream: true` subscribe to `_repl_scoped/<prefix>` directly (no client-side filtering). Per-path replicas use scoped streams when prefix is in `scopedPrefixes`. Scoped streams compact independently alongside `_repl`. Whole-DB replicas unaffected (use `_repl` as before). **Sources**: `ReplicationSource[]` — subscribe to specific paths from multiple remote DBs. Each source is an independent BodClient that filters `_repl` events by path prefix, with optional `localPrefix` remapping (e.g. remote `users/u1` → local `db-a/users/u1`). Sources connect in parallel; individual failures don't block others. Sources are independent of role — a DB can be primary AND consume sources. **Per-path topology**: `PathTopologyRouter` — when `paths` config is set, each path prefix gets an independent mode: `primary` (local authoritative, emits), `replica` (remote authoritative, proxies writes), `sync` (bidirectional, both emit+apply), `readonly` (pull-only, rejects writes), `writeonly` (push-only, ignores remote). Longest-prefix match resolves mode. `writeProxy: 'proxy'|'reject'` overrides replica write behavior. Bootstrap skips sync paths (ongoing stream only). Auth/rules checked before proxy in all handlers. `shouldProxyPath(path)`/`shouldRejectPath(path)` replace `isReplica` checks. `emitsToRepl`/`pullsFromPrimary` getters for compact/bootstrap decisions. Stable `replicaId` from config hash. Falls back to `role` when `paths` absent (backward compat).
|
|
75
76
|
- **KeyAuth integration guide**: `docs/keyauth-integration.md` — flows for signup, signin, new device, autoAuth, IAM roles, common mistakes.
|
|
76
77
|
- **Para-chat integration guide**: `docs/para-chat-integration.md` — how para-chat uses BodDB: per-path topology, VFS, KeyAuth, caching, file sync.
|
|
77
78
|
- **KeyAuth**: `KeyAuthEngine` — portable Ed25519 identity & IAM. Identity hierarchy: Root (server-level, key on filesystem), Account (portable, password-encrypted private key in DB or device-generated), Device (delegate, linked via password unlock). Challenge-response auth: server sends nonce → client signs with Ed25519 → server verifies + creates session. Self-signed tokens (no JWT lib): `base64url(payload).base64url(Ed25519_sign)`. Data model at `_auth/` prefix (protected from external writes). Device reverse-index at `_auth/deviceIndex/{dfp}` for O(1) lookup. Password change is atomic (single `db.update()`). IAM: roles with path-based permissions, account role assignment. `_auth/` excluded from replication. Transport guards: `auth-link-device` and `auth-change-password` require authenticated session; non-root users can only change own password. **Device registration**: `registerDevice(publicKey)` — client-generated keypair, no password, idempotent; `allowOpenRegistration: false` requires authenticated session. **Browser crypto**: `keyAuth.browser.ts` uses `@noble/ed25519` with DER↔raw key bridge for server compatibility. **BodClient autoAuth**: `autoAuth: true` auto-generates keypair (localStorage), registers, authenticates — zero-config device identity. `client.auth.*` convenience methods for all auth ops. **IAM transport ops**: `auth-create-role`, `auth-delete-role`, `auth-update-roles` (root only), `auth-list-accounts`, `auth-list-roles`. Device accounts (no encrypted key) safely reject `linkDevice`/`changePassword`.
|
package/admin/admin.ts
CHANGED
|
@@ -15,7 +15,7 @@ const DEFAULT_SERVER_URL = 'ws://localhost:4444';
|
|
|
15
15
|
const DEFAULT_UI_PORT = 4500;
|
|
16
16
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const DIST_PATH = join(import.meta.dir, 'dist');
|
|
19
19
|
|
|
20
20
|
/** Start the admin UI static server. Returns the Bun server instance. */
|
|
21
21
|
export function startAdminUI(options?: { port?: number; serverUrl?: string }) {
|
|
@@ -29,20 +29,40 @@ export function startAdminUI(options?: { port?: number; serverUrl?: string }) {
|
|
|
29
29
|
async fetch(req) {
|
|
30
30
|
const url = new URL(req.url);
|
|
31
31
|
|
|
32
|
+
// Open containing folder in Finder/Explorer
|
|
33
|
+
if (url.pathname === '/open-folder') {
|
|
34
|
+
const filePath = url.searchParams.get('path');
|
|
35
|
+
if (filePath) {
|
|
36
|
+
const { resolve } = await import('path');
|
|
37
|
+
const abs = resolve(filePath);
|
|
38
|
+
if (process.platform === 'darwin') Bun.spawn(['open', '-R', abs]);
|
|
39
|
+
else if (process.platform === 'win32') Bun.spawn(['explorer', `/select,${abs}`]);
|
|
40
|
+
else { const { dirname } = await import('path'); Bun.spawn(['xdg-open', dirname(abs)]); }
|
|
41
|
+
}
|
|
42
|
+
return new Response('ok');
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
// Proxy API calls to the BodDB server
|
|
33
46
|
if (url.pathname.startsWith('/db/') || url.pathname.startsWith('/files/') || url.pathname.startsWith('/sse/') || url.pathname.startsWith('/replication')) {
|
|
34
47
|
const target = httpBase + url.pathname + url.search;
|
|
35
48
|
return fetch(target, { method: req.method, headers: req.headers, body: req.body });
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
// Serve index.html for root (inject WS URL)
|
|
52
|
+
if (url.pathname === '/' || url.pathname === '/index.html') {
|
|
53
|
+
let html = await Bun.file(join(DIST_PATH, 'index.html')).text();
|
|
40
54
|
html = html.replace(
|
|
41
55
|
'</head>',
|
|
42
56
|
`<script>window.__BODDB_URL__ = ${JSON.stringify(serverUrl)};</script>\n</head>`,
|
|
43
57
|
);
|
|
44
58
|
return new Response(html, { headers: { 'Content-Type': 'text/html' } });
|
|
45
59
|
}
|
|
60
|
+
|
|
61
|
+
// Serve static assets from dist/
|
|
62
|
+
const filePath = join(DIST_PATH, url.pathname);
|
|
63
|
+
const file = Bun.file(filePath);
|
|
64
|
+
if (await file.exists()) return new Response(file);
|
|
65
|
+
|
|
46
66
|
return new Response('Not found', { status: 404 });
|
|
47
67
|
},
|
|
48
68
|
});
|
package/admin/bun.lock
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"workspaces": {
|
|
4
|
+
"": {
|
|
5
|
+
"name": "zuzdb-admin",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"react": "^18.3.1",
|
|
8
|
+
"react-dom": "^18.3.1",
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/react": "^18.3.1",
|
|
12
|
+
"@types/react-dom": "^18.3.1",
|
|
13
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
14
|
+
"typescript": "^5.5.3",
|
|
15
|
+
"vite": "^5.4.2",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
"packages": {
|
|
20
|
+
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
|
21
|
+
|
|
22
|
+
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
|
23
|
+
|
|
24
|
+
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
|
25
|
+
|
|
26
|
+
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
|
27
|
+
|
|
28
|
+
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
|
29
|
+
|
|
30
|
+
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
|
31
|
+
|
|
32
|
+
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
|
|
33
|
+
|
|
34
|
+
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
|
35
|
+
|
|
36
|
+
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
|
|
37
|
+
|
|
38
|
+
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
|
39
|
+
|
|
40
|
+
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
41
|
+
|
|
42
|
+
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
|
43
|
+
|
|
44
|
+
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
|
|
45
|
+
|
|
46
|
+
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
|
47
|
+
|
|
48
|
+
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
|
|
49
|
+
|
|
50
|
+
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
|
51
|
+
|
|
52
|
+
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
|
53
|
+
|
|
54
|
+
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
|
55
|
+
|
|
56
|
+
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
|
57
|
+
|
|
58
|
+
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
|
59
|
+
|
|
60
|
+
"@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
|
|
61
|
+
|
|
62
|
+
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
|
|
63
|
+
|
|
64
|
+
"@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
|
|
65
|
+
|
|
66
|
+
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
|
|
67
|
+
|
|
68
|
+
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
|
|
69
|
+
|
|
70
|
+
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
|
|
71
|
+
|
|
72
|
+
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
|
|
73
|
+
|
|
74
|
+
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
|
|
75
|
+
|
|
76
|
+
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
|
|
77
|
+
|
|
78
|
+
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
|
|
79
|
+
|
|
80
|
+
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
|
|
81
|
+
|
|
82
|
+
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
|
|
83
|
+
|
|
84
|
+
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
|
|
85
|
+
|
|
86
|
+
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
|
|
87
|
+
|
|
88
|
+
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
|
|
89
|
+
|
|
90
|
+
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
|
|
91
|
+
|
|
92
|
+
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
|
|
93
|
+
|
|
94
|
+
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
|
|
95
|
+
|
|
96
|
+
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
|
|
97
|
+
|
|
98
|
+
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
|
|
99
|
+
|
|
100
|
+
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
|
|
101
|
+
|
|
102
|
+
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
|
|
103
|
+
|
|
104
|
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
|
105
|
+
|
|
106
|
+
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
|
107
|
+
|
|
108
|
+
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
|
109
|
+
|
|
110
|
+
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
|
111
|
+
|
|
112
|
+
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
|
113
|
+
|
|
114
|
+
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
|
115
|
+
|
|
116
|
+
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
|
|
117
|
+
|
|
118
|
+
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
|
|
119
|
+
|
|
120
|
+
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
|
|
121
|
+
|
|
122
|
+
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
|
|
123
|
+
|
|
124
|
+
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
|
|
125
|
+
|
|
126
|
+
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
|
|
127
|
+
|
|
128
|
+
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
|
|
129
|
+
|
|
130
|
+
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
|
|
131
|
+
|
|
132
|
+
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
|
|
133
|
+
|
|
134
|
+
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
|
|
135
|
+
|
|
136
|
+
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
|
|
137
|
+
|
|
138
|
+
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
|
|
139
|
+
|
|
140
|
+
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
|
|
141
|
+
|
|
142
|
+
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
|
|
143
|
+
|
|
144
|
+
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
|
|
145
|
+
|
|
146
|
+
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
|
|
147
|
+
|
|
148
|
+
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
|
|
149
|
+
|
|
150
|
+
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
|
|
151
|
+
|
|
152
|
+
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
|
|
153
|
+
|
|
154
|
+
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
|
|
155
|
+
|
|
156
|
+
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
|
|
157
|
+
|
|
158
|
+
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
|
|
159
|
+
|
|
160
|
+
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
|
|
161
|
+
|
|
162
|
+
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
|
|
163
|
+
|
|
164
|
+
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
|
|
165
|
+
|
|
166
|
+
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
|
167
|
+
|
|
168
|
+
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
|
169
|
+
|
|
170
|
+
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
|
171
|
+
|
|
172
|
+
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
|
173
|
+
|
|
174
|
+
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
|
175
|
+
|
|
176
|
+
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
|
177
|
+
|
|
178
|
+
"@types/react": ["@types/react@18.3.28", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw=="],
|
|
179
|
+
|
|
180
|
+
"@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
|
|
181
|
+
|
|
182
|
+
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
|
183
|
+
|
|
184
|
+
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
|
|
185
|
+
|
|
186
|
+
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
|
187
|
+
|
|
188
|
+
"caniuse-lite": ["caniuse-lite@1.0.30001774", "", {}, "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA=="],
|
|
189
|
+
|
|
190
|
+
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
|
191
|
+
|
|
192
|
+
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
|
193
|
+
|
|
194
|
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
|
195
|
+
|
|
196
|
+
"electron-to-chromium": ["electron-to-chromium@1.5.302", "", {}, "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg=="],
|
|
197
|
+
|
|
198
|
+
"esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
|
|
199
|
+
|
|
200
|
+
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
|
201
|
+
|
|
202
|
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
|
203
|
+
|
|
204
|
+
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
|
205
|
+
|
|
206
|
+
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
|
207
|
+
|
|
208
|
+
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
|
209
|
+
|
|
210
|
+
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
|
211
|
+
|
|
212
|
+
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
|
213
|
+
|
|
214
|
+
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
|
215
|
+
|
|
216
|
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
|
217
|
+
|
|
218
|
+
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
|
219
|
+
|
|
220
|
+
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
|
221
|
+
|
|
222
|
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
|
223
|
+
|
|
224
|
+
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
|
225
|
+
|
|
226
|
+
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
|
|
227
|
+
|
|
228
|
+
"react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
|
|
229
|
+
|
|
230
|
+
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
|
231
|
+
|
|
232
|
+
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
|
|
233
|
+
|
|
234
|
+
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
|
235
|
+
|
|
236
|
+
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
|
237
|
+
|
|
238
|
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
|
239
|
+
|
|
240
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
241
|
+
|
|
242
|
+
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
|
243
|
+
|
|
244
|
+
"vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
|
|
245
|
+
|
|
246
|
+
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
|
247
|
+
}
|
|
248
|
+
}
|
package/admin/index.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>ZuzDB Admin</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zuzdb-admin",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react": "^18.3.1",
|
|
13
|
+
"react-dom": "^18.3.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react": "^18.3.1",
|
|
17
|
+
"@types/react-dom": "^18.3.1",
|
|
18
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
19
|
+
"typescript": "^5.5.3",
|
|
20
|
+
"vite": "^5.4.2"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DbProvider } from './context/DbContext';
|
|
2
|
+
import { StatsProvider } from './context/StatsContext';
|
|
3
|
+
import MetricsBar from './components/MetricsBar';
|
|
4
|
+
import TreePane from './components/TreePane';
|
|
5
|
+
import TabPane from './components/tabs/TabPane';
|
|
6
|
+
|
|
7
|
+
export default function App() {
|
|
8
|
+
return (
|
|
9
|
+
<DbProvider>
|
|
10
|
+
<StatsProvider>
|
|
11
|
+
<div id="metrics-bar">
|
|
12
|
+
<MetricsBar />
|
|
13
|
+
</div>
|
|
14
|
+
<div id="main">
|
|
15
|
+
<TreePane />
|
|
16
|
+
<div id="right-pane">
|
|
17
|
+
<TabPane />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</StatsProvider>
|
|
21
|
+
</DbProvider>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { ValueSnapshot, ChildEvent, FilterSpec, OrderSpec } from './types';
|
|
2
|
+
|
|
3
|
+
type PendingRequest = { resolve: (v: unknown) => void; reject: (e: Error) => void };
|
|
4
|
+
|
|
5
|
+
class ClientQueryBuilder {
|
|
6
|
+
private _filters: FilterSpec[] = [];
|
|
7
|
+
private _order?: OrderSpec;
|
|
8
|
+
private _limit?: number;
|
|
9
|
+
private _offset?: number;
|
|
10
|
+
|
|
11
|
+
constructor(private _client: ZuzClient, private _path: string) {}
|
|
12
|
+
|
|
13
|
+
where(field: string, op: string, value: unknown) { this._filters.push({ field, op, value }); return this; }
|
|
14
|
+
order(field: string, dir: 'asc' | 'desc' = 'asc') { this._order = { field, dir }; return this; }
|
|
15
|
+
limit(n: number) { this._limit = n; return this; }
|
|
16
|
+
offset(n: number) { this._offset = n; return this; }
|
|
17
|
+
get() {
|
|
18
|
+
return this._client._query(this._path, this._filters.length ? this._filters : undefined, this._order, this._limit, this._offset);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ZuzClient {
|
|
23
|
+
private _url: string;
|
|
24
|
+
private _ws: WebSocket | null = null;
|
|
25
|
+
private _msgId = 0;
|
|
26
|
+
private _pending = new Map<string, PendingRequest>();
|
|
27
|
+
private _valueCbs = new Map<string, Set<(snap: ValueSnapshot) => void>>();
|
|
28
|
+
private _childCbs = new Map<string, Set<(ev: ChildEvent) => void>>();
|
|
29
|
+
private _activeSubs = new Set<string>();
|
|
30
|
+
private _reconnectDelay = 1000;
|
|
31
|
+
private _closed = false;
|
|
32
|
+
_streamCbs?: Map<string, Set<(events: unknown[]) => void>>;
|
|
33
|
+
|
|
34
|
+
onStatusChange?: (connected: boolean) => void;
|
|
35
|
+
onConnect?: () => void;
|
|
36
|
+
|
|
37
|
+
constructor(url: string) {
|
|
38
|
+
this._url = url;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
connect(): Promise<void> {
|
|
42
|
+
if (this._ws?.readyState === WebSocket.OPEN) return Promise.resolve();
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
const ws = new WebSocket(this._url);
|
|
45
|
+
this._ws = ws;
|
|
46
|
+
ws.onopen = async () => {
|
|
47
|
+
this._reconnectDelay = 1000;
|
|
48
|
+
this.onStatusChange?.(true);
|
|
49
|
+
// re-subscribe all active subs after reconnect
|
|
50
|
+
for (const key of this._activeSubs) {
|
|
51
|
+
const colon = key.indexOf(':');
|
|
52
|
+
const event = key.slice(0, colon), path = key.slice(colon + 1);
|
|
53
|
+
this._send('sub', { path, event }).catch(() => {});
|
|
54
|
+
}
|
|
55
|
+
resolve();
|
|
56
|
+
this.onConnect?.();
|
|
57
|
+
};
|
|
58
|
+
ws.onclose = () => {
|
|
59
|
+
this.onStatusChange?.(false);
|
|
60
|
+
for (const [, p] of this._pending) p.reject(new Error('Connection closed'));
|
|
61
|
+
this._pending.clear();
|
|
62
|
+
if (!this._closed) {
|
|
63
|
+
const delay = this._reconnectDelay = Math.min(this._reconnectDelay * 2, 30000);
|
|
64
|
+
setTimeout(() => this.connect(), delay);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
ws.onerror = () => {};
|
|
68
|
+
ws.onmessage = (e) => {
|
|
69
|
+
const msg = JSON.parse(e.data);
|
|
70
|
+
if (msg.type === 'value') {
|
|
71
|
+
const snap = new ValueSnapshot(msg.path, msg.data, msg.updatedAt);
|
|
72
|
+
for (const cb of this._valueCbs.get(msg.path) ?? []) cb(snap);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (msg.type === 'stream') {
|
|
76
|
+
const streamKey = `${msg.path}:${msg.groupId}`;
|
|
77
|
+
const cbs = this._streamCbs?.get(streamKey);
|
|
78
|
+
if (cbs) { for (const cb of cbs) cb(msg.events); }
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (msg.type === 'child') {
|
|
82
|
+
const ev: ChildEvent = { type: msg.event, key: msg.key, path: msg.path + '/' + msg.key, val: () => msg.data };
|
|
83
|
+
for (const cb of this._childCbs.get(msg.path) ?? []) cb(ev);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const p = this._pending.get(msg.id);
|
|
87
|
+
if (p) { this._pending.delete(msg.id); msg.ok ? p.resolve(msg) : p.reject(new Error(msg.error)); }
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
disconnect() { this._closed = true; this._ws?.close(); }
|
|
93
|
+
reconnect() { this._closed = false; this.connect(); }
|
|
94
|
+
|
|
95
|
+
_sendRaw(op: string, params: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
if (this._ws?.readyState !== WebSocket.OPEN) return reject(new Error('Not connected'));
|
|
98
|
+
const id = String(++this._msgId);
|
|
99
|
+
this._pending.set(id, { resolve: resolve as (v: unknown) => void, reject });
|
|
100
|
+
this._ws.send(JSON.stringify({ id, op, ...params }));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async _send(op: string, params: Record<string, unknown> = {}): Promise<unknown> {
|
|
105
|
+
const msg = await this._sendRaw(op, params);
|
|
106
|
+
return msg.data ?? null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get(path: string) { return this._send('get', { path }); }
|
|
110
|
+
async getSnapshot(path: string) {
|
|
111
|
+
const msg = await this._sendRaw('get', { path });
|
|
112
|
+
return new ValueSnapshot(path, msg.data ?? null, msg.updatedAt as number | undefined);
|
|
113
|
+
}
|
|
114
|
+
getShallow(path?: string) { return this._send('get', { path: path ?? '', shallow: true }); }
|
|
115
|
+
set(path: string, value: unknown) { return this._send('set', { path, value }); }
|
|
116
|
+
update(updates: Record<string, unknown>) { return this._send('update', { updates }); }
|
|
117
|
+
delete(path: string) { return this._send('delete', { path }); }
|
|
118
|
+
batch(operations: unknown[]) { return this._send('batch', { operations }); }
|
|
119
|
+
push(path: string, value: unknown) { return this._send('push', { path, value }); }
|
|
120
|
+
transform(path: string, type: string, value: unknown) { return this._send('transform', { path, type, value }); }
|
|
121
|
+
setTTL(path: string, value: unknown, ttl: number) { return this._send('set-ttl', { path, value, ttl }); }
|
|
122
|
+
sweep() { return this._send('sweep', {}); }
|
|
123
|
+
ftsIndex(path: string, content: string) { return this._send('fts-index', { path, content }); }
|
|
124
|
+
ftsSearch(text: string, path?: string, limit?: number) { return this._send('fts-search', { text, path, limit }); }
|
|
125
|
+
vecStore(path: string, embedding: number[]) { return this._send('vec-store', { path, embedding }); }
|
|
126
|
+
vecSearch(query: number[], path?: string, limit?: number, threshold?: number) { return this._send('vec-search', { query, path, limit, threshold }); }
|
|
127
|
+
getRules() { return this._send('get-rules', {}); }
|
|
128
|
+
streamCompact(path: string, opts: Record<string, unknown>) { return this._send('stream-compact', { path, ...opts }); }
|
|
129
|
+
streamReset(path: string) { return this._send('stream-reset', { path }); }
|
|
130
|
+
streamSnapshot(path: string) { return this._send('stream-snapshot', { path }); }
|
|
131
|
+
streamMaterialize(path: string, keepKey?: string) { return this._send('stream-materialize', { path, keepKey }); }
|
|
132
|
+
streamRead(path: string, groupId: string, limit?: number) { return this._send('stream-read', { path, groupId, limit }); }
|
|
133
|
+
streamAck(path: string, groupId: string, key: string) { return this._send('stream-ack', { path, groupId, key }); }
|
|
134
|
+
streamSub(path: string, groupId: string) { return this._send('stream-sub', { path, groupId }); }
|
|
135
|
+
streamUnsub(path: string, groupId: string) { return this._send('stream-unsub', { path, groupId }); }
|
|
136
|
+
mqPush(path: string, value: unknown, idem?: string) { return this._send('mq-push', { path, value, idempotencyKey: idem || undefined }); }
|
|
137
|
+
mqFetch(path: string, count: number) { return this._send('mq-fetch', { path, count }); }
|
|
138
|
+
mqAck(path: string, key: string) { return this._send('mq-ack', { path, key }); }
|
|
139
|
+
mqNack(path: string, key: string) { return this._send('mq-nack', { path, key }); }
|
|
140
|
+
mqPeek(path: string, count: number) { return this._send('mq-peek', { path, count }); }
|
|
141
|
+
mqDlq(path: string) { return this._send('mq-dlq', { path }); }
|
|
142
|
+
mqPurge(path: string, opts?: { all?: boolean }) { return this._send('mq-purge', { path, all: opts?.all }); }
|
|
143
|
+
query(path: string) { return new ClientQueryBuilder(this, path); }
|
|
144
|
+
_query(path: string, filters?: FilterSpec[], order?: OrderSpec, limit?: number, offset?: number) {
|
|
145
|
+
return this._send('query', { path, filters, order, limit, offset });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
on(path: string, cb: (snap: ValueSnapshot) => void): () => void {
|
|
149
|
+
const key = `value:${path}`;
|
|
150
|
+
if (!this._valueCbs.has(path)) this._valueCbs.set(path, new Set());
|
|
151
|
+
this._valueCbs.get(path)!.add(cb);
|
|
152
|
+
if (!this._activeSubs.has(key)) {
|
|
153
|
+
this._activeSubs.add(key);
|
|
154
|
+
if (this._ws?.readyState === WebSocket.OPEN) this._send('sub', { path, event: 'value' }).catch(() => {});
|
|
155
|
+
}
|
|
156
|
+
return () => {
|
|
157
|
+
this._valueCbs.get(path)?.delete(cb);
|
|
158
|
+
if (!this._valueCbs.get(path)?.size) {
|
|
159
|
+
this._valueCbs.delete(path); this._activeSubs.delete(key);
|
|
160
|
+
if (this._ws?.readyState === WebSocket.OPEN) this._send('unsub', { path, event: 'value' }).catch(() => {});
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
onChild(path: string, cb: (ev: ChildEvent) => void): () => void {
|
|
166
|
+
const key = `child:${path}`;
|
|
167
|
+
if (!this._childCbs.has(path)) this._childCbs.set(path, new Set());
|
|
168
|
+
this._childCbs.get(path)!.add(cb);
|
|
169
|
+
if (!this._activeSubs.has(key)) {
|
|
170
|
+
this._activeSubs.add(key);
|
|
171
|
+
if (this._ws?.readyState === WebSocket.OPEN) this._send('sub', { path, event: 'child' }).catch(() => {});
|
|
172
|
+
}
|
|
173
|
+
return () => {
|
|
174
|
+
this._childCbs.get(path)?.delete(cb);
|
|
175
|
+
if (!this._childCbs.get(path)?.size) {
|
|
176
|
+
this._childCbs.delete(path); this._activeSubs.delete(key);
|
|
177
|
+
if (this._ws?.readyState === WebSocket.OPEN) this._send('unsub', { path, event: 'child' }).catch(() => {});
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
get connected() { return this._ws?.readyState === WebSocket.OPEN; }
|
|
183
|
+
}
|