@totemsdk/omnia-host 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DuckDbAnalyticsStore = exports.DisabledAnalyticsStore = void 0;
4
+ exports.initializeDuckDb = initializeDuckDb;
5
+ /**
6
+ * No-op analytics backend for phase 9 deployments without DuckDB enabled.
7
+ * The interface keeps analytics off the channel critical path.
8
+ */
9
+ class DisabledAnalyticsStore {
10
+ async append(_event) { }
11
+ async query(_kind, _limit) { return []; }
12
+ async close() { }
13
+ }
14
+ exports.DisabledAnalyticsStore = DisabledAnalyticsStore;
15
+ class DuckDbAnalyticsStore {
16
+ db;
17
+ constructor(db) {
18
+ this.db = db;
19
+ }
20
+ async append(event) {
21
+ await this.db.run(`INSERT INTO omnia_events (event_id, kind, occurred_at, channel_id, operation_id, payload_json)
22
+ VALUES (?, ?, ?, ?, ?, ?)`, event.eventId, event.kind, event.occurredAt, event.channelId ?? null, event.operationId ?? null, JSON.stringify(event.payload));
23
+ }
24
+ async query(kind, limit = 100) {
25
+ const rows = await this.db.all(kind
26
+ ? 'SELECT * FROM omnia_events WHERE kind = ? ORDER BY occurred_at DESC LIMIT ?'
27
+ : 'SELECT * FROM omnia_events ORDER BY occurred_at DESC LIMIT ?', ...(kind ? [kind, limit] : [limit]));
28
+ return rows.map((row) => ({
29
+ eventId: row.event_id,
30
+ kind: row.kind,
31
+ occurredAt: row.occurred_at,
32
+ channelId: row.channel_id,
33
+ operationId: row.operation_id,
34
+ payload: JSON.parse(row.payload_json),
35
+ }));
36
+ }
37
+ close() {
38
+ return this.db.close();
39
+ }
40
+ }
41
+ exports.DuckDbAnalyticsStore = DuckDbAnalyticsStore;
42
+ async function initializeDuckDb(db) {
43
+ await db.run(`
44
+ CREATE TABLE IF NOT EXISTS omnia_events (
45
+ event_id VARCHAR PRIMARY KEY,
46
+ kind VARCHAR NOT NULL,
47
+ occurred_at BIGINT NOT NULL,
48
+ channel_id VARCHAR,
49
+ operation_id VARCHAR,
50
+ payload_json VARCHAR NOT NULL
51
+ )
52
+ `);
53
+ }
@@ -0,0 +1,25 @@
1
+ export type OperationStatus = 'pending' | 'reserved' | 'executing' | 'committed' | 'failed' | 'unknown';
2
+ export interface OperationRecord {
3
+ operationId: string;
4
+ status: OperationStatus;
5
+ createdAt: number;
6
+ updatedAt: number;
7
+ request?: unknown;
8
+ requestDigest?: string;
9
+ result?: unknown;
10
+ error?: string;
11
+ }
12
+ /** Durable operation journal with atomic status transitions. */
13
+ export declare class OperationStore {
14
+ private readonly db;
15
+ constructor(dbPath: string);
16
+ get(operationId: string): OperationRecord | undefined;
17
+ create(operationId: string, request?: unknown, now?: number): OperationRecord;
18
+ verifyRequest(operationId: string, request: unknown): boolean;
19
+ transition(operationId: string, from: OperationStatus, to: OperationStatus, patch?: {
20
+ result?: unknown;
21
+ error?: string;
22
+ }, now?: number): OperationRecord;
23
+ listByStatus(status: OperationStatus): OperationRecord[];
24
+ close(): void;
25
+ }
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OperationStore = void 0;
7
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
8
+ const node_crypto_1 = require("node:crypto");
9
+ function encode(value) {
10
+ return value === undefined ? null : JSON.stringify(value);
11
+ }
12
+ function decode(value) {
13
+ return value === null ? undefined : JSON.parse(value);
14
+ }
15
+ function toRecord(row) {
16
+ return {
17
+ operationId: row.operation_id,
18
+ status: row.status,
19
+ createdAt: row.created_at,
20
+ updatedAt: row.updated_at,
21
+ request: decode(row.request_json),
22
+ requestDigest: row.request_digest ?? undefined,
23
+ result: decode(row.result_json),
24
+ error: row.error ?? undefined,
25
+ };
26
+ }
27
+ /** Durable operation journal with atomic status transitions. */
28
+ class OperationStore {
29
+ db;
30
+ constructor(dbPath) {
31
+ this.db = new better_sqlite3_1.default(dbPath);
32
+ this.db.pragma('journal_mode = WAL');
33
+ this.db.exec(`
34
+ CREATE TABLE IF NOT EXISTS omnia_operations (
35
+ operation_id TEXT PRIMARY KEY,
36
+ status TEXT NOT NULL,
37
+ created_at INTEGER NOT NULL,
38
+ updated_at INTEGER NOT NULL,
39
+ request_json TEXT,
40
+ request_digest TEXT,
41
+ result_json TEXT,
42
+ error TEXT
43
+ )
44
+ `);
45
+ const columns = this.db.prepare('PRAGMA table_info(omnia_operations)').all();
46
+ if (!columns.some((column) => column.name === 'request_digest')) {
47
+ this.db.exec('ALTER TABLE omnia_operations ADD COLUMN request_digest TEXT');
48
+ }
49
+ }
50
+ get(operationId) {
51
+ const row = this.db.prepare('SELECT * FROM omnia_operations WHERE operation_id = ?').get(operationId);
52
+ return row ? toRecord(row) : undefined;
53
+ }
54
+ create(operationId, request, now = Date.now()) {
55
+ this.db.prepare(`
56
+ INSERT OR IGNORE INTO omnia_operations
57
+ (operation_id, status, created_at, updated_at, request_json, request_digest)
58
+ VALUES (?, 'pending', ?, ?, ?, ?)
59
+ `).run(operationId, now, now, encode(request), digest(request));
60
+ const record = this.get(operationId);
61
+ if (!record)
62
+ throw new Error(`Failed to create operation ${operationId}`);
63
+ return record;
64
+ }
65
+ verifyRequest(operationId, request) {
66
+ const record = this.get(operationId);
67
+ return !record?.requestDigest || record.requestDigest === digest(request);
68
+ }
69
+ transition(operationId, from, to, patch = {}, now = Date.now()) {
70
+ const result = this.db.prepare(`
71
+ UPDATE omnia_operations
72
+ SET status = ?, updated_at = ?, result_json = ?, error = ?
73
+ WHERE operation_id = ? AND status = ?
74
+ `).run(to, now, encode(patch.result), patch.error ?? null, operationId, from);
75
+ if (result.changes !== 1) {
76
+ const current = this.get(operationId);
77
+ if (!current)
78
+ throw new Error(`Unknown operation ${operationId}`);
79
+ throw new Error(`Operation ${operationId} expected status ${from} but is ${current.status}`);
80
+ }
81
+ return this.get(operationId);
82
+ }
83
+ listByStatus(status) {
84
+ const rows = this.db.prepare('SELECT * FROM omnia_operations WHERE status = ? ORDER BY created_at').all(status);
85
+ return rows.map(toRecord);
86
+ }
87
+ close() {
88
+ this.db.close();
89
+ }
90
+ }
91
+ exports.OperationStore = OperationStore;
92
+ function canonical(value) {
93
+ if (value === null || typeof value !== 'object')
94
+ return JSON.stringify(value);
95
+ if (Array.isArray(value))
96
+ return `[${value.map(canonical).join(',')}]`;
97
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
98
+ }
99
+ function digest(value) {
100
+ return (0, node_crypto_1.createHash)('sha256').update(canonical(value)).digest('hex');
101
+ }
@@ -0,0 +1,21 @@
1
+ import { type OmniaChannel } from '@totemsdk/omnia';
2
+ /** SQLite-backed Map facade accepted by @totemsdk/omnia's ChannelStore type. */
3
+ export declare class SqliteChannelStore extends Map<string, OmniaChannel> {
4
+ private readonly db;
5
+ private readonly readStatement;
6
+ private readonly writeStatement;
7
+ private readonly deleteStatement;
8
+ constructor(dbPath: string);
9
+ get(channelId: string): OmniaChannel | undefined;
10
+ set(channelId: string, channel: OmniaChannel): this;
11
+ has(channelId: string): boolean;
12
+ delete(channelId: string): boolean;
13
+ clear(): void;
14
+ get size(): number;
15
+ entries(): MapIterator<[string, OmniaChannel]>;
16
+ keys(): MapIterator<string>;
17
+ values(): MapIterator<OmniaChannel>;
18
+ [Symbol.iterator](): MapIterator<[string, OmniaChannel]>;
19
+ forEach(callbackfn: (value: OmniaChannel, key: string, map: Map<string, OmniaChannel>) => void): void;
20
+ close(): void;
21
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SqliteChannelStore = void 0;
7
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
8
+ const omnia_1 = require("@totemsdk/omnia");
9
+ function iterator(values) {
10
+ let index = 0;
11
+ return {
12
+ next() {
13
+ if (index >= values.length)
14
+ return { done: true, value: undefined };
15
+ return { done: false, value: values[index++] };
16
+ },
17
+ [Symbol.iterator]() {
18
+ return this;
19
+ },
20
+ [Symbol.dispose]() {
21
+ index = values.length;
22
+ },
23
+ };
24
+ }
25
+ /** SQLite-backed Map facade accepted by @totemsdk/omnia's ChannelStore type. */
26
+ class SqliteChannelStore extends Map {
27
+ db;
28
+ readStatement;
29
+ writeStatement;
30
+ deleteStatement;
31
+ constructor(dbPath) {
32
+ super();
33
+ this.db = new better_sqlite3_1.default(dbPath);
34
+ this.db.pragma('journal_mode = WAL');
35
+ this.db.exec(`
36
+ CREATE TABLE IF NOT EXISTS omnia_channels (
37
+ channel_id TEXT PRIMARY KEY,
38
+ channel_json TEXT NOT NULL,
39
+ updated_at INTEGER NOT NULL
40
+ )
41
+ `);
42
+ this.readStatement = this.db.prepare('SELECT channel_json FROM omnia_channels WHERE channel_id = ?');
43
+ this.writeStatement = this.db.prepare(`
44
+ INSERT INTO omnia_channels (channel_id, channel_json, updated_at)
45
+ VALUES (?, ?, ?)
46
+ ON CONFLICT(channel_id) DO UPDATE SET
47
+ channel_json = excluded.channel_json,
48
+ updated_at = excluded.updated_at
49
+ `);
50
+ this.deleteStatement = this.db.prepare('DELETE FROM omnia_channels WHERE channel_id = ?');
51
+ }
52
+ get(channelId) {
53
+ const row = this.readStatement.get(channelId);
54
+ return row ? (0, omnia_1.recoverChannel)(row.channel_json) : undefined;
55
+ }
56
+ set(channelId, channel) {
57
+ this.writeStatement.run(channelId, (0, omnia_1.serializeChannelSnapshot)(channel), Date.now());
58
+ return this;
59
+ }
60
+ has(channelId) {
61
+ return this.readStatement.get(channelId) !== undefined;
62
+ }
63
+ delete(channelId) {
64
+ return this.deleteStatement.run(channelId).changes > 0;
65
+ }
66
+ clear() {
67
+ this.db.exec('DELETE FROM omnia_channels');
68
+ }
69
+ get size() {
70
+ const row = this.db.prepare('SELECT COUNT(*) AS count FROM omnia_channels').get();
71
+ return row.count;
72
+ }
73
+ entries() {
74
+ const rows = this.db.prepare('SELECT channel_id, channel_json FROM omnia_channels ORDER BY channel_id').iterate();
75
+ return iterator(Array.from(rows, (row) => [
76
+ row.channel_id,
77
+ (0, omnia_1.recoverChannel)(row.channel_json),
78
+ ]));
79
+ }
80
+ keys() {
81
+ return iterator(Array.from(this.entries(), ([channelId]) => channelId));
82
+ }
83
+ values() {
84
+ return iterator(Array.from(this.entries(), ([, channel]) => channel));
85
+ }
86
+ [Symbol.iterator]() {
87
+ return this.entries();
88
+ }
89
+ forEach(callbackfn) {
90
+ for (const [key, value] of this.entries())
91
+ callbackfn(value, key, this);
92
+ }
93
+ close() {
94
+ this.db.close();
95
+ }
96
+ }
97
+ exports.SqliteChannelStore = SqliteChannelStore;
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@totemsdk/omnia-host",
3
+ "version": "0.1.0",
4
+ "description": "Durable Omnia node daemon for channel lifecycle, routing, and control APIs",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "omnia-host": "dist/cli.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "require": "./dist/index.js",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "dependencies": {
23
+ "better-sqlite3": "^12.2.0",
24
+ "ws": "^8.21.1",
25
+ "@totemsdk/chain-provider": "^0.1.5",
26
+ "@totemsdk/omnia": "^0.1.9",
27
+ "@totemsdk/omnia-factory": "^0.1.8",
28
+ "@totemsdk/omnia-router": "^0.1.5",
29
+ "@totemsdk/omnia-vtxo": "^0.1.0",
30
+ "@totemsdk/minima-rpc": "^0.1.4",
31
+ "@totemsdk/wots-lease": "^0.1.5",
32
+ "@totemsdk/omnia-splice": "^0.1.7"
33
+ },
34
+ "devDependencies": {
35
+ "@types/jest": "^30.0.0",
36
+ "@types/better-sqlite3": "^7.6.13",
37
+ "@types/node": "^20.19.43",
38
+ "@types/ws": "^8.5.0",
39
+ "jest": "^30.4.2",
40
+ "ts-jest": "^29.4.11",
41
+ "typescript": "5.9.3"
42
+ },
43
+ "engines": {
44
+ "node": ">=20.0.0"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "license": "MIT",
50
+ "author": "Totem SDK",
51
+ "homepage": "https://totem.ing",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/totem-sdk/totem-sdk.git",
55
+ "directory": "packages/omnia-host"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/Totem-Edge/totem-sdk/issues"
59
+ },
60
+ "keywords": [
61
+ "totem",
62
+ "totemsdk",
63
+ "omnia",
64
+ "payment-channel",
65
+ "daemon",
66
+ "node"
67
+ ],
68
+ "scripts": {
69
+ "build": "tsc",
70
+ "clean": "rm -rf dist",
71
+ "test": "jest",
72
+ "start": "node dist/cli.js"
73
+ }
74
+ }