stellarium-ts 0.19.0 → 0.20.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/dist/client/operatorClient.d.ts +34 -3
- package/dist/client/operatorClient.d.ts.map +1 -1
- package/dist/client/operatorClient.js +15 -0
- package/dist/contract/repository.d.ts +49 -0
- package/dist/contract/repository.d.ts.map +1 -0
- package/dist/contract/repository.js +80 -0
- package/dist/contract/service.d.ts +103 -0
- package/dist/contract/service.d.ts.map +1 -0
- package/dist/contract/service.js +297 -0
- package/dist/contract/state.d.ts +43 -0
- package/dist/contract/state.d.ts.map +1 -0
- package/dist/contract/state.js +79 -0
- package/dist/db/database.d.ts +25 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +1 -0
- package/dist/db/db.d.ts +12 -4
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +97 -24
- package/dist/db/postgres.d.ts +39 -0
- package/dist/db/postgres.d.ts.map +1 -0
- package/dist/db/postgres.js +132 -0
- package/dist/db/sqlite.d.ts +16 -0
- package/dist/db/sqlite.d.ts.map +1 -0
- package/dist/db/sqlite.js +53 -0
- package/dist/grant/repository.d.ts +2 -2
- package/dist/grant/repository.d.ts.map +1 -1
- package/dist/grant/repository.js +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/peer/repository.d.ts +2 -2
- package/dist/peer/repository.d.ts.map +1 -1
- package/dist/peer/repository.js +1 -1
- package/dist/resource/invoker.d.ts +85 -0
- package/dist/resource/invoker.d.ts.map +1 -1
- package/dist/resource/invoker.js +69 -6
- package/dist/resource/repository.d.ts +51 -10
- package/dist/resource/repository.d.ts.map +1 -1
- package/dist/resource/repository.js +116 -61
- package/dist/resource/settlement.d.ts +5 -0
- package/dist/resource/settlement.d.ts.map +1 -1
- package/dist/resource/settlement.js +19 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +161 -25
- package/dist/server.d.ts +30 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +80 -5
- package/dist/service.d.ts +27 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +16 -1
- package/dist/startup.d.ts +2 -2
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +43 -34
- package/dist/tests/contract/repository.test.d.ts +2 -0
- package/dist/tests/contract/repository.test.d.ts.map +1 -0
- package/dist/tests/contract/repository.test.js +68 -0
- package/dist/tests/contract/routes.test.d.ts +2 -0
- package/dist/tests/contract/routes.test.d.ts.map +1 -0
- package/dist/tests/contract/routes.test.js +252 -0
- package/dist/tests/contract/state-routes.test.d.ts +2 -0
- package/dist/tests/contract/state-routes.test.d.ts.map +1 -0
- package/dist/tests/contract/state-routes.test.js +158 -0
- package/dist/tests/contract/state.test.d.ts +2 -0
- package/dist/tests/contract/state.test.d.ts.map +1 -0
- package/dist/tests/contract/state.test.js +57 -0
- package/dist/tests/db/db.test.js +1 -1
- package/dist/tests/db/postgres.test.d.ts +2 -0
- package/dist/tests/db/postgres.test.d.ts.map +1 -0
- package/dist/tests/db/postgres.test.js +43 -0
- package/dist/tests/grant/service.test.js +3 -4
- package/dist/tests/helpers.d.ts +8 -0
- package/dist/tests/helpers.d.ts.map +1 -1
- package/dist/tests/helpers.js +37 -0
- package/dist/tests/integration/network.test.js +74 -4
- package/dist/tests/peer/repository.test.js +3 -4
- package/dist/tests/resource/repository.test.js +3 -4
- package/dist/tests/routes.test.js +10 -11
- package/dist/tests/server.test.js +30 -0
- package/dist/tests/startup.test.js +4 -5
- package/package.json +5 -2
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { StateScope, StateWrite, StoredState } from "requestscript";
|
|
2
|
+
import type { NodeDatabase } from "../db/database.js";
|
|
3
|
+
export declare const DEFAULT_MAX_CONTRACT_STATE_BYTES: number;
|
|
4
|
+
/** How a scope is stored: '' for the shared copy, and a prefix keeping the scope's kind, so 3 and "3" differ. */
|
|
5
|
+
export declare function scopeKey(scope: StateScope): string;
|
|
6
|
+
export declare function scopeFromKey(key: string): StateScope;
|
|
7
|
+
export interface ContractStateRow {
|
|
8
|
+
variable: string;
|
|
9
|
+
scope: StateScope;
|
|
10
|
+
value: unknown;
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
}
|
|
13
|
+
/** Why `commit` saved nothing. */
|
|
14
|
+
export type CommitRefusal = {
|
|
15
|
+
reason: 'conflict';
|
|
16
|
+
} | {
|
|
17
|
+
reason: 'too_large';
|
|
18
|
+
scope: StateScope;
|
|
19
|
+
bytes: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The state variables of contracts, one row per variable and scope. A row's version counts its writes, so a
|
|
23
|
+
* call that read an older copy can't save over a newer one.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ContractStateRepository {
|
|
26
|
+
private readonly db;
|
|
27
|
+
constructor(db: NodeDatabase);
|
|
28
|
+
get(path: string, name: string, variable: string, scope: StateScope): Promise<StoredState | undefined>;
|
|
29
|
+
/**
|
|
30
|
+
* Saves every write of one call, or none. A write carrying the version its call read saves only over that
|
|
31
|
+
* version, and one without a version only where nothing is stored, so a copy changed during the call is a
|
|
32
|
+
* conflict. Each scope written to must stay within `maxBytes`, its variables' encoded values together.
|
|
33
|
+
*/
|
|
34
|
+
commit(path: string, name: string, writes: StateWrite[], maxBytes: number): Promise<{
|
|
35
|
+
ok: true;
|
|
36
|
+
} | ({
|
|
37
|
+
ok: false;
|
|
38
|
+
} & CommitRefusal)>;
|
|
39
|
+
/** Every variable and scope of a contract, with values as stored, ordered by variable then scope. */
|
|
40
|
+
list(path: string, name: string): Promise<ContractStateRow[]>;
|
|
41
|
+
deleteContract(path: string, name: string): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/contract/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,eAAO,MAAM,gCAAgC,QAAY,CAAC;AAE1D,iHAAiH;AACjH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAKlD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAKpD;AAED,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACrB;AAWD,kCAAkC;AAClC,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAQ/G;;;GAGG;AACH,qBAAa,uBAAuB;IACpB,OAAO,CAAC,QAAQ,CAAC,EAAE;IAA/B,YAA6B,EAAE,EAAE,YAAY,EAAI;IAE3C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAM3G;IAED;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG,CAAC;QAAE,EAAE,EAAE,KAAK,CAAA;KAAE,GAAG,aAAa,CAAC,CAAC,CAmCxI;IAED,qGAAqG;IAC/F,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAGlE;IAEK,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9D;CACJ"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export const DEFAULT_MAX_CONTRACT_STATE_BYTES = 64 * 1024;
|
|
2
|
+
/** How a scope is stored: '' for the shared copy, and a prefix keeping the scope's kind, so 3 and "3" differ. */
|
|
3
|
+
export function scopeKey(scope) {
|
|
4
|
+
if (scope === null) {
|
|
5
|
+
return '';
|
|
6
|
+
}
|
|
7
|
+
return `${scope.kind === 'string' ? 's' : 'i'}:${scope.key}`;
|
|
8
|
+
}
|
|
9
|
+
export function scopeFromKey(key) {
|
|
10
|
+
if (key === '') {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return { kind: key.startsWith('s:') ? 'string' : 'integer', key: key.slice(2) };
|
|
14
|
+
}
|
|
15
|
+
class CommitRefused extends Error {
|
|
16
|
+
refusal;
|
|
17
|
+
constructor(refusal) {
|
|
18
|
+
super(refusal.reason);
|
|
19
|
+
this.refusal = refusal;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The state variables of contracts, one row per variable and scope. A row's version counts its writes, so a
|
|
24
|
+
* call that read an older copy can't save over a newer one.
|
|
25
|
+
*/
|
|
26
|
+
export class ContractStateRepository {
|
|
27
|
+
db;
|
|
28
|
+
constructor(db) {
|
|
29
|
+
this.db = db;
|
|
30
|
+
}
|
|
31
|
+
async get(path, name, variable, scope) {
|
|
32
|
+
const row = await this.db.get('SELECT value, type, version FROM contract_state WHERE path = ? AND name = ? AND var_name = ? AND scope_key = ?', [path, name, variable, scopeKey(scope)]);
|
|
33
|
+
return row && { value: JSON.parse(row.value), type: row.type, version: row.version };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Saves every write of one call, or none. A write carrying the version its call read saves only over that
|
|
37
|
+
* version, and one without a version only where nothing is stored, so a copy changed during the call is a
|
|
38
|
+
* conflict. Each scope written to must stay within `maxBytes`, its variables' encoded values together.
|
|
39
|
+
*/
|
|
40
|
+
async commit(path, name, writes, maxBytes) {
|
|
41
|
+
try {
|
|
42
|
+
await this.db.transaction(async (tx) => {
|
|
43
|
+
const now = new Date().toISOString();
|
|
44
|
+
for (const write of writes) {
|
|
45
|
+
const key = scopeKey(write.scope);
|
|
46
|
+
const value = JSON.stringify(write.value);
|
|
47
|
+
const result = write.version === undefined
|
|
48
|
+
? await tx.run('INSERT INTO contract_state (path, name, var_name, scope_key, value, type, version, updated_at) VALUES (?, ?, ?, ?, ?, ?, 1, ?) ON CONFLICT DO NOTHING', [path, name, write.name, key, value, write.type, now])
|
|
49
|
+
: await tx.run('UPDATE contract_state SET value = ?, type = ?, version = version + 1, updated_at = ? WHERE path = ? AND name = ? AND var_name = ? AND scope_key = ? AND version = ?', [value, write.type, now, path, name, write.name, key, write.version]);
|
|
50
|
+
if (result.changes === 0) {
|
|
51
|
+
throw new CommitRefused({ reason: 'conflict' });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const key of new Set(writes.map(write => scopeKey(write.scope)))) {
|
|
55
|
+
const rows = await tx.all('SELECT value FROM contract_state WHERE path = ? AND name = ? AND scope_key = ?', [path, name, key]);
|
|
56
|
+
const bytes = rows.reduce((sum, row) => sum + Buffer.byteLength(row.value), 0);
|
|
57
|
+
if (bytes > maxBytes) {
|
|
58
|
+
throw new CommitRefused({ reason: 'too_large', scope: scopeFromKey(key), bytes });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error instanceof CommitRefused) {
|
|
65
|
+
return { ok: false, ...error.refusal };
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
return { ok: true };
|
|
70
|
+
}
|
|
71
|
+
/** Every variable and scope of a contract, with values as stored, ordered by variable then scope. */
|
|
72
|
+
async list(path, name) {
|
|
73
|
+
const rows = await this.db.all('SELECT var_name, scope_key, value, updated_at FROM contract_state WHERE path = ? AND name = ? ORDER BY var_name, scope_key', [path, name]);
|
|
74
|
+
return rows.map(row => ({ variable: row.var_name, scope: scopeFromKey(row.scope_key), value: JSON.parse(row.value), updatedAt: row.updated_at }));
|
|
75
|
+
}
|
|
76
|
+
async deleteContract(path, name) {
|
|
77
|
+
await this.db.run('DELETE FROM contract_state WHERE path = ? AND name = ?', [path, name]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type DatabaseDialect = 'sqlite' | 'postgres';
|
|
2
|
+
export interface RunResult {
|
|
3
|
+
/** Rows the statement inserted, updated or deleted. */
|
|
4
|
+
changes: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The node's database, SQLite or Postgres. SQL uses `?` placeholders on both, and sticks to what both
|
|
8
|
+
* accept: `ON CONFLICT` instead of `INSERT OR IGNORE`, and `RETURNING id` instead of the last insert id.
|
|
9
|
+
*/
|
|
10
|
+
export interface NodeDatabase {
|
|
11
|
+
readonly dialect: DatabaseDialect;
|
|
12
|
+
get<T = any>(sql: string, params?: unknown[]): Promise<T | undefined>;
|
|
13
|
+
all<T = any[]>(sql: string, params?: unknown[]): Promise<T>;
|
|
14
|
+
run(sql: string, params?: unknown[]): Promise<RunResult>;
|
|
15
|
+
/** Runs one or more statements without parameters. */
|
|
16
|
+
exec(sql: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Runs `work` in a transaction, committing if it resolves and rolling back if it throws. Transactions
|
|
19
|
+
* are serialized, so `work` must not start another one, and must query through `tx` only: on a database
|
|
20
|
+
* with one connection, such as PGlite, a query through the database waits for the transaction to end.
|
|
21
|
+
*/
|
|
22
|
+
transaction<T>(work: (tx: NodeDatabase) => Promise<T>): Promise<T>;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEpD,MAAM,WAAW,SAAS;IACtB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACtE,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACzD,sDAAsD;IACtD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC;;;;OAIG;IACH,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACnE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/db/db.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { NodeDatabase } from './database.js';
|
|
2
2
|
export declare const DEFAULT_DATABASE_PATH = "./requestscript.db";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export interface OpenDatabaseOptions {
|
|
4
|
+
/** Postgres only: the schema the node's tables live in, created if missing. Lets nodes share a database. */
|
|
5
|
+
schema?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function isPostgresUrl(location: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Opens the node database and brings its schema up to date. `location` is a `postgres://` or
|
|
10
|
+
* `postgresql://` URL, or a SQLite file. Pass ':memory:' for tests.
|
|
11
|
+
*/
|
|
12
|
+
export declare function openDatabase(location?: string, options?: OpenDatabaseOptions): Promise<NodeDatabase>;
|
|
13
|
+
export declare function createTables(db: NodeDatabase): Promise<void>;
|
|
6
14
|
//# sourceMappingURL=db.d.ts.map
|
package/dist/db/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,YAAY,EAAE,MAAM,eAAe,CAAC;AAInE,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAgB1D,MAAM,WAAW,mBAAmB;IAChC,4GAA4G;IAC5G,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,QAAQ,GAAE,MAA8B,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,YAAY,CAAC,CAWrI;AA0BD,wBAAsB,YAAY,CAAC,EAAE,EAAE,YAAY,iBAmLlD"}
|
package/dist/db/db.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { nodePostgresDriver, PostgresDatabase } from './postgres.js';
|
|
2
|
+
import { SqliteDatabase } from './sqlite.js';
|
|
3
3
|
export const DEFAULT_DATABASE_PATH = './requestscript.db';
|
|
4
4
|
/**
|
|
5
5
|
* Bumped whenever the schema changes in a way `CREATE TABLE IF NOT EXISTS` can't apply.
|
|
@@ -10,16 +10,53 @@ export const DEFAULT_DATABASE_PATH = './requestscript.db';
|
|
|
10
10
|
* Version 5 added `resource_function.kind` and per-token inference pricing, which made `cost_micro_usdc` nullable.
|
|
11
11
|
* Version 6 moved payments to Stripe: `*_micro_usdc` columns became `*_micro_usd`, and `recipient_address`
|
|
12
12
|
* became `recipient_organization_id`.
|
|
13
|
+
* Version 7 added contract functions: `resource_function.kind` 'contract', priced per million tokens in
|
|
14
|
+
* `resource_function_payment_manifest.price_micro_usd_per_million_tokens`.
|
|
13
15
|
*/
|
|
14
|
-
const SCHEMA_VERSION =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const SCHEMA_VERSION = 7;
|
|
17
|
+
export function isPostgresUrl(location) {
|
|
18
|
+
return location.startsWith('postgres://') || location.startsWith('postgresql://');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Opens the node database and brings its schema up to date. `location` is a `postgres://` or
|
|
22
|
+
* `postgresql://` URL, or a SQLite file. Pass ':memory:' for tests.
|
|
23
|
+
*/
|
|
24
|
+
export async function openDatabase(location = DEFAULT_DATABASE_PATH, options = {}) {
|
|
25
|
+
const db = isPostgresUrl(location)
|
|
26
|
+
? await PostgresDatabase.open(nodePostgresDriver(location, options.schema), options.schema)
|
|
27
|
+
: await SqliteDatabase.open(location);
|
|
28
|
+
try {
|
|
29
|
+
await createTables(db);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
await db.close();
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
19
35
|
return db;
|
|
20
36
|
}
|
|
37
|
+
/** Column types that differ between the dialects. */
|
|
38
|
+
function columnTypes(dialect) {
|
|
39
|
+
return dialect === 'sqlite'
|
|
40
|
+
? { id: 'INTEGER PRIMARY KEY AUTOINCREMENT', bigint: 'INTEGER' }
|
|
41
|
+
: { id: 'BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY', bigint: 'BIGINT' };
|
|
42
|
+
}
|
|
43
|
+
/** SQLite keeps the version in `PRAGMA user_version`; Postgres, which has no equivalent, in a one-row table. */
|
|
44
|
+
async function getSchemaVersion(db) {
|
|
45
|
+
if (db.dialect === 'sqlite') {
|
|
46
|
+
return (await db.get('PRAGMA user_version'))?.user_version ?? 0;
|
|
47
|
+
}
|
|
48
|
+
await db.exec('CREATE TABLE IF NOT EXISTS stellarium_schema (version INTEGER NOT NULL)');
|
|
49
|
+
return (await db.get('SELECT version FROM stellarium_schema'))?.version ?? 0;
|
|
50
|
+
}
|
|
51
|
+
async function setSchemaVersion(db, version) {
|
|
52
|
+
if (db.dialect === 'sqlite') {
|
|
53
|
+
await db.exec(`PRAGMA user_version = ${version}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await db.exec(`DELETE FROM stellarium_schema; INSERT INTO stellarium_schema (version) VALUES (${version})`);
|
|
57
|
+
}
|
|
21
58
|
export async function createTables(db) {
|
|
22
|
-
const
|
|
59
|
+
const version = await getSchemaVersion(db);
|
|
23
60
|
if (version < SCHEMA_VERSION) {
|
|
24
61
|
// Resource rows are a cache of what peers advertise, and are relearned on the next broadcast.
|
|
25
62
|
// SQLite can't drop a constraint in place, so rebuild them. Peers are kept.
|
|
@@ -30,9 +67,10 @@ export async function createTables(db) {
|
|
|
30
67
|
DROP TABLE IF EXISTS resource;
|
|
31
68
|
`);
|
|
32
69
|
}
|
|
70
|
+
const t = columnTypes(db.dialect);
|
|
33
71
|
await db.exec(`
|
|
34
72
|
CREATE TABLE IF NOT EXISTS peer (
|
|
35
|
-
id
|
|
73
|
+
id ${t.id},
|
|
36
74
|
base_url TEXT NOT NULL,
|
|
37
75
|
public_key TEXT NOT NULL,
|
|
38
76
|
name TEXT NOT NULL,
|
|
@@ -45,8 +83,8 @@ export async function createTables(db) {
|
|
|
45
83
|
);
|
|
46
84
|
|
|
47
85
|
CREATE TABLE IF NOT EXISTS resource (
|
|
48
|
-
id
|
|
49
|
-
peer_id
|
|
86
|
+
id ${t.id},
|
|
87
|
+
peer_id ${t.bigint},
|
|
50
88
|
path TEXT NOT NULL,
|
|
51
89
|
name TEXT NOT NULL,
|
|
52
90
|
base_url TEXT NOT NULL,
|
|
@@ -61,8 +99,8 @@ export async function createTables(db) {
|
|
|
61
99
|
);
|
|
62
100
|
|
|
63
101
|
CREATE TABLE IF NOT EXISTS resource_function (
|
|
64
|
-
id
|
|
65
|
-
resource_id
|
|
102
|
+
id ${t.id},
|
|
103
|
+
resource_id ${t.bigint} NOT NULL,
|
|
66
104
|
name TEXT NOT NULL,
|
|
67
105
|
return_type TEXT NOT NULL,
|
|
68
106
|
description TEXT,
|
|
@@ -77,8 +115,8 @@ export async function createTables(db) {
|
|
|
77
115
|
);
|
|
78
116
|
|
|
79
117
|
CREATE TABLE IF NOT EXISTS resource_function_parameter (
|
|
80
|
-
id
|
|
81
|
-
resource_function_id
|
|
118
|
+
id ${t.id},
|
|
119
|
+
resource_function_id ${t.bigint} NOT NULL,
|
|
82
120
|
name TEXT NOT NULL,
|
|
83
121
|
parameter_type TEXT NOT NULL,
|
|
84
122
|
description TEXT,
|
|
@@ -91,12 +129,14 @@ export async function createTables(db) {
|
|
|
91
129
|
);
|
|
92
130
|
|
|
93
131
|
CREATE TABLE IF NOT EXISTS resource_function_payment_manifest (
|
|
94
|
-
id
|
|
95
|
-
resource_function_id
|
|
96
|
-
-- Null for inference functions, which are priced per million tokens instead.
|
|
97
|
-
cost_micro_usd
|
|
98
|
-
input_cost_micro_usd_per_million_tokens
|
|
99
|
-
output_cost_micro_usd_per_million_tokens
|
|
132
|
+
id ${t.id},
|
|
133
|
+
resource_function_id ${t.bigint} NOT NULL,
|
|
134
|
+
-- Null for inference and contract functions, which are priced per million tokens instead.
|
|
135
|
+
cost_micro_usd ${t.bigint},
|
|
136
|
+
input_cost_micro_usd_per_million_tokens ${t.bigint},
|
|
137
|
+
output_cost_micro_usd_per_million_tokens ${t.bigint},
|
|
138
|
+
-- Set for contract functions, which are charged for the tokens they execute.
|
|
139
|
+
price_micro_usd_per_million_tokens ${t.bigint},
|
|
100
140
|
-- The hosting node's organization. Null only if a peer advertised the manifest without one.
|
|
101
141
|
recipient_organization_id TEXT,
|
|
102
142
|
created_at TEXT NOT NULL,
|
|
@@ -115,7 +155,7 @@ export async function createTables(db) {
|
|
|
115
155
|
capabilities TEXT NOT NULL,
|
|
116
156
|
parent_grant_issuer TEXT,
|
|
117
157
|
parent_grant_id TEXT,
|
|
118
|
-
lifetime_ms
|
|
158
|
+
lifetime_ms ${t.bigint} NOT NULL,
|
|
119
159
|
renewable_until TEXT,
|
|
120
160
|
created_at TEXT NOT NULL
|
|
121
161
|
);
|
|
@@ -147,10 +187,43 @@ export async function createTables(db) {
|
|
|
147
187
|
-- publicKey:nonce of accepted authorizations and grant requests, until they expire.
|
|
148
188
|
CREATE TABLE IF NOT EXISTS seen_nonce (
|
|
149
189
|
key TEXT PRIMARY KEY,
|
|
150
|
-
expires_at
|
|
190
|
+
expires_at ${t.bigint} NOT NULL
|
|
151
191
|
);
|
|
152
192
|
CREATE INDEX IF NOT EXISTS seen_nonce_expires_at ON seen_nonce (expires_at);
|
|
153
193
|
|
|
194
|
+
-- Contracts deployed to this node. Versions are immutable. The latest version of each is one of the
|
|
195
|
+
-- node's resources, with a single function, run. An owner of null is the node's admin.
|
|
196
|
+
CREATE TABLE IF NOT EXISTS contract (
|
|
197
|
+
path TEXT NOT NULL,
|
|
198
|
+
name TEXT NOT NULL,
|
|
199
|
+
version TEXT NOT NULL,
|
|
200
|
+
source TEXT NOT NULL,
|
|
201
|
+
-- JSON: [{ name, type }]
|
|
202
|
+
parameters TEXT NOT NULL,
|
|
203
|
+
return_type TEXT NOT NULL,
|
|
204
|
+
owner_organization_id TEXT,
|
|
205
|
+
description TEXT,
|
|
206
|
+
created_at TEXT NOT NULL,
|
|
207
|
+
|
|
208
|
+
PRIMARY KEY (path, name, version)
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
-- The state variables of the contracts deployed to this node, kept across versions. scope_key is ''
|
|
212
|
+
-- for a variable's shared copy, and 's:<string>' or 'i:<integer>' for a scoped one. value is the
|
|
213
|
+
-- variable's encoded JSON, and version counts its writes, so a call can't save over a newer copy.
|
|
214
|
+
CREATE TABLE IF NOT EXISTS contract_state (
|
|
215
|
+
path TEXT NOT NULL,
|
|
216
|
+
name TEXT NOT NULL,
|
|
217
|
+
var_name TEXT NOT NULL,
|
|
218
|
+
scope_key TEXT NOT NULL,
|
|
219
|
+
value TEXT NOT NULL,
|
|
220
|
+
type TEXT NOT NULL,
|
|
221
|
+
version ${t.bigint} NOT NULL,
|
|
222
|
+
updated_at TEXT NOT NULL,
|
|
223
|
+
|
|
224
|
+
PRIMARY KEY (path, name, var_name, scope_key)
|
|
225
|
+
);
|
|
226
|
+
|
|
154
227
|
-- Keys an admin blocked from calling this node or getting grants from it.
|
|
155
228
|
CREATE TABLE IF NOT EXISTS blocked_caller (
|
|
156
229
|
public_key TEXT PRIMARY KEY,
|
|
@@ -158,5 +231,5 @@ export async function createTables(db) {
|
|
|
158
231
|
blocked_at TEXT NOT NULL
|
|
159
232
|
);
|
|
160
233
|
`);
|
|
161
|
-
await db
|
|
234
|
+
await setSchemaVersion(db, SCHEMA_VERSION);
|
|
162
235
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NodeDatabase, RunResult } from './database.js';
|
|
2
|
+
/** What `PostgresDatabase` needs from a Postgres client: node-postgres in production, PGlite in tests. */
|
|
3
|
+
export interface PostgresDriver {
|
|
4
|
+
query(text: string, params: unknown[]): Promise<{
|
|
5
|
+
rows: any[];
|
|
6
|
+
rowCount: number;
|
|
7
|
+
}>;
|
|
8
|
+
/** Runs one or more statements without parameters. */
|
|
9
|
+
exec(text: string): Promise<void>;
|
|
10
|
+
/** Runs `work` on one connection inside BEGIN and COMMIT, or ROLLBACK if it throws. */
|
|
11
|
+
transaction<T>(work: (driver: PostgresDriver) => Promise<T>): Promise<T>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/** Schema names are interpolated into SQL, so only plain identifiers are accepted. */
|
|
15
|
+
export declare function assertSchemaName(schema: string): void;
|
|
16
|
+
/** Rewrites `?` placeholders to `$1`, `$2`, ..., leaving quoted strings and identifiers alone. */
|
|
17
|
+
export declare function numberPlaceholders(sql: string): string;
|
|
18
|
+
/** A node-postgres pool. 64-bit integers come back as numbers, like SQLite's. */
|
|
19
|
+
export declare function nodePostgresDriver(url: string, schema?: string): PostgresDriver;
|
|
20
|
+
export declare class PostgresDatabase implements NodeDatabase {
|
|
21
|
+
private readonly driver;
|
|
22
|
+
private readonly inTransaction;
|
|
23
|
+
readonly dialect = "postgres";
|
|
24
|
+
/** `inTransaction` is set on the handle a transaction's work receives. */
|
|
25
|
+
constructor(driver: PostgresDriver, inTransaction?: boolean);
|
|
26
|
+
/** Creates `schema` if it doesn't exist. The driver must already resolve names in it. */
|
|
27
|
+
static open(driver: PostgresDriver, schema?: string): Promise<PostgresDatabase>;
|
|
28
|
+
get<T>(sql: string, params?: unknown[]): Promise<T | undefined>;
|
|
29
|
+
all<T>(sql: string, params?: unknown[]): Promise<T>;
|
|
30
|
+
run(sql: string, params?: unknown[]): Promise<RunResult>;
|
|
31
|
+
exec(sql: string): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Transactions take one advisory lock for the node's schema, so they run one at a time as they do on
|
|
34
|
+
* SQLite. Their check-then-insert writes rely on that.
|
|
35
|
+
*/
|
|
36
|
+
transaction<T>(work: (tx: NodeDatabase) => Promise<T>): Promise<T>;
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=postgres.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/db/postgres.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE7D,0GAA0G;AAC1G,MAAM,WAAW,cAAc;IAC3B,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnF,sDAAsD;IACtD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,uFAAuF;IACvF,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACzE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAID,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAIrD;AAED,kGAAkG;AAClG,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAoBtD;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,cAAc,CA8C/E;AAED,qBAAa,gBAAiB,YAAW,YAAY;IAIrC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAkB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAHnF,QAAQ,CAAC,OAAO,cAAc;IAE9B,0EAA0E;IAC1E,YAA6B,MAAM,EAAE,cAAc,EAAmB,aAAa,UAAQ,EAAI;IAE/F,yFAAyF;IACzF,OAAa,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAMpF;IAEK,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAGxE;IAEK,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAG5D;IAEK,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAGjE;IAED,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/B;IAED;;;OAGG;IACH,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAQjE;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAErB;CACJ"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
const INT8_OID = 20;
|
|
3
|
+
/** Schema names are interpolated into SQL, so only plain identifiers are accepted. */
|
|
4
|
+
export function assertSchemaName(schema) {
|
|
5
|
+
if (!/^[a-z_][a-z0-9_]*$/.test(schema)) {
|
|
6
|
+
throw new Error(`Invalid database schema '${schema}': use lowercase letters, digits and underscores`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** Rewrites `?` placeholders to `$1`, `$2`, ..., leaving quoted strings and identifiers alone. */
|
|
10
|
+
export function numberPlaceholders(sql) {
|
|
11
|
+
let index = 0;
|
|
12
|
+
let quote;
|
|
13
|
+
let result = '';
|
|
14
|
+
for (const char of sql) {
|
|
15
|
+
if (quote) {
|
|
16
|
+
if (char === quote) {
|
|
17
|
+
quote = undefined;
|
|
18
|
+
}
|
|
19
|
+
result += char;
|
|
20
|
+
}
|
|
21
|
+
else if (char === "'" || char === '"') {
|
|
22
|
+
quote = char;
|
|
23
|
+
result += char;
|
|
24
|
+
}
|
|
25
|
+
else if (char === '?') {
|
|
26
|
+
result += `$${++index}`;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
result += char;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
/** A node-postgres pool. 64-bit integers come back as numbers, like SQLite's. */
|
|
35
|
+
export function nodePostgresDriver(url, schema) {
|
|
36
|
+
const pool = new pg.Pool({
|
|
37
|
+
connectionString: url,
|
|
38
|
+
// Every connection resolves unqualified names in the node's schema.
|
|
39
|
+
options: schema ? `-c search_path=${schema}` : undefined,
|
|
40
|
+
types: { getTypeParser: ((oid, format) => oid === INT8_OID ? Number : pg.types.getTypeParser(oid, format)) },
|
|
41
|
+
});
|
|
42
|
+
const onClient = (client) => ({
|
|
43
|
+
query: async (text, params) => {
|
|
44
|
+
const result = await client.query(text, params);
|
|
45
|
+
return { rows: result.rows, rowCount: result.rowCount ?? 0 };
|
|
46
|
+
},
|
|
47
|
+
exec: async (text) => {
|
|
48
|
+
await client.query(text);
|
|
49
|
+
},
|
|
50
|
+
transaction: () => {
|
|
51
|
+
throw new Error('Transactions cannot be nested');
|
|
52
|
+
},
|
|
53
|
+
close: async () => { },
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
query: async (text, params) => {
|
|
57
|
+
const result = await pool.query(text, params);
|
|
58
|
+
return { rows: result.rows, rowCount: result.rowCount ?? 0 };
|
|
59
|
+
},
|
|
60
|
+
exec: async (text) => {
|
|
61
|
+
await pool.query(text);
|
|
62
|
+
},
|
|
63
|
+
transaction: async (work) => {
|
|
64
|
+
const client = await pool.connect();
|
|
65
|
+
try {
|
|
66
|
+
await client.query('BEGIN');
|
|
67
|
+
try {
|
|
68
|
+
const result = await work(onClient(client));
|
|
69
|
+
await client.query('COMMIT');
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
await client.query('ROLLBACK');
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
client.release();
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
close: () => pool.end(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export class PostgresDatabase {
|
|
85
|
+
driver;
|
|
86
|
+
inTransaction;
|
|
87
|
+
dialect = 'postgres';
|
|
88
|
+
/** `inTransaction` is set on the handle a transaction's work receives. */
|
|
89
|
+
constructor(driver, inTransaction = false) {
|
|
90
|
+
this.driver = driver;
|
|
91
|
+
this.inTransaction = inTransaction;
|
|
92
|
+
}
|
|
93
|
+
/** Creates `schema` if it doesn't exist. The driver must already resolve names in it. */
|
|
94
|
+
static async open(driver, schema) {
|
|
95
|
+
if (schema) {
|
|
96
|
+
assertSchemaName(schema);
|
|
97
|
+
await driver.exec(`CREATE SCHEMA IF NOT EXISTS ${schema}`);
|
|
98
|
+
}
|
|
99
|
+
return new PostgresDatabase(driver);
|
|
100
|
+
}
|
|
101
|
+
async get(sql, params = []) {
|
|
102
|
+
const { rows } = await this.driver.query(numberPlaceholders(sql), params);
|
|
103
|
+
return rows[0];
|
|
104
|
+
}
|
|
105
|
+
async all(sql, params = []) {
|
|
106
|
+
const { rows } = await this.driver.query(numberPlaceholders(sql), params);
|
|
107
|
+
return rows;
|
|
108
|
+
}
|
|
109
|
+
async run(sql, params = []) {
|
|
110
|
+
const { rowCount } = await this.driver.query(numberPlaceholders(sql), params);
|
|
111
|
+
return { changes: rowCount };
|
|
112
|
+
}
|
|
113
|
+
exec(sql) {
|
|
114
|
+
return this.driver.exec(sql);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Transactions take one advisory lock for the node's schema, so they run one at a time as they do on
|
|
118
|
+
* SQLite. Their check-then-insert writes rely on that.
|
|
119
|
+
*/
|
|
120
|
+
transaction(work) {
|
|
121
|
+
if (this.inTransaction) {
|
|
122
|
+
throw new Error('Transactions cannot be nested');
|
|
123
|
+
}
|
|
124
|
+
return this.driver.transaction(async (driver) => {
|
|
125
|
+
await driver.query('SELECT pg_advisory_xact_lock(hashtext(current_schema()))', []);
|
|
126
|
+
return work(new PostgresDatabase(driver, true));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
close() {
|
|
130
|
+
return this.driver.close();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NodeDatabase, RunResult } from './database.js';
|
|
2
|
+
/** A SQLite file, or ':memory:'. */
|
|
3
|
+
export declare class SqliteDatabase implements NodeDatabase {
|
|
4
|
+
private readonly db;
|
|
5
|
+
readonly dialect = "sqlite";
|
|
6
|
+
private writes;
|
|
7
|
+
private constructor();
|
|
8
|
+
static open(filename: string): Promise<SqliteDatabase>;
|
|
9
|
+
get<T>(sql: string, params?: unknown[]): Promise<T | undefined>;
|
|
10
|
+
all<T>(sql: string, params?: unknown[]): Promise<T>;
|
|
11
|
+
run(sql: string, params?: unknown[]): Promise<RunResult>;
|
|
12
|
+
exec(sql: string): Promise<void>;
|
|
13
|
+
transaction<T>(work: (tx: NodeDatabase) => Promise<T>): Promise<T>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/db/sqlite.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE7D,oCAAoC;AACpC,qBAAa,cAAe,YAAW,YAAY;IAK3B,OAAO,CAAC,QAAQ,CAAC,EAAE;IAJvC,QAAQ,CAAC,OAAO,YAAY;IAE5B,OAAO,CAAC,MAAM,CAAuC;IAErD,OAAO,eAA8C;IAErD,OAAa,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAK3D;IAED,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAElE;IAED,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAEtD;IAEK,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAGjE;IAED,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/B;IAED,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAejE;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAErB;CACJ"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import sqlite3 from 'sqlite3';
|
|
4
|
+
import { open } from 'sqlite';
|
|
5
|
+
/** A SQLite file, or ':memory:'. */
|
|
6
|
+
export class SqliteDatabase {
|
|
7
|
+
db;
|
|
8
|
+
dialect = 'sqlite';
|
|
9
|
+
// The connection is shared, so transactions are serialized to keep them from nesting.
|
|
10
|
+
writes = Promise.resolve();
|
|
11
|
+
constructor(db) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
}
|
|
14
|
+
static async open(filename) {
|
|
15
|
+
if (filename !== ':memory:') {
|
|
16
|
+
mkdirSync(dirname(filename), { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
return new SqliteDatabase(await open({ filename, driver: sqlite3.Database }));
|
|
19
|
+
}
|
|
20
|
+
get(sql, params = []) {
|
|
21
|
+
return this.db.get(sql, params);
|
|
22
|
+
}
|
|
23
|
+
all(sql, params = []) {
|
|
24
|
+
return this.db.all(sql, params);
|
|
25
|
+
}
|
|
26
|
+
async run(sql, params = []) {
|
|
27
|
+
const result = await this.db.run(sql, params);
|
|
28
|
+
return { changes: result.changes ?? 0 };
|
|
29
|
+
}
|
|
30
|
+
exec(sql) {
|
|
31
|
+
return this.db.exec(sql);
|
|
32
|
+
}
|
|
33
|
+
transaction(work) {
|
|
34
|
+
const run = async () => {
|
|
35
|
+
await this.db.exec('BEGIN IMMEDIATE');
|
|
36
|
+
try {
|
|
37
|
+
const result = await work(this);
|
|
38
|
+
await this.db.exec('COMMIT');
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
await this.db.exec('ROLLBACK');
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const result = this.writes.then(run, run);
|
|
47
|
+
this.writes = result.catch(() => undefined);
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
close() {
|
|
51
|
+
return this.db.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NodeDatabase } from "../db/database.js";
|
|
2
2
|
import type { Capability, Grant } from "./grant.js";
|
|
3
3
|
export interface IssuedGrant {
|
|
4
4
|
id: string;
|
|
@@ -25,7 +25,7 @@ export interface GrantKey {
|
|
|
25
25
|
/** Grants this node issued, grants it holds, grants it revoked, nonces it has seen, and caller keys it blocked. */
|
|
26
26
|
export declare class GrantRepository {
|
|
27
27
|
private readonly db;
|
|
28
|
-
constructor(db:
|
|
28
|
+
constructor(db: NodeDatabase);
|
|
29
29
|
saveIssued(grant: IssuedGrant): Promise<void>;
|
|
30
30
|
getIssued(id: string): Promise<IssuedGrant | undefined>;
|
|
31
31
|
/** Saves a chain this node holds, replacing an earlier copy of the same leaf grant. */
|