@totemsdk/omnia-host 0.11.0 → 0.12.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.
@@ -1,5 +1,6 @@
1
1
  import type { OmniaHostConfig } from './config.js';
2
2
  import { ObservationBus } from './observe/bus.js';
3
+ import { SyncSqliteStore } from '@totemsdk/storage/sqlite-sync';
3
4
  import type { ObservationStore } from './observe/types.js';
4
5
  import type { OmniaSwarm, ChannelSigner, WotsLeaseProviderLike, MinimalChainProvider } from '@totemsdk/omnia';
5
6
  import { createOmniaSwarm } from '@totemsdk/omnia';
@@ -55,6 +56,8 @@ export interface OmniaHostDependencies {
55
56
  /** Observation feed: inject a store/bus (tests/embedding) or disable it. */
56
57
  observationStore?: ObservationStore;
57
58
  observationBus?: ObservationBus;
59
+ /** Synchronous storage adapter for durable factories/splices/VTXOs/pools. */
60
+ hostStateStore?: SyncSqliteStore;
58
61
  observe?: boolean;
59
62
  networkId?: string;
60
63
  sourceNodeId?: string;
package/dist/lifecycle.js CHANGED
@@ -9,6 +9,8 @@ const bus_js_1 = require("./observe/bus.js");
9
9
  const emitter_js_1 = require("./observe/emitter.js");
10
10
  const store_js_1 = require("./observe/store.js");
11
11
  const snapshot_js_1 = require("./observe/snapshot.js");
12
+ const sqlite_sync_1 = require("@totemsdk/storage/sqlite-sync");
13
+ const persistent_map_js_1 = require("./stores/persistent-map.js");
12
14
  const omnia_1 = require("@totemsdk/omnia");
13
15
  const sqlite_store_js_1 = require("./stores/sqlite-store.js");
14
16
  const operations_js_1 = require("./stores/operations.js");
@@ -37,6 +39,8 @@ function createOmniaHost(config, dependencies = {}) {
37
39
  let ownedOperationStore;
38
40
  let observationStore = dependencies.observationStore;
39
41
  let ownedObservationStore;
42
+ let hostStateStore = dependencies.hostStateStore;
43
+ let ownedHostStateStore;
40
44
  const observationBus = dependencies.observationBus ?? new bus_js_1.ObservationBus();
41
45
  let observation;
42
46
  let controlServer = dependencies.controlServer;
@@ -90,6 +94,16 @@ function createOmniaHost(config, dependencies = {}) {
90
94
  sourceNodeId: config.sourceNodeId ?? dependencies.sourceNodeId ?? config.localPartyId ?? 'omnia-host',
91
95
  });
92
96
  }
97
+ if (!hostStateStore) {
98
+ try {
99
+ ownedHostStateStore = new sqlite_sync_1.SyncSqliteStore(config.dbPath);
100
+ hostStateStore = ownedHostStateStore;
101
+ }
102
+ catch (err) {
103
+ console.error('[omnia-host] host-state store disabled:', err instanceof Error ? err.message : String(err));
104
+ }
105
+ }
106
+ const hostMap = (namespace, injected) => injected ?? (hostStateStore ? new persistent_map_js_1.PersistentMap(hostStateStore, namespace) : new Map());
93
107
  if (!swarm) {
94
108
  const relay = config.relay
95
109
  ? { mode: 'self-hosted', relayUrl: config.relay }
@@ -156,7 +170,7 @@ function createOmniaHost(config, dependencies = {}) {
156
170
  },
157
171
  }
158
172
  : undefined);
159
- const factories = dependencies.factories ?? (factoryBundles ? new Map() : undefined);
173
+ const factories = dependencies.factories ?? (factoryBundles ? hostMap('omnia:factory:') : undefined);
160
174
  const localBundle = localParticipant
161
175
  ? factoryBundles?.[localParticipant.partyId]
162
176
  : undefined;
