@breeztech/breez-sdk-spark 0.13.10-dev → 0.13.12-dev1

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.
Files changed (49) hide show
  1. package/breez-sdk-spark.tgz +0 -0
  2. package/bundler/breez_sdk_spark_wasm.d.ts +157 -0
  3. package/bundler/breez_sdk_spark_wasm.js +1 -1
  4. package/bundler/breez_sdk_spark_wasm_bg.js +419 -41
  5. package/bundler/breez_sdk_spark_wasm_bg.wasm +0 -0
  6. package/bundler/breez_sdk_spark_wasm_bg.wasm.d.ts +27 -7
  7. package/deno/breez_sdk_spark_wasm.d.ts +157 -0
  8. package/deno/breez_sdk_spark_wasm.js +419 -41
  9. package/deno/breez_sdk_spark_wasm_bg.wasm +0 -0
  10. package/deno/breez_sdk_spark_wasm_bg.wasm.d.ts +27 -7
  11. package/nodejs/breez_sdk_spark_wasm.d.ts +157 -0
  12. package/nodejs/breez_sdk_spark_wasm.js +428 -41
  13. package/nodejs/breez_sdk_spark_wasm_bg.wasm +0 -0
  14. package/nodejs/breez_sdk_spark_wasm_bg.wasm.d.ts +27 -7
  15. package/nodejs/index.js +56 -0
  16. package/nodejs/index.mjs +9 -0
  17. package/nodejs/mysql-session-manager/errors.cjs +13 -0
  18. package/nodejs/mysql-session-manager/index.cjs +144 -0
  19. package/nodejs/mysql-session-manager/migrations.cjs +102 -0
  20. package/nodejs/mysql-session-manager/package.json +9 -0
  21. package/nodejs/mysql-storage/errors.cjs +19 -0
  22. package/nodejs/mysql-storage/index.cjs +1367 -0
  23. package/nodejs/mysql-storage/migrations.cjs +387 -0
  24. package/nodejs/mysql-storage/package.json +9 -0
  25. package/nodejs/mysql-token-store/errors.cjs +9 -0
  26. package/nodejs/mysql-token-store/index.cjs +980 -0
  27. package/nodejs/mysql-token-store/migrations.cjs +255 -0
  28. package/nodejs/mysql-token-store/package.json +9 -0
  29. package/nodejs/mysql-tree-store/errors.cjs +9 -0
  30. package/nodejs/mysql-tree-store/index.cjs +941 -0
  31. package/nodejs/mysql-tree-store/migrations.cjs +221 -0
  32. package/nodejs/mysql-tree-store/package.json +9 -0
  33. package/nodejs/package.json +5 -0
  34. package/nodejs/postgres-session-manager/errors.cjs +13 -0
  35. package/nodejs/postgres-session-manager/index.cjs +165 -0
  36. package/nodejs/postgres-session-manager/migrations.cjs +126 -0
  37. package/nodejs/postgres-session-manager/package.json +9 -0
  38. package/nodejs/postgres-storage/index.cjs +147 -92
  39. package/nodejs/postgres-storage/migrations.cjs +85 -4
  40. package/nodejs/postgres-token-store/index.cjs +178 -102
  41. package/nodejs/postgres-token-store/migrations.cjs +92 -3
  42. package/nodejs/postgres-tree-store/index.cjs +168 -83
  43. package/nodejs/postgres-tree-store/migrations.cjs +80 -3
  44. package/package.json +1 -1
  45. package/ssr/index.js +53 -0
  46. package/web/breez_sdk_spark_wasm.d.ts +184 -7
  47. package/web/breez_sdk_spark_wasm.js +419 -41
  48. package/web/breez_sdk_spark_wasm_bg.wasm +0 -0
  49. package/web/breez_sdk_spark_wasm_bg.wasm.d.ts +27 -7
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Tree store migrations for MySQL 8.0+. Mirrors `postgres-tree-store/migrations.cjs`.
3
+ */
4
+
5
+ const { TreeStoreError } = require("./errors.cjs");
6
+
7
+ const TREE_MIGRATIONS_TABLE = "tree_schema_migrations";
8
+ const MIGRATION_LOCK_NAME = "breez_mysql_tree_store_migration_lock";
9
+ const MIGRATION_LOCK_TIMEOUT = 60;
10
+
11
+ /**
12
+ * Runs a single migration step. Plain strings are run as-is; tagged objects
13
+ * (`{ op: 'dropPrimaryKey', table }`) are guarded against partial-apply replay
14
+ * by checking `information_schema` first. MySQL DDL implicitly commits, so
15
+ * if the migration crashes between two DDL statements the version row never
16
+ * gets recorded — and on retry, an unguarded DROP PRIMARY KEY would fail
17
+ * (`ER_CANT_DROP_FIELD_OR_KEY`) because the PK is already gone.
18
+ */
19
+ async function runMigrationStep(conn, step) {
20
+ if (typeof step === "string") {
21
+ await conn.query(step);
22
+ return;
23
+ }
24
+ if (step.op === "dropPrimaryKey") {
25
+ const [rows] = await conn.query(
26
+ `SELECT COUNT(*) AS c FROM information_schema.table_constraints
27
+ WHERE table_schema = DATABASE()
28
+ AND table_name = ?
29
+ AND constraint_type = 'PRIMARY KEY'`,
30
+ [step.table]
31
+ );
32
+ if (rows[0].c > 0) {
33
+ await conn.query(`ALTER TABLE \`${step.table}\` DROP PRIMARY KEY`);
34
+ }
35
+ return;
36
+ }
37
+ throw new Error(`Unknown migration step op: ${JSON.stringify(step)}`);
38
+ }
39
+
40
+ class MysqlTreeStoreMigrationManager {
41
+ constructor(logger = null) {
42
+ this.logger = logger;
43
+ }
44
+
45
+ async migrate(pool, identity) {
46
+ const conn = await pool.getConnection();
47
+ try {
48
+ const [lockRows] = await conn.query(
49
+ "SELECT GET_LOCK(?, ?) AS acquired",
50
+ [MIGRATION_LOCK_NAME, MIGRATION_LOCK_TIMEOUT]
51
+ );
52
+ if (!lockRows || lockRows[0].acquired !== 1) {
53
+ throw new TreeStoreError(
54
+ `Failed to acquire tree store migration lock within ${MIGRATION_LOCK_TIMEOUT}s`
55
+ );
56
+ }
57
+
58
+ try {
59
+ await conn.query("START TRANSACTION");
60
+
61
+ await conn.query(`
62
+ CREATE TABLE IF NOT EXISTS \`${TREE_MIGRATIONS_TABLE}\` (
63
+ version INT PRIMARY KEY,
64
+ applied_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
65
+ )
66
+ `);
67
+
68
+ const [versionRows] = await conn.query(
69
+ `SELECT COALESCE(MAX(version), 0) AS version FROM \`${TREE_MIGRATIONS_TABLE}\``
70
+ );
71
+ const currentVersion = versionRows[0].version;
72
+
73
+ const migrations = this._getMigrations(identity);
74
+
75
+ if (currentVersion >= migrations.length) {
76
+ await conn.query("COMMIT");
77
+ return;
78
+ }
79
+
80
+ for (let i = currentVersion; i < migrations.length; i++) {
81
+ const migration = migrations[i];
82
+ const version = i + 1;
83
+ for (const step of migration.sql) {
84
+ await runMigrationStep(conn, step);
85
+ }
86
+ await conn.query(
87
+ `INSERT INTO \`${TREE_MIGRATIONS_TABLE}\` (version) VALUES (?)`,
88
+ [version]
89
+ );
90
+ }
91
+
92
+ await conn.query("COMMIT");
93
+ } catch (error) {
94
+ await conn.query("ROLLBACK").catch(() => {});
95
+ throw new TreeStoreError(
96
+ `Tree store migration failed: ${error.message}`,
97
+ error
98
+ );
99
+ } finally {
100
+ await conn
101
+ .query("SELECT RELEASE_LOCK(?)", [MIGRATION_LOCK_NAME])
102
+ .catch(() => {});
103
+ }
104
+ } finally {
105
+ conn.release();
106
+ }
107
+ }
108
+
109
+ _getMigrations(identity) {
110
+ const idHex = Buffer.from(identity).toString("hex");
111
+ const idLit = `UNHEX('${idHex}')`;
112
+
113
+ return [
114
+ {
115
+ name: "Create tree store tables",
116
+ sql: [
117
+ `CREATE TABLE IF NOT EXISTS tree_reservations (
118
+ id VARCHAR(255) NOT NULL PRIMARY KEY,
119
+ purpose VARCHAR(64) NOT NULL,
120
+ pending_change_amount BIGINT NOT NULL DEFAULT 0,
121
+ created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
122
+ )`,
123
+ `CREATE TABLE IF NOT EXISTS tree_leaves (
124
+ id VARCHAR(255) NOT NULL PRIMARY KEY,
125
+ status VARCHAR(64) NOT NULL,
126
+ is_missing_from_operators TINYINT(1) NOT NULL DEFAULT 0,
127
+ reservation_id VARCHAR(255) NULL,
128
+ data JSON NOT NULL,
129
+ created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
130
+ added_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
131
+ CONSTRAINT fk_tree_leaves_reservation FOREIGN KEY (reservation_id)
132
+ REFERENCES tree_reservations(id) ON DELETE SET NULL
133
+ )`,
134
+ `CREATE TABLE IF NOT EXISTS tree_spent_leaves (
135
+ leaf_id VARCHAR(255) NOT NULL PRIMARY KEY,
136
+ spent_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
137
+ )`,
138
+ `CREATE INDEX idx_tree_leaves_available
139
+ ON tree_leaves(status, is_missing_from_operators)`,
140
+ `CREATE INDEX idx_tree_leaves_reservation ON tree_leaves(reservation_id)`,
141
+ `CREATE INDEX idx_tree_leaves_added_at ON tree_leaves(added_at)`,
142
+ ],
143
+ },
144
+ {
145
+ name: "Add swap status tracking",
146
+ sql: [
147
+ `CREATE TABLE IF NOT EXISTS tree_swap_status (
148
+ id INT NOT NULL PRIMARY KEY DEFAULT 1,
149
+ last_completed_at DATETIME(6) NULL,
150
+ CHECK (id = 1)
151
+ )`,
152
+ `INSERT INTO tree_swap_status (id) VALUES (1)
153
+ ON DUPLICATE KEY UPDATE id = id`,
154
+ ],
155
+ },
156
+ {
157
+ name: "Promote leaf value to BIGINT column with covering index",
158
+ sql: [
159
+ `ALTER TABLE tree_leaves
160
+ ADD COLUMN value BIGINT NOT NULL DEFAULT 0`,
161
+ `UPDATE tree_leaves
162
+ SET value = CAST(JSON_UNQUOTE(JSON_EXTRACT(data, '$.value')) AS UNSIGNED)
163
+ WHERE value = 0`,
164
+ `CREATE INDEX idx_tree_leaves_slim
165
+ ON tree_leaves(status, is_missing_from_operators, reservation_id, value)`,
166
+ ],
167
+ },
168
+ {
169
+ name: "Multi-tenant scoping: add user_id and rewrite primary keys / FKs",
170
+ sql: [
171
+ // Drop the existing FK so we can rewrite the parent PK.
172
+ `ALTER TABLE tree_leaves DROP FOREIGN KEY fk_tree_leaves_reservation`,
173
+
174
+ // tree_reservations: scope by user_id.
175
+ `ALTER TABLE tree_reservations ADD COLUMN user_id VARBINARY(33) NULL`,
176
+ `UPDATE tree_reservations SET user_id = ${idLit} WHERE user_id IS NULL`,
177
+ `ALTER TABLE tree_reservations MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
178
+ `ALTER TABLE tree_reservations DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, id)`,
179
+
180
+ // tree_leaves: scope by user_id, rekey, re-add composite FK.
181
+ `ALTER TABLE tree_leaves ADD COLUMN user_id VARBINARY(33) NULL`,
182
+ `UPDATE tree_leaves SET user_id = ${idLit} WHERE user_id IS NULL`,
183
+ `ALTER TABLE tree_leaves MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
184
+ `ALTER TABLE tree_leaves DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, id)`,
185
+ `ALTER TABLE tree_leaves
186
+ ADD CONSTRAINT fk_tree_leaves_reservation_user
187
+ FOREIGN KEY (user_id, reservation_id)
188
+ REFERENCES tree_reservations(user_id, id)`,
189
+ `DROP INDEX idx_tree_leaves_available ON tree_leaves`,
190
+ `DROP INDEX idx_tree_leaves_reservation ON tree_leaves`,
191
+ `DROP INDEX idx_tree_leaves_added_at ON tree_leaves`,
192
+ `DROP INDEX idx_tree_leaves_slim ON tree_leaves`,
193
+ `CREATE INDEX idx_tree_leaves_user_available
194
+ ON tree_leaves(user_id, status, is_missing_from_operators)`,
195
+ `CREATE INDEX idx_tree_leaves_user_reservation
196
+ ON tree_leaves(user_id, reservation_id)`,
197
+ `CREATE INDEX idx_tree_leaves_user_added_at ON tree_leaves(user_id, added_at)`,
198
+ `CREATE INDEX idx_tree_leaves_user_slim
199
+ ON tree_leaves(user_id, status, is_missing_from_operators, reservation_id, value)`,
200
+
201
+ // tree_spent_leaves: scope by user_id.
202
+ `ALTER TABLE tree_spent_leaves ADD COLUMN user_id VARBINARY(33) NULL`,
203
+ `UPDATE tree_spent_leaves SET user_id = ${idLit} WHERE user_id IS NULL`,
204
+ `ALTER TABLE tree_spent_leaves MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
205
+ `ALTER TABLE tree_spent_leaves DROP PRIMARY KEY, ADD PRIMARY KEY (user_id, leaf_id)`,
206
+
207
+ // tree_swap_status was a singleton (PK id=1, CHECK id=1). Drop the PK
208
+ // and the id column, then re-key by user_id.
209
+ { op: "dropPrimaryKey", table: "tree_swap_status" },
210
+ `ALTER TABLE tree_swap_status DROP COLUMN id`,
211
+ `ALTER TABLE tree_swap_status ADD COLUMN user_id VARBINARY(33) NULL`,
212
+ `UPDATE tree_swap_status SET user_id = ${idLit} WHERE user_id IS NULL`,
213
+ `ALTER TABLE tree_swap_status MODIFY COLUMN user_id VARBINARY(33) NOT NULL`,
214
+ `ALTER TABLE tree_swap_status ADD PRIMARY KEY (user_id)`,
215
+ ],
216
+ },
217
+ ];
218
+ }
219
+ }
220
+
221
+ module.exports = { MysqlTreeStoreMigrationManager };
@@ -0,0 +1,9 @@
1
+ {
2
+ "dependencies": {
3
+ "mysql2": "^3.11.0"
4
+ },
5
+ "description": "Node.js MySQL tree store implementation for Breez SDK WASM (CommonJS)",
6
+ "main": "index.cjs",
7
+ "name": "@breez-sdk/mysql-tree-store",
8
+ "version": "1.0.0"
9
+ }
@@ -7,6 +7,11 @@
7
7
  "postgres-storage/",
8
8
  "postgres-tree-store/",
9
9
  "postgres-token-store/",
10
+ "postgres-session-manager/",
11
+ "mysql-storage/",
12
+ "mysql-tree-store/",
13
+ "mysql-token-store/",
14
+ "mysql-session-manager/",
10
15
  "index.js",
11
16
  "index.mjs"
12
17
  ],
@@ -0,0 +1,13 @@
1
+ // errors.cjs - Session manager error wrapper with cause chain support
2
+ class SessionManagerError extends Error {
3
+ constructor(message, cause = null) {
4
+ super(message);
5
+ this.name = 'SessionManagerError';
6
+ this.cause = cause;
7
+ if (Error.captureStackTrace) {
8
+ Error.captureStackTrace(this, SessionManagerError);
9
+ }
10
+ }
11
+ }
12
+
13
+ module.exports = { SessionManagerError };
@@ -0,0 +1,165 @@
1
+ /**
2
+ * CommonJS implementation for Node.js PostgreSQL Session Manager.
3
+ *
4
+ * Implements the JS-side `SessionManager` interface consumed by the Breez
5
+ * SDK WASM bindings: `getSession(serviceIdentityKey)` returns the cached
6
+ * session for the (tenant, service) pair or `null` when not found, and
7
+ * `setSession(serviceIdentityKey, session)` upserts a session.
8
+ *
9
+ * Tenant identity is bound at construction so multiple tenants can share
10
+ * a single Postgres database without leaking sessions across tenants.
11
+ */
12
+
13
+ let pg;
14
+ try {
15
+ const mainModule = require.main;
16
+ if (mainModule) {
17
+ pg = mainModule.require("pg");
18
+ } else {
19
+ pg = require("pg");
20
+ }
21
+ } catch (error) {
22
+ try {
23
+ pg = require("pg");
24
+ } catch (fallbackError) {
25
+ throw new Error(
26
+ `pg not found. Please install it in your project: npm install pg@^8.18.0\n` +
27
+ `Original error: ${error.message}\nFallback error: ${fallbackError.message}`
28
+ );
29
+ }
30
+ }
31
+
32
+ const { SessionManagerError } = require("./errors.cjs");
33
+ const { SessionManagerMigrationManager } = require("./migrations.cjs");
34
+
35
+ class PostgresSessionManager {
36
+ /**
37
+ * @param {import('pg').Pool} pool
38
+ * @param {Buffer|Uint8Array} identity - 33-byte secp256k1 compressed pubkey
39
+ * identifying the tenant. All reads and writes are scoped by this.
40
+ * @param {object} [logger]
41
+ */
42
+ constructor(pool, identity, logger = null) {
43
+ if (!identity || identity.length !== 33) {
44
+ throw new SessionManagerError(
45
+ "tenant identity (33-byte secp256k1 pubkey) is required"
46
+ );
47
+ }
48
+ this.pool = pool;
49
+ this.identity = Buffer.from(identity);
50
+ this.logger = logger;
51
+ }
52
+
53
+ async initialize() {
54
+ try {
55
+ const migrationManager = new SessionManagerMigrationManager(this.logger);
56
+ await migrationManager.migrate(this.pool);
57
+ return this;
58
+ } catch (error) {
59
+ throw new SessionManagerError(
60
+ `Failed to initialize PostgreSQL session manager: ${error.message}`,
61
+ error
62
+ );
63
+ }
64
+ }
65
+
66
+ async close() {
67
+ if (this.pool) {
68
+ await this.pool.end();
69
+ this.pool = null;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Returns the cached session for the given service identity key, or `null`
75
+ * if no session is cached. The Rust adapter maps `null` to
76
+ * `SessionManagerError::NotFound`.
77
+ * @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
78
+ * @returns {Promise<{token: string, expiration: number} | null>}
79
+ */
80
+ async getSession(serviceIdentityKey) {
81
+ const serviceKey = _decodePubkey(serviceIdentityKey);
82
+ try {
83
+ const { rows } = await this.pool.query(
84
+ `SELECT token, expiration FROM sessions
85
+ WHERE user_id = $1 AND service_identity_key = $2`,
86
+ [this.identity, serviceKey]
87
+ );
88
+ if (rows.length === 0) {
89
+ return null;
90
+ }
91
+ const row = rows[0];
92
+ return {
93
+ token: row.token,
94
+ expiration: Number(row.expiration),
95
+ };
96
+ } catch (error) {
97
+ throw new SessionManagerError(
98
+ `Failed to read session: ${error.message}`,
99
+ error
100
+ );
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Upserts a session for the given service identity key.
106
+ * @param {string} serviceIdentityKey - hex-encoded 33-byte secp256k1 pubkey
107
+ * @param {{token: string, expiration: number}} session
108
+ */
109
+ async setSession(serviceIdentityKey, session) {
110
+ const serviceKey = _decodePubkey(serviceIdentityKey);
111
+ try {
112
+ await this.pool.query(
113
+ `INSERT INTO sessions (user_id, service_identity_key, token, expiration)
114
+ VALUES ($1, $2, $3, $4)
115
+ ON CONFLICT (user_id, service_identity_key)
116
+ DO UPDATE SET token = EXCLUDED.token, expiration = EXCLUDED.expiration`,
117
+ [this.identity, serviceKey, session.token, session.expiration]
118
+ );
119
+ } catch (error) {
120
+ throw new SessionManagerError(
121
+ `Failed to write session: ${error.message}`,
122
+ error
123
+ );
124
+ }
125
+ }
126
+ }
127
+
128
+ function _decodePubkey(hex) {
129
+ if (typeof hex !== "string" || hex.length !== 66) {
130
+ throw new SessionManagerError(
131
+ "service_identity_key must be a 66-character hex-encoded 33-byte pubkey"
132
+ );
133
+ }
134
+ return Buffer.from(hex, "hex");
135
+ }
136
+
137
+ /**
138
+ * Convenience factory: creates a pool from a Pool config and returns an
139
+ * initialized `PostgresSessionManager`. Most callers should use
140
+ * `createPostgresSessionManagerWithPool` instead so the pool can be shared
141
+ * across stores.
142
+ */
143
+ async function createPostgresSessionManager(poolConfig, identity, logger = null) {
144
+ const pool = new pg.Pool(poolConfig);
145
+ const manager = new PostgresSessionManager(pool, identity, logger);
146
+ await manager.initialize();
147
+ return manager;
148
+ }
149
+
150
+ /**
151
+ * Wraps an existing pool — useful when sharing the pool with the storage,
152
+ * tree store, and token store implementations.
153
+ */
154
+ async function createPostgresSessionManagerWithPool(pool, identity, logger = null) {
155
+ const manager = new PostgresSessionManager(pool, identity, logger);
156
+ await manager.initialize();
157
+ return manager;
158
+ }
159
+
160
+ module.exports = {
161
+ PostgresSessionManager,
162
+ createPostgresSessionManager,
163
+ createPostgresSessionManagerWithPool,
164
+ SessionManagerError,
165
+ };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Database Migration Manager for Breez SDK PostgreSQL Session Manager.
3
+ *
4
+ * Uses a session_schema_migrations table + pg_advisory_xact_lock to safely
5
+ * run migrations from concurrent processes. Mirrors the schema produced by
6
+ * the Rust `PostgresSessionManager`.
7
+ */
8
+
9
+ const { SessionManagerError } = require("./errors.cjs");
10
+
11
+ /**
12
+ * Advisory lock ID for session-manager migrations.
13
+ * Uses a different lock ID from the storage / tree store / token store
14
+ * migrations to avoid contention. Derived from ASCII bytes of "SESN"
15
+ * (0x5345534E).
16
+ */
17
+ const MIGRATION_LOCK_ID = "1397245774"; // 0x5345534E as decimal string
18
+
19
+ class SessionManagerMigrationManager {
20
+ constructor(logger = null) {
21
+ this.logger = logger;
22
+ }
23
+
24
+ /**
25
+ * Run all pending migrations inside a single transaction with an advisory
26
+ * lock.
27
+ * @param {import('pg').Pool} pool
28
+ */
29
+ async migrate(pool) {
30
+ const client = await pool.connect();
31
+ try {
32
+ await client.query("BEGIN");
33
+ await client.query(`SELECT pg_advisory_xact_lock(${MIGRATION_LOCK_ID})`);
34
+
35
+ await client.query(`
36
+ CREATE TABLE IF NOT EXISTS session_schema_migrations (
37
+ version INTEGER PRIMARY KEY,
38
+ applied_at TIMESTAMPTZ DEFAULT NOW()
39
+ )
40
+ `);
41
+
42
+ const versionResult = await client.query(
43
+ "SELECT COALESCE(MAX(version), 0) AS version FROM session_schema_migrations"
44
+ );
45
+ const currentVersion = versionResult.rows[0].version;
46
+
47
+ const migrations = this._getMigrations();
48
+
49
+ if (currentVersion >= migrations.length) {
50
+ this._log(
51
+ "info",
52
+ `Session manager database is up to date (version ${currentVersion})`
53
+ );
54
+ await client.query("COMMIT");
55
+ return;
56
+ }
57
+
58
+ this._log(
59
+ "info",
60
+ `Migrating session manager database from version ${currentVersion} to ${migrations.length}`
61
+ );
62
+
63
+ for (let i = currentVersion; i < migrations.length; i++) {
64
+ const migration = migrations[i];
65
+ const version = i + 1;
66
+ this._log(
67
+ "debug",
68
+ `Running session manager migration ${version}: ${migration.name}`
69
+ );
70
+
71
+ for (const sql of migration.sql) {
72
+ await client.query(sql);
73
+ }
74
+
75
+ await client.query(
76
+ "INSERT INTO session_schema_migrations (version) VALUES ($1)",
77
+ [version]
78
+ );
79
+ }
80
+
81
+ await client.query("COMMIT");
82
+ this._log(
83
+ "info",
84
+ "Session manager database migration completed successfully"
85
+ );
86
+ } catch (error) {
87
+ await client.query("ROLLBACK").catch(() => {});
88
+ throw new SessionManagerError(
89
+ `Session manager migration failed: ${error.message}`,
90
+ error
91
+ );
92
+ } finally {
93
+ client.release();
94
+ }
95
+ }
96
+
97
+ _log(level, message) {
98
+ if (this.logger && typeof this.logger.log === "function") {
99
+ this.logger.log({ line: message, level });
100
+ } else if (level === "error") {
101
+ console.error(`[SessionManagerMigrationManager] ${message}`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Migrations matching the Rust PostgresSessionManager schema exactly.
107
+ */
108
+ _getMigrations() {
109
+ return [
110
+ {
111
+ name: "Create sessions table",
112
+ sql: [
113
+ `CREATE TABLE IF NOT EXISTS sessions (
114
+ user_id BYTEA NOT NULL,
115
+ service_identity_key BYTEA NOT NULL,
116
+ token TEXT NOT NULL,
117
+ expiration BIGINT NOT NULL,
118
+ PRIMARY KEY (user_id, service_identity_key)
119
+ )`,
120
+ ],
121
+ },
122
+ ];
123
+ }
124
+ }
125
+
126
+ module.exports = { SessionManagerMigrationManager };
@@ -0,0 +1,9 @@
1
+ {
2
+ "dependencies": {
3
+ "pg": "^8.18.0"
4
+ },
5
+ "description": "Node.js PostgreSQL session manager implementation for Breez SDK WASM (CommonJS)",
6
+ "main": "index.cjs",
7
+ "name": "@breez-sdk/postgres-session-manager",
8
+ "version": "1.0.0"
9
+ }