@rawdash/adapter-libsql 0.4.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/README.md +54 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +563 -0
- package/dist/index.js.map +1 -0
- package/migrations/0000_nosy_wendell_vaughn.sql +55 -0
- package/migrations/0001_clumsy_siren.sql +6 -0
- package/migrations/meta/0000_snapshot.json +349 -0
- package/migrations/meta/0001_snapshot.json +387 -0
- package/migrations/meta/_journal.json +20 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @rawdash/adapter-libsql
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@rawdash/adapter-libsql)
|
|
4
|
+
[](https://github.com/rawdash/rawdash/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
libSQL / Turso storage adapter for rawdash.
|
|
7
|
+
|
|
8
|
+
## What it is
|
|
9
|
+
|
|
10
|
+
`@rawdash/adapter-libsql` is a `ServerStorage` implementation backed by [libSQL](https://github.com/tursodatabase/libsql) — works against any libSQL endpoint (Turso Cloud, self-hosted libsql-server, or a local file).
|
|
11
|
+
|
|
12
|
+
Internals: [Kysely](https://kysely.dev) for type-safe queries on top of `@libsql/client`. Runs on Node and Cloudflare Workers / V8 edge from the same package.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm install @rawdash/adapter-libsql @libsql/client
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick example
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createClient } from '@libsql/client';
|
|
24
|
+
import { LibsqlStorage } from '@rawdash/adapter-libsql';
|
|
25
|
+
import { defineConfig } from '@rawdash/core';
|
|
26
|
+
import { serve } from '@rawdash/server';
|
|
27
|
+
|
|
28
|
+
const storage = new LibsqlStorage({
|
|
29
|
+
client: createClient({
|
|
30
|
+
url: process.env.TURSO_DATABASE_URL!,
|
|
31
|
+
authToken: process.env.TURSO_AUTH_TOKEN,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const config = defineConfig({
|
|
36
|
+
connectors: [],
|
|
37
|
+
dashboards: {},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
serve(config, { storage });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
On Cloudflare Workers, import from `@libsql/client/web` and pass the resulting client in the same way.
|
|
44
|
+
|
|
45
|
+
## Migrations
|
|
46
|
+
|
|
47
|
+
The Drizzle schema in `src/drizzle-schema.ts` is the source of truth.
|
|
48
|
+
|
|
49
|
+
- `pnpm db:generate` runs `drizzle-kit generate` to emit a new `drizzle/NNNN_*.sql` file, then re-inlines the SQL into `src/migrations.ts`.
|
|
50
|
+
- At runtime, `LibsqlStorage` applies pending migrations from `MIGRATIONS` on first use and records applied versions in a `schema_migrations` table. The runtime applier reads from the inlined array — no filesystem access — so it works on Workers.
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Client } from '@libsql/client/web';
|
|
2
|
+
import { ServerStorage, StorageHandle, SyncState } from '@rawdash/core';
|
|
3
|
+
|
|
4
|
+
declare function initLibsqlSchema(client: Client): Promise<void>;
|
|
5
|
+
interface LibsqlStorageOptions {
|
|
6
|
+
client: Client;
|
|
7
|
+
initSchema?: boolean;
|
|
8
|
+
}
|
|
9
|
+
declare class LibsqlStorage implements ServerStorage {
|
|
10
|
+
private client;
|
|
11
|
+
private db;
|
|
12
|
+
private ready;
|
|
13
|
+
private initError;
|
|
14
|
+
constructor(options: LibsqlStorageOptions);
|
|
15
|
+
private init;
|
|
16
|
+
waitUntilReady(): Promise<void>;
|
|
17
|
+
getStorageHandle(connectorId: string): StorageHandle;
|
|
18
|
+
getSyncState(): Promise<SyncState>;
|
|
19
|
+
setSyncing(): Promise<boolean>;
|
|
20
|
+
setSyncSuccess(): Promise<void>;
|
|
21
|
+
setSyncError(error: string): Promise<void>;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
declare function applyMigrations(client: Client): Promise<void>;
|
|
26
|
+
|
|
27
|
+
interface BundledMigration {
|
|
28
|
+
tag: string;
|
|
29
|
+
statements: string[];
|
|
30
|
+
}
|
|
31
|
+
declare const MIGRATIONS: readonly BundledMigration[];
|
|
32
|
+
|
|
33
|
+
export { type BundledMigration, LibsqlStorage, type LibsqlStorageOptions, MIGRATIONS, applyMigrations, initLibsqlSchema };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
// src/libsql-storage.ts
|
|
2
|
+
import { Kysely } from "kysely";
|
|
3
|
+
import { LibsqlDialect } from "kysely-libsql";
|
|
4
|
+
|
|
5
|
+
// src/migrations-bundle.ts
|
|
6
|
+
var MIGRATIONS = [
|
|
7
|
+
{
|
|
8
|
+
tag: "0000_nosy_wendell_vaughn",
|
|
9
|
+
statements: [
|
|
10
|
+
"CREATE TABLE `distributions` (\n `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n `connector_id` text NOT NULL,\n `name` text NOT NULL,\n `ts` integer NOT NULL,\n `kind` text NOT NULL,\n `data` text NOT NULL,\n `attributes` text DEFAULT '{}' NOT NULL\n);",
|
|
11
|
+
"CREATE INDEX `distributions_conn_name_ts` ON `distributions` (`connector_id`,`name`,`ts`);",
|
|
12
|
+
"CREATE TABLE `edges` (\n `connector_id` text NOT NULL,\n `from_type` text NOT NULL,\n `from_id` text NOT NULL,\n `kind` text NOT NULL,\n `to_type` text NOT NULL,\n `to_id` text NOT NULL,\n `attributes` text DEFAULT '{}' NOT NULL,\n `updated_at` integer NOT NULL,\n PRIMARY KEY(`connector_id`, `from_type`, `from_id`, `kind`, `to_type`, `to_id`)\n);",
|
|
13
|
+
"CREATE INDEX `edges_conn_kind` ON `edges` (`connector_id`,`kind`);",
|
|
14
|
+
"CREATE INDEX `edges_conn_from` ON `edges` (`connector_id`,`from_type`,`from_id`);",
|
|
15
|
+
"CREATE TABLE `entities` (\n `connector_id` text NOT NULL,\n `type` text NOT NULL,\n `id` text NOT NULL,\n `attributes` text DEFAULT '{}' NOT NULL,\n `updated_at` integer NOT NULL,\n PRIMARY KEY(`connector_id`, `type`, `id`)\n);",
|
|
16
|
+
"CREATE INDEX `entities_conn_type` ON `entities` (`connector_id`,`type`);",
|
|
17
|
+
"CREATE TABLE `events` (\n `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n `connector_id` text NOT NULL,\n `name` text NOT NULL,\n `start_ts` integer NOT NULL,\n `end_ts` integer,\n `attributes` text DEFAULT '{}' NOT NULL\n);",
|
|
18
|
+
"CREATE INDEX `events_conn_name_start` ON `events` (`connector_id`,`name`,`start_ts`);",
|
|
19
|
+
"CREATE TABLE `metrics` (\n `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,\n `connector_id` text NOT NULL,\n `name` text NOT NULL,\n `ts` integer NOT NULL,\n `value` real NOT NULL,\n `attributes` text DEFAULT '{}' NOT NULL\n);",
|
|
20
|
+
"CREATE INDEX `metrics_conn_name_ts` ON `metrics` (`connector_id`,`name`,`ts`);"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
tag: "0001_clumsy_siren",
|
|
25
|
+
statements: [
|
|
26
|
+
"CREATE TABLE `sync_state` (\n `id` integer PRIMARY KEY NOT NULL,\n `status` text NOT NULL,\n `last_sync_at` text,\n `last_error` text\n);"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// src/migrate.ts
|
|
32
|
+
var SCHEMA_MIGRATIONS_TABLE = "schema_migrations";
|
|
33
|
+
var LEGACY_BASELINE_TABLE = "events";
|
|
34
|
+
async function tableExists(client, name) {
|
|
35
|
+
const result = await client.execute({
|
|
36
|
+
sql: "SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1",
|
|
37
|
+
args: [name]
|
|
38
|
+
});
|
|
39
|
+
return result.rows.length > 0;
|
|
40
|
+
}
|
|
41
|
+
async function readAppliedTags(client) {
|
|
42
|
+
const result = await client.execute(
|
|
43
|
+
`SELECT tag FROM ${SCHEMA_MIGRATIONS_TABLE}`
|
|
44
|
+
);
|
|
45
|
+
return new Set(result.rows.map((r) => String(r["tag"])));
|
|
46
|
+
}
|
|
47
|
+
async function applyMigrations(client) {
|
|
48
|
+
const hadSchemaTable = await tableExists(client, SCHEMA_MIGRATIONS_TABLE);
|
|
49
|
+
await client.execute(
|
|
50
|
+
`CREATE TABLE IF NOT EXISTS ${SCHEMA_MIGRATIONS_TABLE} (
|
|
51
|
+
tag TEXT PRIMARY KEY,
|
|
52
|
+
applied_at INTEGER NOT NULL
|
|
53
|
+
)`
|
|
54
|
+
);
|
|
55
|
+
if (!hadSchemaTable) {
|
|
56
|
+
const hasLegacySchema = await tableExists(client, LEGACY_BASELINE_TABLE);
|
|
57
|
+
if (hasLegacySchema) {
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
for (const migration of MIGRATIONS) {
|
|
60
|
+
await client.execute({
|
|
61
|
+
sql: `INSERT OR IGNORE INTO ${SCHEMA_MIGRATIONS_TABLE} (tag, applied_at) VALUES (?, ?)`,
|
|
62
|
+
args: [migration.tag, now]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
let applied = await readAppliedTags(client);
|
|
69
|
+
for (const migration of MIGRATIONS) {
|
|
70
|
+
if (applied.has(migration.tag)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const batch = [
|
|
74
|
+
{
|
|
75
|
+
sql: `INSERT INTO ${SCHEMA_MIGRATIONS_TABLE} (tag, applied_at) VALUES (?, ?)`,
|
|
76
|
+
args: [migration.tag, Date.now()]
|
|
77
|
+
},
|
|
78
|
+
...migration.statements
|
|
79
|
+
];
|
|
80
|
+
try {
|
|
81
|
+
await client.batch(batch, "write");
|
|
82
|
+
} catch (err) {
|
|
83
|
+
applied = await readAppliedTags(client);
|
|
84
|
+
if (!applied.has(migration.tag)) {
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/libsql-storage.ts
|
|
92
|
+
var SYNC_STATE_ID = 1;
|
|
93
|
+
async function initLibsqlSchema(client) {
|
|
94
|
+
await applyMigrations(client);
|
|
95
|
+
await client.execute({
|
|
96
|
+
sql: "INSERT OR IGNORE INTO sync_state (id, status, last_sync_at, last_error) VALUES (?, 'idle', NULL, NULL)",
|
|
97
|
+
args: [SYNC_STATE_ID]
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function parseJson(value, fallback) {
|
|
101
|
+
if (typeof value !== "string") {
|
|
102
|
+
return fallback;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(value);
|
|
106
|
+
} catch {
|
|
107
|
+
return fallback;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function createDb(client) {
|
|
111
|
+
return new Kysely({
|
|
112
|
+
dialect: new LibsqlDialect({ client })
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function toBatchStmt(q) {
|
|
116
|
+
return { sql: q.sql, args: q.parameters };
|
|
117
|
+
}
|
|
118
|
+
var LibsqlStorage = class {
|
|
119
|
+
client;
|
|
120
|
+
db;
|
|
121
|
+
ready;
|
|
122
|
+
initError = null;
|
|
123
|
+
constructor(options) {
|
|
124
|
+
this.client = options.client;
|
|
125
|
+
this.db = createDb(options.client);
|
|
126
|
+
this.ready = options.initSchema === false ? Promise.resolve() : this.init();
|
|
127
|
+
}
|
|
128
|
+
async init() {
|
|
129
|
+
try {
|
|
130
|
+
await initLibsqlSchema(this.client);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
133
|
+
this.initError = `init failed: ${message}`;
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async waitUntilReady() {
|
|
138
|
+
return this.ready;
|
|
139
|
+
}
|
|
140
|
+
getStorageHandle(connectorId) {
|
|
141
|
+
const ready = this.ready;
|
|
142
|
+
const db = this.db;
|
|
143
|
+
const client = this.client;
|
|
144
|
+
const eventRow = (e) => ({
|
|
145
|
+
connector_id: connectorId,
|
|
146
|
+
name: e.name,
|
|
147
|
+
start_ts: e.start_ts,
|
|
148
|
+
end_ts: e.end_ts,
|
|
149
|
+
attributes: JSON.stringify(e.attributes)
|
|
150
|
+
});
|
|
151
|
+
const entityRow = (e) => ({
|
|
152
|
+
connector_id: connectorId,
|
|
153
|
+
type: e.type,
|
|
154
|
+
id: e.id,
|
|
155
|
+
attributes: JSON.stringify(e.attributes),
|
|
156
|
+
updated_at: e.updated_at
|
|
157
|
+
});
|
|
158
|
+
const metricRow = (m) => ({
|
|
159
|
+
connector_id: connectorId,
|
|
160
|
+
name: m.name,
|
|
161
|
+
ts: m.ts,
|
|
162
|
+
value: m.value,
|
|
163
|
+
attributes: JSON.stringify(m.attributes)
|
|
164
|
+
});
|
|
165
|
+
const edgeRow = (e) => ({
|
|
166
|
+
connector_id: connectorId,
|
|
167
|
+
from_type: e.from_type,
|
|
168
|
+
from_id: e.from_id,
|
|
169
|
+
kind: e.kind,
|
|
170
|
+
to_type: e.to_type,
|
|
171
|
+
to_id: e.to_id,
|
|
172
|
+
attributes: JSON.stringify(e.attributes),
|
|
173
|
+
updated_at: e.updated_at
|
|
174
|
+
});
|
|
175
|
+
const distributionRow = (d) => ({
|
|
176
|
+
connector_id: connectorId,
|
|
177
|
+
name: d.name,
|
|
178
|
+
ts: d.ts,
|
|
179
|
+
kind: d.kind,
|
|
180
|
+
data: JSON.stringify(d.data),
|
|
181
|
+
attributes: JSON.stringify(d.attributes)
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
event: async (e) => {
|
|
185
|
+
await ready;
|
|
186
|
+
await db.insertInto("events").values(eventRow(e)).execute();
|
|
187
|
+
},
|
|
188
|
+
entity: async (e) => {
|
|
189
|
+
await ready;
|
|
190
|
+
await db.insertInto("entities").values(entityRow(e)).onConflict(
|
|
191
|
+
(oc) => oc.columns(["connector_id", "type", "id"]).doUpdateSet({
|
|
192
|
+
attributes: (eb) => eb.ref("excluded.attributes"),
|
|
193
|
+
updated_at: (eb) => eb.ref("excluded.updated_at")
|
|
194
|
+
})
|
|
195
|
+
).execute();
|
|
196
|
+
},
|
|
197
|
+
metric: async (m) => {
|
|
198
|
+
await ready;
|
|
199
|
+
await db.insertInto("metrics").values(metricRow(m)).execute();
|
|
200
|
+
},
|
|
201
|
+
edge: async (e) => {
|
|
202
|
+
await ready;
|
|
203
|
+
await db.insertInto("edges").values(edgeRow(e)).onConflict(
|
|
204
|
+
(oc) => oc.columns([
|
|
205
|
+
"connector_id",
|
|
206
|
+
"from_type",
|
|
207
|
+
"from_id",
|
|
208
|
+
"kind",
|
|
209
|
+
"to_type",
|
|
210
|
+
"to_id"
|
|
211
|
+
]).doUpdateSet({
|
|
212
|
+
attributes: (eb) => eb.ref("excluded.attributes"),
|
|
213
|
+
updated_at: (eb) => eb.ref("excluded.updated_at")
|
|
214
|
+
})
|
|
215
|
+
).execute();
|
|
216
|
+
},
|
|
217
|
+
distribution: async (d) => {
|
|
218
|
+
await ready;
|
|
219
|
+
await db.insertInto("distributions").values(distributionRow(d)).execute();
|
|
220
|
+
},
|
|
221
|
+
events: async (es, scope) => {
|
|
222
|
+
await ready;
|
|
223
|
+
const names = Array.from(
|
|
224
|
+
new Set(scope?.names ?? es.map((e) => e.name))
|
|
225
|
+
);
|
|
226
|
+
const stmts = [];
|
|
227
|
+
if (names.length > 0) {
|
|
228
|
+
stmts.push(
|
|
229
|
+
toBatchStmt(
|
|
230
|
+
db.deleteFrom("events").where("connector_id", "=", connectorId).where("name", "in", names).compile()
|
|
231
|
+
)
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (es.length > 0) {
|
|
235
|
+
stmts.push(
|
|
236
|
+
toBatchStmt(
|
|
237
|
+
db.insertInto("events").values(es.map(eventRow)).compile()
|
|
238
|
+
)
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
if (stmts.length > 0) {
|
|
242
|
+
await client.batch(stmts, "write");
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
entities: async (es, scope) => {
|
|
246
|
+
await ready;
|
|
247
|
+
const types = Array.from(
|
|
248
|
+
new Set(scope?.types ?? es.map((e) => e.type))
|
|
249
|
+
);
|
|
250
|
+
const stmts = [];
|
|
251
|
+
if (types.length > 0) {
|
|
252
|
+
stmts.push(
|
|
253
|
+
toBatchStmt(
|
|
254
|
+
db.deleteFrom("entities").where("connector_id", "=", connectorId).where("type", "in", types).compile()
|
|
255
|
+
)
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
if (es.length > 0) {
|
|
259
|
+
stmts.push(
|
|
260
|
+
toBatchStmt(
|
|
261
|
+
db.insertInto("entities").values(es.map(entityRow)).onConflict(
|
|
262
|
+
(oc) => oc.columns(["connector_id", "type", "id"]).doUpdateSet({
|
|
263
|
+
attributes: (eb) => eb.ref("excluded.attributes"),
|
|
264
|
+
updated_at: (eb) => eb.ref("excluded.updated_at")
|
|
265
|
+
})
|
|
266
|
+
).compile()
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
if (stmts.length > 0) {
|
|
271
|
+
await client.batch(stmts, "write");
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
metrics: async (ms, scope) => {
|
|
275
|
+
await ready;
|
|
276
|
+
const names = Array.from(
|
|
277
|
+
new Set(scope?.names ?? ms.map((m) => m.name))
|
|
278
|
+
);
|
|
279
|
+
const stmts = [];
|
|
280
|
+
if (names.length > 0) {
|
|
281
|
+
stmts.push(
|
|
282
|
+
toBatchStmt(
|
|
283
|
+
db.deleteFrom("metrics").where("connector_id", "=", connectorId).where("name", "in", names).compile()
|
|
284
|
+
)
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
if (ms.length > 0) {
|
|
288
|
+
stmts.push(
|
|
289
|
+
toBatchStmt(
|
|
290
|
+
db.insertInto("metrics").values(ms.map(metricRow)).compile()
|
|
291
|
+
)
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (stmts.length > 0) {
|
|
295
|
+
await client.batch(stmts, "write");
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
edges: async (es, scope) => {
|
|
299
|
+
await ready;
|
|
300
|
+
const kinds = Array.from(
|
|
301
|
+
new Set(scope?.kinds ?? es.map((e) => e.kind))
|
|
302
|
+
);
|
|
303
|
+
const stmts = [];
|
|
304
|
+
if (kinds.length > 0) {
|
|
305
|
+
stmts.push(
|
|
306
|
+
toBatchStmt(
|
|
307
|
+
db.deleteFrom("edges").where("connector_id", "=", connectorId).where("kind", "in", kinds).compile()
|
|
308
|
+
)
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
if (es.length > 0) {
|
|
312
|
+
stmts.push(
|
|
313
|
+
toBatchStmt(
|
|
314
|
+
db.insertInto("edges").values(es.map(edgeRow)).onConflict(
|
|
315
|
+
(oc) => oc.columns([
|
|
316
|
+
"connector_id",
|
|
317
|
+
"from_type",
|
|
318
|
+
"from_id",
|
|
319
|
+
"kind",
|
|
320
|
+
"to_type",
|
|
321
|
+
"to_id"
|
|
322
|
+
]).doUpdateSet({
|
|
323
|
+
attributes: (eb) => eb.ref("excluded.attributes"),
|
|
324
|
+
updated_at: (eb) => eb.ref("excluded.updated_at")
|
|
325
|
+
})
|
|
326
|
+
).compile()
|
|
327
|
+
)
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (stmts.length > 0) {
|
|
331
|
+
await client.batch(stmts, "write");
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
distributions: async (ds, scope) => {
|
|
335
|
+
await ready;
|
|
336
|
+
const names = Array.from(
|
|
337
|
+
new Set(scope?.names ?? ds.map((d) => d.name))
|
|
338
|
+
);
|
|
339
|
+
const stmts = [];
|
|
340
|
+
if (names.length > 0) {
|
|
341
|
+
stmts.push(
|
|
342
|
+
toBatchStmt(
|
|
343
|
+
db.deleteFrom("distributions").where("connector_id", "=", connectorId).where("name", "in", names).compile()
|
|
344
|
+
)
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
if (ds.length > 0) {
|
|
348
|
+
stmts.push(
|
|
349
|
+
toBatchStmt(
|
|
350
|
+
db.insertInto("distributions").values(ds.map(distributionRow)).compile()
|
|
351
|
+
)
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
if (stmts.length > 0) {
|
|
355
|
+
await client.batch(stmts, "write");
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
queryEvents: async (q) => {
|
|
359
|
+
await ready;
|
|
360
|
+
let qb = db.selectFrom("events").select(["name", "start_ts", "end_ts", "attributes"]).where("connector_id", "=", connectorId);
|
|
361
|
+
if (q.name !== void 0) {
|
|
362
|
+
qb = qb.where("name", "=", q.name);
|
|
363
|
+
}
|
|
364
|
+
if (q.start !== void 0) {
|
|
365
|
+
qb = qb.where("start_ts", ">=", q.start);
|
|
366
|
+
}
|
|
367
|
+
if (q.end !== void 0) {
|
|
368
|
+
qb = qb.where("start_ts", "<=", q.end);
|
|
369
|
+
}
|
|
370
|
+
const rows = await qb.execute();
|
|
371
|
+
return rows.map(
|
|
372
|
+
(r) => ({
|
|
373
|
+
name: r.name,
|
|
374
|
+
start_ts: Number(r.start_ts),
|
|
375
|
+
end_ts: r.end_ts === null ? null : Number(r.end_ts),
|
|
376
|
+
attributes: parseJson(r.attributes, {})
|
|
377
|
+
})
|
|
378
|
+
);
|
|
379
|
+
},
|
|
380
|
+
getEntity: async (type, id) => {
|
|
381
|
+
await ready;
|
|
382
|
+
const r = await db.selectFrom("entities").select(["type", "id", "attributes", "updated_at"]).where("connector_id", "=", connectorId).where("type", "=", type).where("id", "=", id).limit(1).executeTakeFirst();
|
|
383
|
+
if (!r) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
type: r.type,
|
|
388
|
+
id: r.id,
|
|
389
|
+
attributes: parseJson(r.attributes, {}),
|
|
390
|
+
updated_at: Number(r.updated_at)
|
|
391
|
+
};
|
|
392
|
+
},
|
|
393
|
+
queryEntities: async (q) => {
|
|
394
|
+
await ready;
|
|
395
|
+
const rows = await db.selectFrom("entities").select(["type", "id", "attributes", "updated_at"]).where("connector_id", "=", connectorId).where("type", "=", q.type).execute();
|
|
396
|
+
return rows.map(
|
|
397
|
+
(r) => ({
|
|
398
|
+
type: r.type,
|
|
399
|
+
id: r.id,
|
|
400
|
+
attributes: parseJson(r.attributes, {}),
|
|
401
|
+
updated_at: Number(r.updated_at)
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
},
|
|
405
|
+
queryMetrics: async (q) => {
|
|
406
|
+
await ready;
|
|
407
|
+
let qb = db.selectFrom("metrics").select(["name", "ts", "value", "attributes"]).where("connector_id", "=", connectorId);
|
|
408
|
+
if (q.name !== void 0) {
|
|
409
|
+
qb = qb.where("name", "=", q.name);
|
|
410
|
+
}
|
|
411
|
+
if (q.start !== void 0) {
|
|
412
|
+
qb = qb.where("ts", ">=", q.start);
|
|
413
|
+
}
|
|
414
|
+
if (q.end !== void 0) {
|
|
415
|
+
qb = qb.where("ts", "<=", q.end);
|
|
416
|
+
}
|
|
417
|
+
const rows = await qb.execute();
|
|
418
|
+
return rows.map(
|
|
419
|
+
(r) => ({
|
|
420
|
+
name: r.name,
|
|
421
|
+
ts: Number(r.ts),
|
|
422
|
+
value: Number(r.value),
|
|
423
|
+
attributes: parseJson(r.attributes, {})
|
|
424
|
+
})
|
|
425
|
+
);
|
|
426
|
+
},
|
|
427
|
+
traverse: async (q) => {
|
|
428
|
+
await ready;
|
|
429
|
+
let qb = db.selectFrom("edges").select([
|
|
430
|
+
"from_type",
|
|
431
|
+
"from_id",
|
|
432
|
+
"kind",
|
|
433
|
+
"to_type",
|
|
434
|
+
"to_id",
|
|
435
|
+
"attributes",
|
|
436
|
+
"updated_at"
|
|
437
|
+
]).where("connector_id", "=", connectorId);
|
|
438
|
+
if (q.fromType !== void 0) {
|
|
439
|
+
qb = qb.where("from_type", "=", q.fromType);
|
|
440
|
+
}
|
|
441
|
+
if (q.fromId !== void 0) {
|
|
442
|
+
qb = qb.where("from_id", "=", q.fromId);
|
|
443
|
+
}
|
|
444
|
+
if (q.kind !== void 0) {
|
|
445
|
+
qb = qb.where("kind", "=", q.kind);
|
|
446
|
+
}
|
|
447
|
+
if (q.toType !== void 0) {
|
|
448
|
+
qb = qb.where("to_type", "=", q.toType);
|
|
449
|
+
}
|
|
450
|
+
if (q.toId !== void 0) {
|
|
451
|
+
qb = qb.where("to_id", "=", q.toId);
|
|
452
|
+
}
|
|
453
|
+
const rows = await qb.execute();
|
|
454
|
+
return rows.map(
|
|
455
|
+
(r) => ({
|
|
456
|
+
from_type: r.from_type,
|
|
457
|
+
from_id: r.from_id,
|
|
458
|
+
kind: r.kind,
|
|
459
|
+
to_type: r.to_type,
|
|
460
|
+
to_id: r.to_id,
|
|
461
|
+
attributes: parseJson(r.attributes, {}),
|
|
462
|
+
updated_at: Number(r.updated_at)
|
|
463
|
+
})
|
|
464
|
+
);
|
|
465
|
+
},
|
|
466
|
+
queryDistributions: async (q) => {
|
|
467
|
+
await ready;
|
|
468
|
+
let qb = db.selectFrom("distributions").select(["name", "ts", "kind", "data", "attributes"]).where("connector_id", "=", connectorId);
|
|
469
|
+
if (q.name !== void 0) {
|
|
470
|
+
qb = qb.where("name", "=", q.name);
|
|
471
|
+
}
|
|
472
|
+
if (q.start !== void 0) {
|
|
473
|
+
qb = qb.where("ts", ">=", q.start);
|
|
474
|
+
}
|
|
475
|
+
if (q.end !== void 0) {
|
|
476
|
+
qb = qb.where("ts", "<=", q.end);
|
|
477
|
+
}
|
|
478
|
+
const rows = await qb.execute();
|
|
479
|
+
return rows.map((r) => {
|
|
480
|
+
const base = {
|
|
481
|
+
name: r.name,
|
|
482
|
+
ts: Number(r.ts),
|
|
483
|
+
attributes: parseJson(r.attributes, {})
|
|
484
|
+
};
|
|
485
|
+
const data = parseJson(r.data, {
|
|
486
|
+
count: 0,
|
|
487
|
+
sum: 0
|
|
488
|
+
});
|
|
489
|
+
if (r.kind === "histogram") {
|
|
490
|
+
return { ...base, kind: "histogram", data };
|
|
491
|
+
}
|
|
492
|
+
if (r.kind === "summary") {
|
|
493
|
+
return { ...base, kind: "summary", data };
|
|
494
|
+
}
|
|
495
|
+
throw new Error(
|
|
496
|
+
`Unknown distribution kind: ${r.kind} (name=${base.name})`
|
|
497
|
+
);
|
|
498
|
+
});
|
|
499
|
+
},
|
|
500
|
+
deleteOlderThan: async (shape, tsUnixMs) => {
|
|
501
|
+
await ready;
|
|
502
|
+
if (shape === "events") {
|
|
503
|
+
const r = await db.deleteFrom("events").where("connector_id", "=", connectorId).where("start_ts", "<", tsUnixMs).executeTakeFirst();
|
|
504
|
+
return { rowsDeleted: Number(r.numDeletedRows) };
|
|
505
|
+
}
|
|
506
|
+
if (shape === "metrics") {
|
|
507
|
+
const r = await db.deleteFrom("metrics").where("connector_id", "=", connectorId).where("ts", "<", tsUnixMs).executeTakeFirst();
|
|
508
|
+
return { rowsDeleted: Number(r.numDeletedRows) };
|
|
509
|
+
}
|
|
510
|
+
if (shape === "distributions") {
|
|
511
|
+
const r = await db.deleteFrom("distributions").where("connector_id", "=", connectorId).where("ts", "<", tsUnixMs).executeTakeFirst();
|
|
512
|
+
return { rowsDeleted: Number(r.numDeletedRows) };
|
|
513
|
+
}
|
|
514
|
+
throw new Error(
|
|
515
|
+
`Unsupported shape for deleteOlderThan: ${String(shape)}`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
async getSyncState() {
|
|
521
|
+
if (this.initError !== null) {
|
|
522
|
+
return { status: "error", lastSyncAt: null, lastError: this.initError };
|
|
523
|
+
}
|
|
524
|
+
await this.ready;
|
|
525
|
+
const r = await this.db.selectFrom("sync_state").select(["status", "last_sync_at", "last_error"]).where("id", "=", SYNC_STATE_ID).limit(1).executeTakeFirst();
|
|
526
|
+
if (!r) {
|
|
527
|
+
return { status: "idle", lastSyncAt: null, lastError: null };
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
status: r.status,
|
|
531
|
+
lastSyncAt: r.last_sync_at,
|
|
532
|
+
lastError: r.last_error
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
async setSyncing() {
|
|
536
|
+
await this.ready;
|
|
537
|
+
const r = await this.db.updateTable("sync_state").set({ status: "syncing" }).where("id", "=", SYNC_STATE_ID).where("status", "!=", "syncing").executeTakeFirst();
|
|
538
|
+
return Number(r.numUpdatedRows) > 0;
|
|
539
|
+
}
|
|
540
|
+
async setSyncSuccess() {
|
|
541
|
+
await this.ready;
|
|
542
|
+
await this.db.updateTable("sync_state").set({
|
|
543
|
+
status: "idle",
|
|
544
|
+
last_sync_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
545
|
+
last_error: null
|
|
546
|
+
}).where("id", "=", SYNC_STATE_ID).execute();
|
|
547
|
+
}
|
|
548
|
+
async setSyncError(error) {
|
|
549
|
+
await this.ready;
|
|
550
|
+
await this.db.updateTable("sync_state").set({ status: "error", last_error: error }).where("id", "=", SYNC_STATE_ID).execute();
|
|
551
|
+
}
|
|
552
|
+
async close() {
|
|
553
|
+
await this.ready.catch(() => void 0);
|
|
554
|
+
this.client.close();
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
export {
|
|
558
|
+
LibsqlStorage,
|
|
559
|
+
MIGRATIONS,
|
|
560
|
+
applyMigrations,
|
|
561
|
+
initLibsqlSchema
|
|
562
|
+
};
|
|
563
|
+
//# sourceMappingURL=index.js.map
|