@@ -169,8 +183,8 @@ function createOmniaHost(config, dependencies = {}) {
169
183
  : {}),
170
184
  }
171
185
  : undefined);
172
- const spliceProposals = dependencies.spliceProposals ?? new Map();
173
- const spliceAcceptances = dependencies.spliceAcceptances ?? new Map();
186
+ const spliceProposals = hostMap('omnia:splice:proposal:', dependencies.spliceProposals);
187
+ const spliceAcceptances = hostMap('omnia:splice:acceptance:', dependencies.spliceAcceptances);
174
188
  const onSpliceMessage = async (peer, msg) => {
175
189
  if (!spliceLeaseProvider || !channelStore)
176
190
  return;
@@ -449,12 +463,12 @@ function createOmniaHost(config, dependencies = {}) {
449
463
  const preparedOpen = new Map();
450
464
  const preparedSplice = new Map();
451
465
  const preparedFactory = new Map();
452
- const vtxoPools = new Map();
453
- const vtxos = new Map();
454
- const vtxoReceipts = new Map();
466
+ const vtxoPools = hostMap('omnia:vtxo:pool:');
467
+ const vtxos = hostMap('omnia:vtxo:');
468
+ const vtxoReceipts = hostMap('omnia:vtxo:receipt:');
455
469
  const preparedVtxoReceipts = new Map();
456
- const pools = new Map();
457
- const poolPositions = new Map();
470
+ const pools = hostMap('omnia:pool:');
471
+ const poolPositions = hostMap('omnia:pool:position:');
458
472
  if (!controlServer) {
459
473
  const readOnly = !dependencies.signer || !dependencies.leaseProvider;
460
474
  if (readOnly) {
@@ -567,6 +581,8 @@ function createOmniaHost(config, dependencies = {}) {
567
581
  if (ownedObservationStore)
568
582
  await ownedObservationStore.close();
569
583
  ownedObservationStore = undefined;
584
+ ownedHostStateStore?.close();
585
+ ownedHostStateStore = undefined;
570
586
  ownedStore?.close();
571
587
  ownedStore = undefined;
572
588
  started = false;
@@ -0,0 +1,31 @@
1
+ import { SyncSqliteStore } from '@totemsdk/storage/sqlite-sync';
2
+ /**
3
+ * Durable Map facade for host state (factories, virtual channels, splices,
4
+ * VTXOs, pools). Backed by `@totemsdk/storage/sqlite-sync` (same `totemsdk_kv`
5
+ * table + versioned codec as the channel/operation stores).
6
+ *
7
+ * Values are plain objects/arrays/bigints/Uint8Arrays. Runtime-only handles
8
+ * (`localSigner`, signer/lease/swarm objects) are stripped; Map/Set/Date are
9
+ * explicitly tagged so they round-trip. A function anywhere else is dropped
10
+ * rather than persisted (never serialise authority material by accident).
11
+ */
12
+ export declare const HOST_STATE_FORMAT_KEY = "omnia:store:host-state:format";
13
+ export declare const HOST_STATE_FORMAT = 1;
14
+ export declare class PersistentMap<V> extends Map<string, V> {
15
+ private readonly kv;
16
+ private readonly namespace;
17
+ constructor(kv: SyncSqliteStore, namespace: string);
18
+ private key;
19
+ private keysInNamespace;
20
+ get(id: string): V | undefined;
21
+ set(id: string, value: V): this;
22
+ has(id: string): boolean;
23
+ delete(id: string): boolean;
24
+ clear(): void;
25
+ get size(): number;
26
+ keys(): MapIterator<string>;
27
+ values(): MapIterator<V>;
28
+ entries(): MapIterator<[string, V]>;
29
+ [Symbol.iterator](): MapIterator<[string, V]>;
30
+ forEach(callbackfn: (value: V, key: string, map: Map<string, V>) => void): void;
31
+ }
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PersistentMap = exports.HOST_STATE_FORMAT = exports.HOST_STATE_FORMAT_KEY = void 0;
4
+ /**
5
+ * Durable Map facade for host state (factories, virtual channels, splices,
6
+ * VTXOs, pools). Backed by `@totemsdk/storage/sqlite-sync` (same `totemsdk_kv`
7
+ * table + versioned codec as the channel/operation stores).
8
+ *
9
+ * Values are plain objects/arrays/bigints/Uint8Arrays. Runtime-only handles
10
+ * (`localSigner`, signer/lease/swarm objects) are stripped; Map/Set/Date are
11
+ * explicitly tagged so they round-trip. A function anywhere else is dropped
12
+ * rather than persisted (never serialise authority material by accident).
13
+ */
14
+ exports.HOST_STATE_FORMAT_KEY = 'omnia:store:host-state:format';
15
+ exports.HOST_STATE_FORMAT = 1;
16
+ const EPHEMERAL_KEYS = new Set([
17
+ 'localSigner',
18
+ 'signer',
19
+ 'leaseProvider',
20
+ 'signerPort',
21
+ 'swarm',
22
+ 'routing',
23
+ ]);
24
+ function encode(value, seen = new WeakSet()) {
25
+ if (value === null || value === undefined)
26
+ return null;
27
+ if (typeof value === 'bigint')
28
+ return value;
29
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
30
+ return value;
31
+ }
32
+ if (typeof value === 'function')
33
+ return null;
34
+ if (value instanceof Uint8Array)
35
+ return value;
36
+ if (value instanceof Date)
37
+ return { $date: value.toISOString() };
38
+ if (Array.isArray(value)) {
39
+ return value.map((item) => encode(item, seen));
40
+ }
41
+ if (value instanceof Map) {
42
+ const entries = [];
43
+ for (const [key, item] of value.entries())
44
+ entries.push([String(key), encode(item, seen)]);
45
+ return { $map: entries };
46
+ }
47
+ if (value instanceof Set) {
48
+ return { $set: Array.from(value.values(), (item) => encode(item, seen)) };
49
+ }
50
+ if (typeof value === 'object') {
51
+ if (seen.has(value))
52
+ throw new Error('cannot persist cyclic host state');
53
+ seen.add(value);
54
+ const out = {};
55
+ for (const [key, item] of Object.entries(value)) {
56
+ if (EPHEMERAL_KEYS.has(key) || item === undefined || typeof item === 'function')
57
+ continue;
58
+ out[key] = encode(item, seen);
59
+ }
60
+ seen.delete(value);
61
+ return out;
62
+ }
63
+ return null;
64
+ }
65
+ function decode(value) {
66
+ if (value === null)
67
+ return null;
68
+ if (typeof value === 'bigint' ||
69
+ typeof value === 'string' ||
70
+ typeof value === 'number' ||
71
+ typeof value === 'boolean') {
72
+ return value;
73
+ }
74
+ if (value instanceof Uint8Array)
75
+ return value;
76
+ if (Array.isArray(value))
77
+ return value.map(decode);
78
+ if (typeof value === 'object') {
79
+ const record = value;
80
+ if (Array.isArray(record['$map'])) {
81
+ return new Map(record['$map'].map(([key, item]) => [key, decode(item)]));
82
+ }
83
+ if (Array.isArray(record['$set'])) {
84
+ return new Set(record['$set'].map(decode));
85
+ }
86
+ if (typeof record['$date'] === 'string')
87
+ return new Date(record['$date']);
88
+ const out = {};
89
+ for (const [key, item] of Object.entries(record))
90
+ out[key] = decode(item);
91
+ return out;
92
+ }
93
+ return value;
94
+ }
95
+ function iterator(values) {
96
+ let index = 0;
97
+ return {
98
+ next() {
99
+ if (index >= values.length)
100
+ return { done: true, value: undefined };
101
+ return { done: false, value: values[index++] };
102
+ },
103
+ [Symbol.iterator]() {
104
+ return this;
105
+ },
106
+ [Symbol.dispose]() {
107
+ index = values.length;
108
+ },
109
+ };
110
+ }
111
+ class PersistentMap extends Map {
112
+ kv;
113
+ namespace;
114
+ constructor(kv, namespace) {
115
+ super();
116
+ this.kv = kv;
117
+ this.namespace = namespace;
118
+ const format = kv.get(exports.HOST_STATE_FORMAT_KEY);
119
+ if (format !== null && format !== exports.HOST_STATE_FORMAT) {
120
+ throw new Error(`Unsupported omnia-host state format ${format} (this build supports ${exports.HOST_STATE_FORMAT}) — refusing to open`);
121
+ }
122
+ if (format === null)
123
+ kv.set(exports.HOST_STATE_FORMAT_KEY, exports.HOST_STATE_FORMAT);
124
+ }
125
+ key(id) {
126
+ return this.namespace + id;
127
+ }
128
+ keysInNamespace() {
129
+ return this.kv.keys().filter((key) => key.startsWith(this.namespace));
130
+ }
131
+ get(id) {
132
+ const raw = this.kv.get(this.key(id));
133
+ return raw === null ? undefined : decode(raw);
134
+ }
135
+ set(id, value) {
136
+ this.kv.set(this.key(id), encode(value));
137
+ return this;
138
+ }
139
+ has(id) {
140
+ return this.kv.has(this.key(id));
141
+ }
142
+ delete(id) {
143
+ return this.kv.remove(this.key(id));
144
+ }
145
+ clear() {
146
+ for (const key of this.keysInNamespace())
147
+ this.kv.remove(key);
148
+ }
149
+ get size() {
150
+ return this.keysInNamespace().length;
151
+ }
152
+ keys() {
153
+ return iterator(this.keysInNamespace().map((key) => key.slice(this.namespace.length)));
154
+ }
155
+ values() {
156
+ return iterator(Array.from(this.entries(), ([, value]) => value));
157
+ }
158
+ entries() {
159
+ return iterator(this.keysInNamespace().map((key) => [
160
+ key.slice(this.namespace.length),
161
+ decode(this.kv.get(key)),
162
+ ]));
163
+ }
164
+ [Symbol.iterator]() {
165
+ return this.entries();
166
+ }
167
+ forEach(callbackfn) {
168
+ for (const [key, value] of this.entries())
169
+ callbackfn(value, key, this);
170
+ }
171
+ }
172
+ exports.PersistentMap = PersistentMap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@totemsdk/omnia-host",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Durable Omnia node daemon for channel lifecycle, routing, and control APIs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,20 +22,20 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "ws": "^8.21.1",
25
+ "@totemsdk/chain-provider": "^0.3.0",
25
26
  "@totemsdk/core": "^1.4.0",
26
27
  "@totemsdk/identity": "^1.0.2",
27
- "@totemsdk/chain-provider": "^0.3.0",
28
28
  "@totemsdk/liquidity-bond": "^0.3.1",
29
- "@totemsdk/manifest": "^2.0.1",
29
+ "@totemsdk/manifest": "^2.0.2",
30
30
  "@totemsdk/minima-rpc": "^0.4.0",
31
31
  "@totemsdk/omnia": "^2.3.1",
32
32
  "@totemsdk/omnia-factory": "^0.7.0",
33
33
  "@totemsdk/omnia-router": "^0.4.0",
34
- "@totemsdk/omnia-pool": "^0.5.3",
35
34
  "@totemsdk/omnia-splice": "^0.3.0",
35
+ "@totemsdk/omnia-pool": "^0.5.3",
36
36
  "@totemsdk/p2p-hub": "^0.1.0",
37
- "@totemsdk/omnia-vtxo": "^0.6.1",
38
37
  "@totemsdk/storage": "^0.2.0",
38
+ "@totemsdk/omnia-vtxo": "^0.6.1",
39
39
  "@totemsdk/wots-lease": "^1.0.1"
40
40
  },
41
41
  "devDependencies": {