@earendil-works/chord 1.2.0 → 1.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/CHANGELOG.md +71 -0
- package/README.md +69 -14
- package/dist/cell.d.ts +30 -9
- package/dist/cell.d.ts.map +1 -1
- package/dist/cell.js +52 -12
- package/dist/cell.js.map +1 -1
- package/dist/cluster.d.ts +4 -3
- package/dist/cluster.d.ts.map +1 -1
- package/dist/cluster.js +58 -42
- package/dist/cluster.js.map +1 -1
- package/dist/connection.d.ts +171 -58
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +676 -217
- package/dist/connection.js.map +1 -1
- package/dist/context/index.d.ts +8 -0
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +15 -0
- package/dist/context/index.js.map +1 -1
- package/dist/contract.d.ts +120 -45
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +104 -15
- package/dist/contract.js.map +1 -1
- package/dist/declare.d.ts +11 -48
- package/dist/declare.d.ts.map +1 -1
- package/dist/declare.js +1 -21
- package/dist/declare.js.map +1 -1
- package/dist/delta/index.d.ts +2 -1
- package/dist/delta/index.d.ts.map +1 -1
- package/dist/delta/index.js +4 -0
- package/dist/delta/index.js.map +1 -1
- package/dist/errors.d.ts +4 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +15 -0
- package/dist/errors.js.map +1 -1
- package/dist/host.d.ts +214 -120
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +446 -241
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/link.d.ts +47 -12
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +200 -14
- package/dist/link.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +7 -2
- package/dist/loader.js.map +1 -1
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +35 -9
- package/dist/manager.js.map +1 -1
- package/dist/object.d.ts +438 -0
- package/dist/object.d.ts.map +1 -0
- package/dist/{record.js → object.js} +614 -335
- package/dist/object.js.map +1 -0
- package/dist/output.d.ts +91 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +360 -0
- package/dist/output.js.map +1 -0
- package/dist/registry.d.ts +3 -3
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +1 -1
- package/dist/registry.js.map +1 -1
- package/dist/reload.d.ts +35 -27
- package/dist/reload.d.ts.map +1 -1
- package/dist/reload.js +256 -192
- package/dist/reload.js.map +1 -1
- package/dist/remote.d.ts +130 -35
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +493 -189
- package/dist/remote.js.map +1 -1
- package/dist/sqlite/cloudflare.d.ts +52 -0
- package/dist/sqlite/cloudflare.d.ts.map +1 -0
- package/dist/sqlite/cloudflare.js +134 -0
- package/dist/sqlite/cloudflare.js.map +1 -0
- package/dist/sqlite/database.d.ts +37 -0
- package/dist/sqlite/database.d.ts.map +1 -0
- package/dist/sqlite/database.js +2 -0
- package/dist/sqlite/database.js.map +1 -0
- package/dist/sqlite/index.d.ts +4 -0
- package/dist/sqlite/index.d.ts.map +1 -0
- package/dist/sqlite/index.js +3 -0
- package/dist/sqlite/index.js.map +1 -0
- package/dist/sqlite/migrations.d.ts +10 -0
- package/dist/sqlite/migrations.d.ts.map +1 -0
- package/dist/sqlite/migrations.js +33 -0
- package/dist/sqlite/migrations.js.map +1 -0
- package/dist/sqlite/node.d.ts +38 -0
- package/dist/sqlite/node.d.ts.map +1 -0
- package/dist/sqlite/node.js +174 -0
- package/dist/sqlite/node.js.map +1 -0
- package/dist/sqlite/schema.d.ts +3 -0
- package/dist/sqlite/schema.d.ts.map +1 -0
- package/dist/sqlite/schema.js +10 -0
- package/dist/sqlite/schema.js.map +1 -0
- package/dist/telemetry-sqlite/index.d.ts +24 -0
- package/dist/telemetry-sqlite/index.d.ts.map +1 -0
- package/dist/telemetry-sqlite/index.js +273 -0
- package/dist/telemetry-sqlite/index.js.map +1 -0
- package/dist/telemetry-sqlite/store.d.ts +9 -0
- package/dist/telemetry-sqlite/store.d.ts.map +1 -0
- package/dist/telemetry-sqlite/store.js +110 -0
- package/dist/telemetry-sqlite/store.js.map +1 -0
- package/dist/types.d.ts +39 -28
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/websocket.d.ts +4 -5
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +3 -2
- package/dist/websocket.js.map +1 -1
- package/dist/wire.d.ts +210 -129
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +108 -55
- package/dist/wire.js.map +1 -1
- package/docs/guide.md +267 -203
- package/docs/spec.md +141 -114
- package/docs/tutorial.md +257 -235
- package/package.json +27 -1
- package/dist/record.d.ts +0 -320
- package/dist/record.d.ts.map +0 -1
- package/dist/record.js.map +0 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
const DEFAULT_WAL_AUTO_CHECKPOINT_PAGES = 1_000;
|
|
5
|
+
const DEFAULT_BUSY_TIMEOUT_MS = 5_000;
|
|
6
|
+
const ignore = () => { };
|
|
7
|
+
/**
|
|
8
|
+
* Runs operations in call order. An operation starts immediately when nothing is running or waiting;
|
|
9
|
+
* otherwise it waits for everything before it. An asynchronous operation holds the queue until it settles.
|
|
10
|
+
*/
|
|
11
|
+
class SerialOperationQueue {
|
|
12
|
+
tail = Promise.resolve();
|
|
13
|
+
pending = 0;
|
|
14
|
+
run(operation) {
|
|
15
|
+
if (this.pending > 0)
|
|
16
|
+
return this.enqueue(operation);
|
|
17
|
+
try {
|
|
18
|
+
return Promise.resolve(operation());
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return Promise.reject(error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
runAsync(operation) {
|
|
25
|
+
if (this.pending > 0)
|
|
26
|
+
return this.enqueue(operation);
|
|
27
|
+
this.pending++;
|
|
28
|
+
// Publish the barrier before the operation starts, so calls it makes synchronously wait behind it.
|
|
29
|
+
const { promise: barrier, resolve: releaseBarrier } = Promise.withResolvers();
|
|
30
|
+
this.tail = barrier;
|
|
31
|
+
let started;
|
|
32
|
+
try {
|
|
33
|
+
started = operation();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
started = Promise.reject(error);
|
|
37
|
+
}
|
|
38
|
+
return started.finally(() => {
|
|
39
|
+
this.pending--;
|
|
40
|
+
releaseBarrier();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
enqueue(operation) {
|
|
44
|
+
this.pending++;
|
|
45
|
+
return this.release(this.tail.then(operation));
|
|
46
|
+
}
|
|
47
|
+
release(operation) {
|
|
48
|
+
const settled = operation.finally(() => {
|
|
49
|
+
this.pending--;
|
|
50
|
+
});
|
|
51
|
+
this.tail = settled.then(ignore, ignore);
|
|
52
|
+
return settled;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Executes SQL on one connection. Prepared statements are cached per connection by SQL text, so the
|
|
57
|
+
* database and its transaction handles share them across transactions.
|
|
58
|
+
*/
|
|
59
|
+
class NodeSqliteExecutor {
|
|
60
|
+
database;
|
|
61
|
+
statements;
|
|
62
|
+
constructor(database, statements) {
|
|
63
|
+
this.database = database;
|
|
64
|
+
this.statements = statements;
|
|
65
|
+
}
|
|
66
|
+
exec(sql) {
|
|
67
|
+
return this.runOperation(() => {
|
|
68
|
+
this.database.exec(sql);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
run(sql, ...params) {
|
|
72
|
+
return this.runOperation(() => {
|
|
73
|
+
this.statement(sql).run(...params);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
get(sql, ...params) {
|
|
77
|
+
return this.runOperation(() => this.statement(sql).get(...params));
|
|
78
|
+
}
|
|
79
|
+
all(sql, ...params) {
|
|
80
|
+
return this.runOperation(() => this.statement(sql).all(...params));
|
|
81
|
+
}
|
|
82
|
+
statement(sql) {
|
|
83
|
+
let statement = this.statements.get(sql);
|
|
84
|
+
if (statement === undefined) {
|
|
85
|
+
statement = this.database.prepare(sql);
|
|
86
|
+
this.statements.set(sql, statement);
|
|
87
|
+
}
|
|
88
|
+
return statement;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
class NodeSqliteTransaction extends NodeSqliteExecutor {
|
|
92
|
+
scope;
|
|
93
|
+
constructor(database, statements, scope) {
|
|
94
|
+
super(database, statements);
|
|
95
|
+
this.scope = scope;
|
|
96
|
+
}
|
|
97
|
+
async runOperation(operation) {
|
|
98
|
+
if (!this.scope.active)
|
|
99
|
+
throw new Error("SQLite transaction handle is no longer active");
|
|
100
|
+
return operation();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */
|
|
104
|
+
export class NodeSqliteDatabase extends NodeSqliteExecutor {
|
|
105
|
+
access = new SerialOperationQueue();
|
|
106
|
+
closed = false;
|
|
107
|
+
constructor(database) {
|
|
108
|
+
super(database, new Map());
|
|
109
|
+
}
|
|
110
|
+
transaction(callback) {
|
|
111
|
+
return this.access.runAsync(async () => {
|
|
112
|
+
this.database.exec("BEGIN IMMEDIATE");
|
|
113
|
+
const scope = { active: true };
|
|
114
|
+
try {
|
|
115
|
+
const result = await callback(new NodeSqliteTransaction(this.database, this.statements, scope));
|
|
116
|
+
scope.active = false;
|
|
117
|
+
this.database.exec("COMMIT");
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
scope.active = false;
|
|
122
|
+
try {
|
|
123
|
+
this.database.exec("ROLLBACK");
|
|
124
|
+
}
|
|
125
|
+
catch (rollbackError) {
|
|
126
|
+
throw new AggregateError([error, rollbackError], "SQLite transaction failed and rollback failed");
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
close() {
|
|
133
|
+
return this.access.run(() => {
|
|
134
|
+
if (this.closed)
|
|
135
|
+
return;
|
|
136
|
+
this.closed = true;
|
|
137
|
+
this.statements.clear();
|
|
138
|
+
try {
|
|
139
|
+
this.database.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
this.database.close();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
runOperation(operation) {
|
|
147
|
+
return this.access.run(operation);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** Open and configure a Node-backed SQLite database facade. */
|
|
151
|
+
export async function openNodeSqliteDatabase(path, options = {}) {
|
|
152
|
+
const checkpointPages = options.walAutoCheckpointPages ?? DEFAULT_WAL_AUTO_CHECKPOINT_PAGES;
|
|
153
|
+
const timeout = options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS;
|
|
154
|
+
if (path !== ":memory:")
|
|
155
|
+
await mkdir(dirname(path), { recursive: true });
|
|
156
|
+
const database = new DatabaseSync(path, { timeout });
|
|
157
|
+
const adapter = new NodeSqliteDatabase(database);
|
|
158
|
+
try {
|
|
159
|
+
await adapter.exec("PRAGMA journal_mode = WAL");
|
|
160
|
+
await adapter.exec("PRAGMA synchronous = NORMAL");
|
|
161
|
+
await adapter.exec(`PRAGMA wal_autocheckpoint = ${checkpointPages}`);
|
|
162
|
+
return adapter;
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
try {
|
|
166
|
+
await adapter.close();
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Preserve the configuration failure.
|
|
170
|
+
}
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/sqlite/node.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { StatementSync } from \"node:sqlite\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport type { SqliteDatabase, SqliteExecutor, SqliteValue } from \"./database.ts\";\n\n/** Node SQLite connection settings for a database file. */\nexport type NodeSqliteDatabaseOptions = {\n\t/** SQLite WAL auto-checkpoint threshold. SQLite and this adapter default to 1,000 pages; 0 disables it. */\n\treadonly walAutoCheckpointPages?: number;\n\t/** Time SQLite waits for a competing file lock. SQLite defaults to 0; this adapter defaults to 5,000 ms. */\n\treadonly busyTimeoutMs?: number;\n};\n\nconst DEFAULT_WAL_AUTO_CHECKPOINT_PAGES = 1_000;\nconst DEFAULT_BUSY_TIMEOUT_MS = 5_000;\n\ntype TransactionScope = { active: boolean };\n\nconst ignore = (): void => {};\n\n/**\n * Runs operations in call order. An operation starts immediately when nothing is running or waiting;\n * otherwise it waits for everything before it. An asynchronous operation holds the queue until it settles.\n */\nclass SerialOperationQueue {\n\tprivate tail: Promise<void> = Promise.resolve();\n\tprivate pending = 0;\n\n\trun<T>(operation: () => T): Promise<T> {\n\t\tif (this.pending > 0) return this.enqueue(operation);\n\t\ttry {\n\t\t\treturn Promise.resolve(operation());\n\t\t} catch (error) {\n\t\t\treturn Promise.reject(error);\n\t\t}\n\t}\n\n\trunAsync<T>(operation: () => Promise<T>): Promise<T> {\n\t\tif (this.pending > 0) return this.enqueue(operation);\n\t\tthis.pending++;\n\t\t// Publish the barrier before the operation starts, so calls it makes synchronously wait behind it.\n\t\tconst { promise: barrier, resolve: releaseBarrier } = Promise.withResolvers<void>();\n\t\tthis.tail = barrier;\n\t\tlet started: Promise<T>;\n\t\ttry {\n\t\t\tstarted = operation();\n\t\t} catch (error) {\n\t\t\tstarted = Promise.reject(error);\n\t\t}\n\t\treturn started.finally(() => {\n\t\t\tthis.pending--;\n\t\t\treleaseBarrier();\n\t\t});\n\t}\n\n\tprivate enqueue<T>(operation: () => T | Promise<T>): Promise<T> {\n\t\tthis.pending++;\n\t\treturn this.release(this.tail.then(operation));\n\t}\n\n\tprivate release<T>(operation: Promise<T>): Promise<T> {\n\t\tconst settled = operation.finally(() => {\n\t\t\tthis.pending--;\n\t\t});\n\t\tthis.tail = settled.then(ignore, ignore);\n\t\treturn settled;\n\t}\n}\n\n/**\n * Executes SQL on one connection. Prepared statements are cached per connection by SQL text, so the\n * database and its transaction handles share them across transactions.\n */\nabstract class NodeSqliteExecutor implements SqliteExecutor {\n\tprotected readonly database: DatabaseSync;\n\tprotected readonly statements: Map<string, StatementSync>;\n\n\tconstructor(database: DatabaseSync, statements: Map<string, StatementSync>) {\n\t\tthis.database = database;\n\t\tthis.statements = statements;\n\t}\n\n\texec(sql: string): Promise<void> {\n\t\treturn this.runOperation(() => {\n\t\t\tthis.database.exec(sql);\n\t\t});\n\t}\n\n\trun(sql: string, ...params: SqliteValue[]): Promise<void> {\n\t\treturn this.runOperation(() => {\n\t\t\tthis.statement(sql).run(...params);\n\t\t});\n\t}\n\n\tget<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T | undefined> {\n\t\treturn this.runOperation(() => this.statement(sql).get(...params) as T | undefined);\n\t}\n\n\tall<T extends object>(sql: string, ...params: SqliteValue[]): Promise<T[]> {\n\t\treturn this.runOperation(() => this.statement(sql).all(...params) as T[]);\n\t}\n\n\tprotected abstract runOperation<T>(operation: () => T): Promise<T>;\n\n\tprivate statement(sql: string): StatementSync {\n\t\tlet statement = this.statements.get(sql);\n\t\tif (statement === undefined) {\n\t\t\tstatement = this.database.prepare(sql);\n\t\t\tthis.statements.set(sql, statement);\n\t\t}\n\t\treturn statement;\n\t}\n}\n\nclass NodeSqliteTransaction extends NodeSqliteExecutor {\n\tprivate readonly scope: TransactionScope;\n\n\tconstructor(database: DatabaseSync, statements: Map<string, StatementSync>, scope: TransactionScope) {\n\t\tsuper(database, statements);\n\t\tthis.scope = scope;\n\t}\n\n\tprotected async runOperation<T>(operation: () => T): Promise<T> {\n\t\tif (!this.scope.active) throw new Error(\"SQLite transaction handle is no longer active\");\n\t\treturn operation();\n\t}\n}\n\n/** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */\nexport class NodeSqliteDatabase extends NodeSqliteExecutor implements SqliteDatabase {\n\tprivate readonly access = new SerialOperationQueue();\n\tprivate closed = false;\n\n\tconstructor(database: DatabaseSync) {\n\t\tsuper(database, new Map());\n\t}\n\n\ttransaction<T>(callback: (transaction: SqliteExecutor) => Promise<T>): Promise<T> {\n\t\treturn this.access.runAsync(async () => {\n\t\t\tthis.database.exec(\"BEGIN IMMEDIATE\");\n\t\t\tconst scope = { active: true };\n\t\t\ttry {\n\t\t\t\tconst result = await callback(new NodeSqliteTransaction(this.database, this.statements, scope));\n\t\t\t\tscope.active = false;\n\t\t\t\tthis.database.exec(\"COMMIT\");\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tscope.active = false;\n\t\t\t\ttry {\n\t\t\t\t\tthis.database.exec(\"ROLLBACK\");\n\t\t\t\t} catch (rollbackError) {\n\t\t\t\t\tthrow new AggregateError([error, rollbackError], \"SQLite transaction failed and rollback failed\");\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\t}\n\n\tclose(): Promise<void> {\n\t\treturn this.access.run(() => {\n\t\t\tif (this.closed) return;\n\t\t\tthis.closed = true;\n\t\t\tthis.statements.clear();\n\t\t\ttry {\n\t\t\t\tthis.database.exec(\"PRAGMA wal_checkpoint(TRUNCATE)\");\n\t\t\t} finally {\n\t\t\t\tthis.database.close();\n\t\t\t}\n\t\t});\n\t}\n\n\tprotected runOperation<T>(operation: () => T): Promise<T> {\n\t\treturn this.access.run(operation);\n\t}\n}\n\n/** Open and configure a Node-backed SQLite database facade. */\nexport async function openNodeSqliteDatabase(\n\tpath: string,\n\toptions: NodeSqliteDatabaseOptions = {},\n): Promise<NodeSqliteDatabase> {\n\tconst checkpointPages = options.walAutoCheckpointPages ?? DEFAULT_WAL_AUTO_CHECKPOINT_PAGES;\n\tconst timeout = options.busyTimeoutMs ?? DEFAULT_BUSY_TIMEOUT_MS;\n\tif (path !== \":memory:\") await mkdir(dirname(path), { recursive: true });\n\tconst database = new DatabaseSync(path, { timeout });\n\tconst adapter = new NodeSqliteDatabase(database);\n\ttry {\n\t\tawait adapter.exec(\"PRAGMA journal_mode = WAL\");\n\t\tawait adapter.exec(\"PRAGMA synchronous = NORMAL\");\n\t\tawait adapter.exec(`PRAGMA wal_autocheckpoint = ${checkpointPages}`);\n\t\treturn adapter;\n\t} catch (error) {\n\t\ttry {\n\t\t\tawait adapter.close();\n\t\t} catch {\n\t\t\t// Preserve the configuration failure.\n\t\t}\n\t\tthrow error;\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/sqlite/schema.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAO/D"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Validate a prefix before using it as part of a SQLite identifier. */
|
|
2
|
+
export function validateSqliteSchemaPrefix(prefix) {
|
|
3
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) {
|
|
4
|
+
throw new Error(`Invalid SQLite schema prefix ${JSON.stringify(prefix)}`);
|
|
5
|
+
}
|
|
6
|
+
if (prefix.toLowerCase().startsWith("sqlite_")) {
|
|
7
|
+
throw new Error(`SQLite schema prefix ${JSON.stringify(prefix)} is reserved`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/sqlite/schema.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,UAAU,0BAA0B,CAAC,MAAc;IACxD,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAC/E,CAAC;AACF,CAAC","sourcesContent":["/** Validate a prefix before using it as part of a SQLite identifier. */\nexport function validateSqliteSchemaPrefix(prefix: string): void {\n\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) {\n\t\tthrow new Error(`Invalid SQLite schema prefix ${JSON.stringify(prefix)}`);\n\t}\n\tif (prefix.toLowerCase().startsWith(\"sqlite_\")) {\n\t\tthrow new Error(`SQLite schema prefix ${JSON.stringify(prefix)} is reserved`);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type TelemetryContext, type TelemetryMetrics, type TelemetryPropagation, type TelemetryStore } from "@earendil-works/pi-telemetry";
|
|
2
|
+
import { type SqliteDatabase } from "../sqlite/index.ts";
|
|
3
|
+
export interface SqliteTelemetryOptions {
|
|
4
|
+
/** Retain seven days by default. Cleanup runs in the flush transaction. */
|
|
5
|
+
readonly retentionMs?: number;
|
|
6
|
+
/** Settled spans awaiting commit, including the in-flight batch. Defaults to 10,000.
|
|
7
|
+
* Also bounds pending metric interval/series pairs independently. */
|
|
8
|
+
readonly maxQueuedSpans?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface SqliteTelemetry extends TelemetryContext, TelemetryStore {
|
|
11
|
+
readonly metrics: TelemetryMetrics;
|
|
12
|
+
readonly propagation: TelemetryPropagation;
|
|
13
|
+
readonly droppedSpans: number;
|
|
14
|
+
readonly droppedMeasurements: number;
|
|
15
|
+
/** Persist settled spans and completed ten-second metric intervals. */
|
|
16
|
+
flush(): Promise<void>;
|
|
17
|
+
/** Stop recording and drain, including partial metric intervals. Does not close the database. */
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/** Portable, buffered SQLite recorder. Recording never performs SQL; flush runs every second.
|
|
21
|
+
* Only settled spans are stored. Metrics aggregate count/sum/min/max per ten-second interval.
|
|
22
|
+
* A write failure permanently stops recording and is reported by flush and close. */
|
|
23
|
+
export declare function createSqliteTelemetry(db: SqliteDatabase, options?: SqliteTelemetryOptions): Promise<SqliteTelemetry>;
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry-sqlite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAQN,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAEzB,KAAK,cAAc,EACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAyB,KAAK,cAAc,EAAoB,MAAM,oBAAoB,CAAC;AAGlG,MAAM,WAAW,sBAAsB;IACtC,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;yEACqE;IACrE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,eAAgB,SAAQ,gBAAgB,EAAE,cAAc;IACxE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,uEAAuE;IACvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,iGAAiG;IACjG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AA4SD;;qFAEqF;AACrF,wBAAsB,qBAAqB,CAC1C,EAAE,EAAE,cAAc,EAClB,OAAO,GAAE,sBAA2B,GAClC,OAAO,CAAC,eAAe,CAAC,CAsB1B"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { NOOP_TELEMETRY_CONTEXT, } from "@earendil-works/pi-telemetry";
|
|
2
|
+
import { applySqliteMigrations } from "../sqlite/index.js";
|
|
3
|
+
import { findMetrics, findTraces, getTrace } from "./store.js";
|
|
4
|
+
const identities = new WeakMap();
|
|
5
|
+
const carrierKey = "pi-sqlite-span";
|
|
6
|
+
const traceKey = "pi-sqlite-trace";
|
|
7
|
+
const intervalMs = 10_000;
|
|
8
|
+
function copyAttributes(attributes) {
|
|
9
|
+
return Object.fromEntries(Object.entries(attributes ?? {})
|
|
10
|
+
.filter(([, value]) => value !== undefined)
|
|
11
|
+
.map(([key, value]) => [key, Array.isArray(value) ? [...value] : value]));
|
|
12
|
+
}
|
|
13
|
+
function copyLink(link) {
|
|
14
|
+
const identity = identities.get(link.context);
|
|
15
|
+
return identity
|
|
16
|
+
? { trace_id: identity.traceId, span_id: identity.id, attributes: copyAttributes(link.attributes) }
|
|
17
|
+
: undefined;
|
|
18
|
+
}
|
|
19
|
+
class Recorder {
|
|
20
|
+
propagation = this;
|
|
21
|
+
metrics = {
|
|
22
|
+
counter: (name, options) => ({ add: this.measurement("counter", name, options) }),
|
|
23
|
+
upDownCounter: (name, options) => ({ add: this.measurement("upDownCounter", name, options) }),
|
|
24
|
+
histogram: (name, options) => ({ record: this.measurement("histogram", name, options) }),
|
|
25
|
+
};
|
|
26
|
+
droppedSpans = 0;
|
|
27
|
+
droppedMeasurements = 0;
|
|
28
|
+
db;
|
|
29
|
+
retention;
|
|
30
|
+
capacity;
|
|
31
|
+
scope = crypto.randomUUID();
|
|
32
|
+
sequence = 0;
|
|
33
|
+
spans = [];
|
|
34
|
+
measurements = new Map();
|
|
35
|
+
inFlightMeasurements = 0;
|
|
36
|
+
timer;
|
|
37
|
+
stopped = false;
|
|
38
|
+
failed = false;
|
|
39
|
+
failure;
|
|
40
|
+
writing;
|
|
41
|
+
closing;
|
|
42
|
+
constructor(db, retention, capacity) {
|
|
43
|
+
this.db = db;
|
|
44
|
+
this.retention = retention;
|
|
45
|
+
this.capacity = capacity;
|
|
46
|
+
this.timer = setInterval(() => {
|
|
47
|
+
if (!this.writing)
|
|
48
|
+
void this.flush().catch(() => { });
|
|
49
|
+
}, 1000);
|
|
50
|
+
this.timer.unref?.();
|
|
51
|
+
}
|
|
52
|
+
startSpan(options, callback) {
|
|
53
|
+
return this.start(undefined, options, callback);
|
|
54
|
+
}
|
|
55
|
+
inject(context) {
|
|
56
|
+
const identity = identities.get(context);
|
|
57
|
+
return identity ? { [carrierKey]: identity.id, [traceKey]: identity.traceId } : undefined;
|
|
58
|
+
}
|
|
59
|
+
extract(carrier) {
|
|
60
|
+
const id = carrier?.[carrierKey];
|
|
61
|
+
const traceId = carrier?.[traceKey];
|
|
62
|
+
if (typeof id !== "string" || typeof traceId !== "string")
|
|
63
|
+
return this;
|
|
64
|
+
const identity = { id, traceId };
|
|
65
|
+
const parent = {
|
|
66
|
+
metrics: this.metrics,
|
|
67
|
+
propagation: this,
|
|
68
|
+
startSpan: (options, callback) => this.start(identity, options, callback),
|
|
69
|
+
};
|
|
70
|
+
identities.set(parent, identity);
|
|
71
|
+
return parent;
|
|
72
|
+
}
|
|
73
|
+
inert(options, callback) {
|
|
74
|
+
return NOOP_TELEMETRY_CONTEXT.startSpan(options, (noop) => callback({
|
|
75
|
+
...noop,
|
|
76
|
+
metrics: this.metrics,
|
|
77
|
+
propagation: this,
|
|
78
|
+
startSpan: (options, callback) => this.inert(options, callback),
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
async start(parent, options, callback) {
|
|
82
|
+
if (this.stopped)
|
|
83
|
+
return this.inert(options, callback);
|
|
84
|
+
const name = options.name;
|
|
85
|
+
let attributes = copyAttributes(options.attributes);
|
|
86
|
+
const links = [];
|
|
87
|
+
for (const link of options.links ?? []) {
|
|
88
|
+
const copied = copyLink(link);
|
|
89
|
+
if (copied)
|
|
90
|
+
links.push(copied);
|
|
91
|
+
}
|
|
92
|
+
const id = `${this.scope}:${++this.sequence}`;
|
|
93
|
+
const identity = { id, traceId: parent?.traceId ?? id };
|
|
94
|
+
const started = Date.now();
|
|
95
|
+
const clock = performance.now();
|
|
96
|
+
const events = [];
|
|
97
|
+
let status = { status: "ok" };
|
|
98
|
+
let explicit = false;
|
|
99
|
+
let settled = false;
|
|
100
|
+
const record = (action) => {
|
|
101
|
+
if (!settled && !this.stopped)
|
|
102
|
+
action();
|
|
103
|
+
};
|
|
104
|
+
const span = {
|
|
105
|
+
metrics: this.metrics,
|
|
106
|
+
propagation: this,
|
|
107
|
+
startSpan: (options, callback) => settled ? this.inert(options, callback) : this.start(identity, options, callback),
|
|
108
|
+
addLink: (link) => record(() => {
|
|
109
|
+
const copied = copyLink(link);
|
|
110
|
+
if (copied)
|
|
111
|
+
links.push(copied);
|
|
112
|
+
}),
|
|
113
|
+
addEvent: (name, attrs) => record(() => {
|
|
114
|
+
events.push({ name, time_ms: performance.now() - clock, attributes: copyAttributes(attrs) });
|
|
115
|
+
}),
|
|
116
|
+
setAttributes: (attrs) => record(() => {
|
|
117
|
+
attributes = { ...attributes, ...copyAttributes(attrs) };
|
|
118
|
+
}),
|
|
119
|
+
setStatus: (value) => record(() => {
|
|
120
|
+
status =
|
|
121
|
+
value.status === "error" && value.error
|
|
122
|
+
? { status: "error", error: { ...value.error } }
|
|
123
|
+
: { status: value.status };
|
|
124
|
+
explicit = true;
|
|
125
|
+
}),
|
|
126
|
+
};
|
|
127
|
+
identities.set(span, identity);
|
|
128
|
+
try {
|
|
129
|
+
return await callback(span);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (!explicit)
|
|
133
|
+
status =
|
|
134
|
+
error instanceof Error
|
|
135
|
+
? { status: "error", error: { name: error.name, message: error.message } }
|
|
136
|
+
: { status: "error" };
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
settled = true;
|
|
141
|
+
if (!this.stopped) {
|
|
142
|
+
if (this.spans.length >= this.capacity)
|
|
143
|
+
this.droppedSpans++;
|
|
144
|
+
else
|
|
145
|
+
this.spans.push([
|
|
146
|
+
id,
|
|
147
|
+
identity.traceId,
|
|
148
|
+
parent?.id ?? null,
|
|
149
|
+
name,
|
|
150
|
+
started,
|
|
151
|
+
started + performance.now() - clock,
|
|
152
|
+
JSON.stringify(attributes),
|
|
153
|
+
JSON.stringify(events),
|
|
154
|
+
JSON.stringify(links),
|
|
155
|
+
JSON.stringify(status),
|
|
156
|
+
]);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
measurement(kind, name, options) {
|
|
161
|
+
const unit = options?.unit ?? null;
|
|
162
|
+
return (value, attributes) => {
|
|
163
|
+
if (this.stopped)
|
|
164
|
+
return;
|
|
165
|
+
const copied = copyAttributes(attributes);
|
|
166
|
+
const encoded = JSON.stringify(Object.fromEntries(Object.keys(copied)
|
|
167
|
+
.sort()
|
|
168
|
+
.map((key) => [key, copied[key]])));
|
|
169
|
+
const key = JSON.stringify([name, kind, unit, encoded]);
|
|
170
|
+
const interval = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
171
|
+
const pendingKey = `${interval}:${key}`;
|
|
172
|
+
const previous = this.measurements.get(pendingKey);
|
|
173
|
+
if (!previous && this.measurements.size + this.inFlightMeasurements >= this.capacity) {
|
|
174
|
+
this.droppedMeasurements++;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this.measurements.set(pendingKey, {
|
|
178
|
+
interval,
|
|
179
|
+
key,
|
|
180
|
+
name,
|
|
181
|
+
kind,
|
|
182
|
+
unit,
|
|
183
|
+
attributes: encoded,
|
|
184
|
+
count: (previous?.count ?? 0) + 1,
|
|
185
|
+
sum: (previous?.sum ?? 0) + value,
|
|
186
|
+
min: Math.min(previous?.min ?? value, value),
|
|
187
|
+
max: Math.max(previous?.max ?? value, value),
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/** Readers use committed rows only; flush first to include newly settled spans. */
|
|
192
|
+
getTrace = (traceId) => getTrace(this.db, traceId);
|
|
193
|
+
findTraces = (query) => findTraces(this.db, query);
|
|
194
|
+
findMetrics = (query) => findMetrics(this.db, query);
|
|
195
|
+
flush() {
|
|
196
|
+
return this.closing ?? this.drain(false);
|
|
197
|
+
}
|
|
198
|
+
close() {
|
|
199
|
+
if (!this.closing) {
|
|
200
|
+
this.stopped = true;
|
|
201
|
+
clearInterval(this.timer);
|
|
202
|
+
this.closing = this.drain(true);
|
|
203
|
+
}
|
|
204
|
+
return this.closing;
|
|
205
|
+
}
|
|
206
|
+
async drain(partial) {
|
|
207
|
+
while (this.writing)
|
|
208
|
+
await this.writing;
|
|
209
|
+
if (this.failed)
|
|
210
|
+
throw this.failure;
|
|
211
|
+
const spans = this.spans.slice();
|
|
212
|
+
const metrics = [];
|
|
213
|
+
const completed = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
214
|
+
for (const [key, metric] of this.measurements) {
|
|
215
|
+
if (!partial && metric.interval >= completed)
|
|
216
|
+
continue;
|
|
217
|
+
metrics.push(metric);
|
|
218
|
+
this.measurements.delete(key);
|
|
219
|
+
}
|
|
220
|
+
this.inFlightMeasurements = metrics.length;
|
|
221
|
+
this.writing = Promise.resolve()
|
|
222
|
+
.then(() => this.db.transaction(async (tx) => {
|
|
223
|
+
for (const span of spans)
|
|
224
|
+
await tx.run("INSERT INTO telemetry_spans VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ...span);
|
|
225
|
+
for (const metric of metrics)
|
|
226
|
+
await tx.run(`INSERT INTO telemetry_metrics VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
227
|
+
ON CONFLICT(interval_ms, series_key) DO UPDATE SET count = count + excluded.count,
|
|
228
|
+
sum = sum + excluded.sum, min = min(min, excluded.min), max = max(max, excluded.max)`, metric.interval, metric.key, metric.name, metric.kind, metric.unit, metric.attributes, metric.count, metric.sum, metric.min, metric.max);
|
|
229
|
+
const cutoff = Date.now() - this.retention;
|
|
230
|
+
await tx.run("DELETE FROM telemetry_spans WHERE ended_ms < ?", cutoff);
|
|
231
|
+
await tx.run("DELETE FROM telemetry_metrics WHERE interval_ms < ?", cutoff);
|
|
232
|
+
}))
|
|
233
|
+
.then(() => {
|
|
234
|
+
this.spans.splice(0, spans.length);
|
|
235
|
+
})
|
|
236
|
+
.catch((error) => {
|
|
237
|
+
this.failed = this.stopped = true;
|
|
238
|
+
this.failure = error;
|
|
239
|
+
this.spans.length = 0;
|
|
240
|
+
this.measurements.clear();
|
|
241
|
+
clearInterval(this.timer);
|
|
242
|
+
throw error;
|
|
243
|
+
})
|
|
244
|
+
.finally(() => {
|
|
245
|
+
this.writing = undefined;
|
|
246
|
+
this.inFlightMeasurements = 0;
|
|
247
|
+
});
|
|
248
|
+
await this.writing;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** Portable, buffered SQLite recorder. Recording never performs SQL; flush runs every second.
|
|
252
|
+
* Only settled spans are stored. Metrics aggregate count/sum/min/max per ten-second interval.
|
|
253
|
+
* A write failure permanently stops recording and is reported by flush and close. */
|
|
254
|
+
export async function createSqliteTelemetry(db, options = {}) {
|
|
255
|
+
const retention = options.retentionMs ?? 7 * 24 * 60 * 60 * 1000;
|
|
256
|
+
const capacity = options.maxQueuedSpans ?? 10_000;
|
|
257
|
+
await applySqliteMigrations(db, [
|
|
258
|
+
{
|
|
259
|
+
version: 1,
|
|
260
|
+
statements: [
|
|
261
|
+
`CREATE TABLE telemetry_spans (id TEXT PRIMARY KEY, trace_id TEXT NOT NULL, parent_id TEXT, name TEXT, started_ms REAL,
|
|
262
|
+
ended_ms REAL NOT NULL, attributes TEXT, events TEXT, links TEXT, status TEXT)`,
|
|
263
|
+
"CREATE INDEX telemetry_spans_ended_ms ON telemetry_spans(ended_ms)",
|
|
264
|
+
"CREATE INDEX telemetry_spans_trace_started ON telemetry_spans(trace_id, started_ms)",
|
|
265
|
+
"CREATE INDEX telemetry_spans_started_ms ON telemetry_spans(started_ms)",
|
|
266
|
+
`CREATE TABLE telemetry_metrics (interval_ms INTEGER, series_key TEXT, name TEXT, kind TEXT,
|
|
267
|
+
unit TEXT, attributes TEXT, count INTEGER, sum REAL, min REAL, max REAL, PRIMARY KEY(interval_ms, series_key))`,
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
], { table: "telemetry_schema" });
|
|
271
|
+
return new Recorder(db, retention, capacity);
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/telemetry-sqlite/index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype InstrumentOptions,\n\tNOOP_TELEMETRY_CONTEXT,\n\ttype SpanAttributes,\n\ttype SpanLink,\n\ttype SpanOptions,\n\ttype SpanStatus,\n\ttype TelemetryCarrier,\n\ttype TelemetryContext,\n\ttype TelemetryMetrics,\n\ttype TelemetryPropagation,\n\ttype TelemetrySpan,\n\ttype TelemetryStore,\n} from \"@earendil-works/pi-telemetry\";\nimport { applySqliteMigrations, type SqliteDatabase, type SqliteValue } from \"../sqlite/index.ts\";\nimport { findMetrics, findTraces, getTrace } from \"./store.ts\";\n\nexport interface SqliteTelemetryOptions {\n\t/** Retain seven days by default. Cleanup runs in the flush transaction. */\n\treadonly retentionMs?: number;\n\t/** Settled spans awaiting commit, including the in-flight batch. Defaults to 10,000.\n\t * Also bounds pending metric interval/series pairs independently. */\n\treadonly maxQueuedSpans?: number;\n}\n\nexport interface SqliteTelemetry extends TelemetryContext, TelemetryStore {\n\treadonly metrics: TelemetryMetrics;\n\treadonly propagation: TelemetryPropagation;\n\treadonly droppedSpans: number;\n\treadonly droppedMeasurements: number;\n\t/** Persist settled spans and completed ten-second metric intervals. */\n\tflush(): Promise<void>;\n\t/** Stop recording and drain, including partial metric intervals. Does not close the database. */\n\tclose(): Promise<void>;\n}\n\ntype Identity = { id: string; traceId: string };\ntype Link = { trace_id: string; span_id: string; attributes?: SpanAttributes };\ntype Metric = {\n\tinterval: number;\n\tkey: string;\n\tname: string;\n\tkind: \"counter\" | \"upDownCounter\" | \"histogram\";\n\tunit: string | null;\n\tattributes: string;\n\tcount: number;\n\tsum: number;\n\tmin: number;\n\tmax: number;\n};\nconst identities = new WeakMap<TelemetryContext, Identity>();\nconst carrierKey = \"pi-sqlite-span\";\nconst traceKey = \"pi-sqlite-trace\";\nconst intervalMs = 10_000;\n\nfunction copyAttributes(attributes?: SpanAttributes): SpanAttributes {\n\treturn Object.fromEntries(\n\t\tObject.entries(attributes ?? {})\n\t\t\t.filter(([, value]) => value !== undefined)\n\t\t\t.map(([key, value]) => [key, Array.isArray(value) ? [...value] : value]),\n\t);\n}\n\nfunction copyLink(link: SpanLink): Link | undefined {\n\tconst identity = identities.get(link.context);\n\treturn identity\n\t\t? { trace_id: identity.traceId, span_id: identity.id, attributes: copyAttributes(link.attributes) }\n\t\t: undefined;\n}\n\nclass Recorder implements SqliteTelemetry, TelemetryPropagation {\n\treadonly propagation: TelemetryPropagation = this;\n\treadonly metrics: TelemetryMetrics = {\n\t\tcounter: (name, options) => ({ add: this.measurement(\"counter\", name, options) }),\n\t\tupDownCounter: (name, options) => ({ add: this.measurement(\"upDownCounter\", name, options) }),\n\t\thistogram: (name, options) => ({ record: this.measurement(\"histogram\", name, options) }),\n\t};\n\tdroppedSpans = 0;\n\tdroppedMeasurements = 0;\n\tprivate readonly db: SqliteDatabase;\n\tprivate readonly retention: number;\n\tprivate readonly capacity: number;\n\tprivate readonly scope = crypto.randomUUID();\n\tprivate sequence = 0;\n\tprivate readonly spans: SqliteValue[][] = [];\n\tprivate readonly measurements = new Map<string, Metric>();\n\tprivate inFlightMeasurements = 0;\n\tprivate readonly timer: ReturnType<typeof setInterval>;\n\tprivate stopped = false;\n\tprivate failed = false;\n\tprivate failure: unknown;\n\tprivate writing?: Promise<void>;\n\tprivate closing?: Promise<void>;\n\n\tconstructor(db: SqliteDatabase, retention: number, capacity: number) {\n\t\tthis.db = db;\n\t\tthis.retention = retention;\n\t\tthis.capacity = capacity;\n\t\tthis.timer = setInterval(() => {\n\t\t\tif (!this.writing) void this.flush().catch(() => {});\n\t\t}, 1000);\n\t\tthis.timer.unref?.();\n\t}\n\n\tstartSpan<T>(options: SpanOptions, callback: (span: TelemetrySpan) => T | Promise<T>): Promise<T> {\n\t\treturn this.start(undefined, options, callback);\n\t}\n\n\tinject(context: TelemetryContext): TelemetryCarrier | undefined {\n\t\tconst identity = identities.get(context);\n\t\treturn identity ? { [carrierKey]: identity.id, [traceKey]: identity.traceId } : undefined;\n\t}\n\n\textract(carrier: TelemetryCarrier | undefined): TelemetryContext {\n\t\tconst id = carrier?.[carrierKey];\n\t\tconst traceId = carrier?.[traceKey];\n\t\tif (typeof id !== \"string\" || typeof traceId !== \"string\") return this;\n\t\tconst identity = { id, traceId };\n\t\tconst parent: TelemetryContext = {\n\t\t\tmetrics: this.metrics,\n\t\t\tpropagation: this,\n\t\t\tstartSpan: (options, callback) => this.start(identity, options, callback),\n\t\t};\n\t\tidentities.set(parent, identity);\n\t\treturn parent;\n\t}\n\n\tprivate inert<T>(options: SpanOptions, callback: (span: TelemetrySpan) => T | Promise<T>): Promise<T> {\n\t\treturn NOOP_TELEMETRY_CONTEXT.startSpan(options, (noop) =>\n\t\t\tcallback({\n\t\t\t\t...noop,\n\t\t\t\tmetrics: this.metrics,\n\t\t\t\tpropagation: this,\n\t\t\t\tstartSpan: (options, callback) => this.inert(options, callback),\n\t\t\t}),\n\t\t);\n\t}\n\n\tprivate async start<T>(\n\t\tparent: Identity | undefined,\n\t\toptions: SpanOptions,\n\t\tcallback: (span: TelemetrySpan) => T | Promise<T>,\n\t): Promise<T> {\n\t\tif (this.stopped) return this.inert(options, callback);\n\t\tconst name = options.name;\n\t\tlet attributes = copyAttributes(options.attributes);\n\t\tconst links: Link[] = [];\n\t\tfor (const link of options.links ?? []) {\n\t\t\tconst copied = copyLink(link);\n\t\t\tif (copied) links.push(copied);\n\t\t}\n\t\tconst id = `${this.scope}:${++this.sequence}`;\n\t\tconst identity = { id, traceId: parent?.traceId ?? id };\n\t\tconst started = Date.now();\n\t\tconst clock = performance.now();\n\t\tconst events: { name: string; time_ms: number; attributes: SpanAttributes }[] = [];\n\t\tlet status: SpanStatus = { status: \"ok\" };\n\t\tlet explicit = false;\n\t\tlet settled = false;\n\t\tconst record = (action: () => void): void => {\n\t\t\tif (!settled && !this.stopped) action();\n\t\t};\n\t\tconst span: TelemetrySpan = {\n\t\t\tmetrics: this.metrics,\n\t\t\tpropagation: this,\n\t\t\tstartSpan: (options, callback) =>\n\t\t\t\tsettled ? this.inert(options, callback) : this.start(identity, options, callback),\n\t\t\taddLink: (link) =>\n\t\t\t\trecord(() => {\n\t\t\t\t\tconst copied = copyLink(link);\n\t\t\t\t\tif (copied) links.push(copied);\n\t\t\t\t}),\n\t\t\taddEvent: (name, attrs) =>\n\t\t\t\trecord(() => {\n\t\t\t\t\tevents.push({ name, time_ms: performance.now() - clock, attributes: copyAttributes(attrs) });\n\t\t\t\t}),\n\t\t\tsetAttributes: (attrs) =>\n\t\t\t\trecord(() => {\n\t\t\t\t\tattributes = { ...attributes, ...copyAttributes(attrs) };\n\t\t\t\t}),\n\t\t\tsetStatus: (value) =>\n\t\t\t\trecord(() => {\n\t\t\t\t\tstatus =\n\t\t\t\t\t\tvalue.status === \"error\" && value.error\n\t\t\t\t\t\t\t? { status: \"error\", error: { ...value.error } }\n\t\t\t\t\t\t\t: { status: value.status };\n\t\t\t\t\texplicit = true;\n\t\t\t\t}),\n\t\t};\n\t\tidentities.set(span, identity);\n\t\ttry {\n\t\t\treturn await callback(span);\n\t\t} catch (error) {\n\t\t\tif (!explicit)\n\t\t\t\tstatus =\n\t\t\t\t\terror instanceof Error\n\t\t\t\t\t\t? { status: \"error\", error: { name: error.name, message: error.message } }\n\t\t\t\t\t\t: { status: \"error\" };\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tsettled = true;\n\t\t\tif (!this.stopped) {\n\t\t\t\tif (this.spans.length >= this.capacity) this.droppedSpans++;\n\t\t\t\telse\n\t\t\t\t\tthis.spans.push([\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tidentity.traceId,\n\t\t\t\t\t\tparent?.id ?? null,\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tstarted,\n\t\t\t\t\t\tstarted + performance.now() - clock,\n\t\t\t\t\t\tJSON.stringify(attributes),\n\t\t\t\t\t\tJSON.stringify(events),\n\t\t\t\t\t\tJSON.stringify(links),\n\t\t\t\t\t\tJSON.stringify(status),\n\t\t\t\t\t]);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate measurement(\n\t\tkind: Metric[\"kind\"],\n\t\tname: string,\n\t\toptions?: InstrumentOptions,\n\t): (value: number, attributes?: SpanAttributes) => void {\n\t\tconst unit = options?.unit ?? null;\n\t\treturn (value, attributes) => {\n\t\t\tif (this.stopped) return;\n\t\t\tconst copied = copyAttributes(attributes);\n\t\t\tconst encoded = JSON.stringify(\n\t\t\t\tObject.fromEntries(\n\t\t\t\t\tObject.keys(copied)\n\t\t\t\t\t\t.sort()\n\t\t\t\t\t\t.map((key) => [key, copied[key]]),\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst key = JSON.stringify([name, kind, unit, encoded]);\n\t\t\tconst interval = Math.floor(Date.now() / intervalMs) * intervalMs;\n\t\t\tconst pendingKey = `${interval}:${key}`;\n\t\t\tconst previous = this.measurements.get(pendingKey);\n\t\t\tif (!previous && this.measurements.size + this.inFlightMeasurements >= this.capacity) {\n\t\t\t\tthis.droppedMeasurements++;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.measurements.set(pendingKey, {\n\t\t\t\tinterval,\n\t\t\t\tkey,\n\t\t\t\tname,\n\t\t\t\tkind,\n\t\t\t\tunit,\n\t\t\t\tattributes: encoded,\n\t\t\t\tcount: (previous?.count ?? 0) + 1,\n\t\t\t\tsum: (previous?.sum ?? 0) + value,\n\t\t\t\tmin: Math.min(previous?.min ?? value, value),\n\t\t\t\tmax: Math.max(previous?.max ?? value, value),\n\t\t\t});\n\t\t};\n\t}\n\n\t/** Readers use committed rows only; flush first to include newly settled spans. */\n\treadonly getTrace: TelemetryStore[\"getTrace\"] = (traceId) => getTrace(this.db, traceId);\n\treadonly findTraces: TelemetryStore[\"findTraces\"] = (query) => findTraces(this.db, query);\n\treadonly findMetrics: TelemetryStore[\"findMetrics\"] = (query) => findMetrics(this.db, query);\n\n\tflush(): Promise<void> {\n\t\treturn this.closing ?? this.drain(false);\n\t}\n\n\tclose(): Promise<void> {\n\t\tif (!this.closing) {\n\t\t\tthis.stopped = true;\n\t\t\tclearInterval(this.timer);\n\t\t\tthis.closing = this.drain(true);\n\t\t}\n\t\treturn this.closing;\n\t}\n\n\tprivate async drain(partial: boolean): Promise<void> {\n\t\twhile (this.writing) await this.writing;\n\t\tif (this.failed) throw this.failure;\n\t\tconst spans = this.spans.slice();\n\t\tconst metrics: Metric[] = [];\n\t\tconst completed = Math.floor(Date.now() / intervalMs) * intervalMs;\n\t\tfor (const [key, metric] of this.measurements) {\n\t\t\tif (!partial && metric.interval >= completed) continue;\n\t\t\tmetrics.push(metric);\n\t\t\tthis.measurements.delete(key);\n\t\t}\n\t\tthis.inFlightMeasurements = metrics.length;\n\t\tthis.writing = Promise.resolve()\n\t\t\t.then(() =>\n\t\t\t\tthis.db.transaction(async (tx) => {\n\t\t\t\t\tfor (const span of spans)\n\t\t\t\t\t\tawait tx.run(\"INSERT INTO telemetry_spans VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\", ...span);\n\t\t\t\t\tfor (const metric of metrics)\n\t\t\t\t\t\tawait tx.run(\n\t\t\t\t\t\t\t`INSERT INTO telemetry_metrics VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n\t\t\t\tON CONFLICT(interval_ms, series_key) DO UPDATE SET count = count + excluded.count,\n\t\t\t\tsum = sum + excluded.sum, min = min(min, excluded.min), max = max(max, excluded.max)`,\n\t\t\t\t\t\t\tmetric.interval,\n\t\t\t\t\t\t\tmetric.key,\n\t\t\t\t\t\t\tmetric.name,\n\t\t\t\t\t\t\tmetric.kind,\n\t\t\t\t\t\t\tmetric.unit,\n\t\t\t\t\t\t\tmetric.attributes,\n\t\t\t\t\t\t\tmetric.count,\n\t\t\t\t\t\t\tmetric.sum,\n\t\t\t\t\t\t\tmetric.min,\n\t\t\t\t\t\t\tmetric.max,\n\t\t\t\t\t\t);\n\t\t\t\t\tconst cutoff = Date.now() - this.retention;\n\t\t\t\t\tawait tx.run(\"DELETE FROM telemetry_spans WHERE ended_ms < ?\", cutoff);\n\t\t\t\t\tawait tx.run(\"DELETE FROM telemetry_metrics WHERE interval_ms < ?\", cutoff);\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.then(() => {\n\t\t\t\tthis.spans.splice(0, spans.length);\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tthis.failed = this.stopped = true;\n\t\t\t\tthis.failure = error;\n\t\t\t\tthis.spans.length = 0;\n\t\t\t\tthis.measurements.clear();\n\t\t\t\tclearInterval(this.timer);\n\t\t\t\tthrow error;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.writing = undefined;\n\t\t\t\tthis.inFlightMeasurements = 0;\n\t\t\t});\n\t\tawait this.writing;\n\t}\n}\n\n/** Portable, buffered SQLite recorder. Recording never performs SQL; flush runs every second.\n * Only settled spans are stored. Metrics aggregate count/sum/min/max per ten-second interval.\n * A write failure permanently stops recording and is reported by flush and close. */\nexport async function createSqliteTelemetry(\n\tdb: SqliteDatabase,\n\toptions: SqliteTelemetryOptions = {},\n): Promise<SqliteTelemetry> {\n\tconst retention = options.retentionMs ?? 7 * 24 * 60 * 60 * 1000;\n\tconst capacity = options.maxQueuedSpans ?? 10_000;\n\tawait applySqliteMigrations(\n\t\tdb,\n\t\t[\n\t\t\t{\n\t\t\t\tversion: 1,\n\t\t\t\tstatements: [\n\t\t\t\t\t`CREATE TABLE telemetry_spans (id TEXT PRIMARY KEY, trace_id TEXT NOT NULL, parent_id TEXT, name TEXT, started_ms REAL,\n\t\t\tended_ms REAL NOT NULL, attributes TEXT, events TEXT, links TEXT, status TEXT)`,\n\t\t\t\t\t\"CREATE INDEX telemetry_spans_ended_ms ON telemetry_spans(ended_ms)\",\n\t\t\t\t\t\"CREATE INDEX telemetry_spans_trace_started ON telemetry_spans(trace_id, started_ms)\",\n\t\t\t\t\t\"CREATE INDEX telemetry_spans_started_ms ON telemetry_spans(started_ms)\",\n\t\t\t\t\t`CREATE TABLE telemetry_metrics (interval_ms INTEGER, series_key TEXT, name TEXT, kind TEXT,\n\t\t\tunit TEXT, attributes TEXT, count INTEGER, sum REAL, min REAL, max REAL, PRIMARY KEY(interval_ms, series_key))`,\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t\t{ table: \"telemetry_schema\" },\n\t);\n\treturn new Recorder(db, retention, capacity);\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MetricQuery, StoredMetric, StoredTrace, TraceQuery } from "@earendil-works/pi-telemetry";
|
|
2
|
+
import type { SqliteDatabase } from "../sqlite/index.ts";
|
|
3
|
+
export declare function getTrace(db: SqliteDatabase, traceId: string): Promise<StoredTrace | undefined>;
|
|
4
|
+
export declare function findTraces(db: SqliteDatabase, query?: TraceQuery): Promise<{
|
|
5
|
+
traces: readonly StoredTrace[];
|
|
6
|
+
cursor?: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function findMetrics(db: SqliteDatabase, query: MetricQuery): Promise<readonly StoredMetric[]>;
|
|
9
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/telemetry-sqlite/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,WAAW,EAGX,YAAY,EAEZ,WAAW,EACX,UAAU,EACV,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,oBAAoB,CAAC;AAwDtE,wBAAsB,QAAQ,CAAC,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAMpG;AAED,wBAAsB,UAAU,CAC/B,EAAE,EAAE,cAAc,EAClB,KAAK,GAAE,UAAe,GACpB,OAAO,CAAC;IAAE,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA0D9D;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAkC1G"}
|