@rei-standard/amsg-server 1.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,248 @@
1
+ import { Pool } from 'pg';
2
+ import { T as TABLE_SQL, I as INDEXES, V as VERIFY_TABLE_SQL, C as COLUMNS_SQL } from './index-BxrBvKHy.js';
3
+ import 'crypto';
4
+
5
+ /**
6
+ * PostgreSQL (pg) Database Adapter
7
+ * ReiStandard SDK v1.1.0
8
+ *
9
+ * Uses the standard 'pg' npm package.
10
+ *
11
+ * @implements {import('./interface.js').DbAdapter}
12
+ */
13
+
14
+
15
+ class PgAdapter {
16
+ /** @param {string} connectionString */
17
+ constructor(connectionString) {
18
+ /** @private */
19
+ this._connectionString = connectionString;
20
+ /** @private */
21
+ this._pool = null;
22
+ }
23
+
24
+ /** @private */
25
+ _getPool() {
26
+ if (!this._pool) {
27
+ this._pool = new Pool({ connectionString: this._connectionString });
28
+ }
29
+ return this._pool;
30
+ }
31
+
32
+ /** @private */
33
+ async _query(text, params) {
34
+ const pool = this._getPool();
35
+ const result = await pool.query(text, params);
36
+ return result.rows;
37
+ }
38
+
39
+ async initSchema() {
40
+ await this._query(TABLE_SQL);
41
+
42
+ const indexResults = [];
43
+ for (const index of INDEXES) {
44
+ try {
45
+ await this._query(index.sql);
46
+ indexResults.push({
47
+ name: index.name,
48
+ status: 'success',
49
+ description: index.description,
50
+ critical: !!index.critical
51
+ });
52
+ } catch (error) {
53
+ indexResults.push({
54
+ name: index.name,
55
+ status: 'failed',
56
+ description: index.description,
57
+ critical: !!index.critical,
58
+ error: error.message
59
+ });
60
+ }
61
+ }
62
+
63
+ const criticalFailures = indexResults.filter((index) => index.critical && index.status === 'failed');
64
+ if (criticalFailures.length > 0) {
65
+ const failedNames = criticalFailures.map((index) => index.name).join(', ');
66
+ throw new Error(
67
+ `Critical index creation failed (${failedNames}). ` +
68
+ 'Please remove duplicate UUID rows and run initSchema again.'
69
+ );
70
+ }
71
+
72
+ const tableCheck = await this._query(VERIFY_TABLE_SQL);
73
+ if (tableCheck.length === 0) {
74
+ throw new Error('Table creation verification failed');
75
+ }
76
+
77
+ const columns = await this._query(COLUMNS_SQL);
78
+
79
+ return {
80
+ columnsCreated: columns.length,
81
+ indexesCreated: indexResults.filter(r => r.status === 'success').length,
82
+ indexesFailed: indexResults.filter(r => r.status === 'failed').length,
83
+ columns: columns.map(c => ({ name: c.column_name, type: c.data_type, nullable: c.is_nullable === 'YES' })),
84
+ indexes: indexResults
85
+ };
86
+ }
87
+
88
+ async dropSchema() {
89
+ await this._query('DROP TABLE IF EXISTS scheduled_messages CASCADE');
90
+ }
91
+
92
+ async createTask(params) {
93
+ const rows = await this._query(
94
+ `INSERT INTO scheduled_messages
95
+ (user_id, uuid, encrypted_payload, next_send_at, message_type, status, retry_count, created_at, updated_at)
96
+ VALUES ($1, $2, $3, $4, $5, 'pending', 0, NOW(), NOW())
97
+ RETURNING id, uuid, next_send_at, status, created_at`,
98
+ [params.user_id, params.uuid, params.encrypted_payload, params.next_send_at, params.message_type]
99
+ );
100
+ return rows[0] || null;
101
+ }
102
+
103
+ async getTaskByUuid(uuid, userId) {
104
+ const rows = await this._query(
105
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
106
+ FROM scheduled_messages
107
+ WHERE uuid = $1 AND user_id = $2 AND status = 'pending'
108
+ LIMIT 1`,
109
+ [uuid, userId]
110
+ );
111
+ return rows[0] || null;
112
+ }
113
+
114
+ async getTaskByUuidOnly(uuid) {
115
+ const rows = await this._query(
116
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
117
+ FROM scheduled_messages
118
+ WHERE uuid = $1 AND status = 'pending'
119
+ LIMIT 1`,
120
+ [uuid]
121
+ );
122
+ return rows[0] || null;
123
+ }
124
+
125
+ async updateTaskById(taskId, updates) {
126
+ const sets = [];
127
+ const values = [];
128
+ let idx = 1;
129
+
130
+ for (const [key, value] of Object.entries(updates)) {
131
+ sets.push(`${key} = $${idx}`);
132
+ values.push(value);
133
+ idx++;
134
+ }
135
+
136
+ sets.push('updated_at = NOW()');
137
+ values.push(taskId);
138
+
139
+ const rows = await this._query(
140
+ `UPDATE scheduled_messages SET ${sets.join(', ')} WHERE id = $${idx} RETURNING *`,
141
+ values
142
+ );
143
+ return rows[0] || null;
144
+ }
145
+
146
+ async updateTaskByUuid(uuid, userId, encryptedPayload, extraFields) {
147
+ const sets = ['encrypted_payload = $1', 'updated_at = NOW()'];
148
+ const values = [encryptedPayload];
149
+ let idx = 2;
150
+
151
+ if (extraFields) {
152
+ for (const [key, value] of Object.entries(extraFields)) {
153
+ sets.push(`${key} = $${idx}`);
154
+ values.push(value);
155
+ idx++;
156
+ }
157
+ }
158
+
159
+ values.push(uuid, userId);
160
+ const rows = await this._query(
161
+ `UPDATE scheduled_messages SET ${sets.join(', ')}
162
+ WHERE uuid = $${idx} AND user_id = $${idx + 1} AND status = 'pending'
163
+ RETURNING uuid, updated_at`,
164
+ values
165
+ );
166
+ return rows[0] || null;
167
+ }
168
+
169
+ async deleteTaskById(taskId) {
170
+ const rows = await this._query('DELETE FROM scheduled_messages WHERE id = $1 RETURNING id', [taskId]);
171
+ return rows.length > 0;
172
+ }
173
+
174
+ async deleteTaskByUuid(uuid, userId) {
175
+ const rows = await this._query(
176
+ 'DELETE FROM scheduled_messages WHERE uuid = $1 AND user_id = $2 RETURNING id',
177
+ [uuid, userId]
178
+ );
179
+ return rows.length > 0;
180
+ }
181
+
182
+ async getPendingTasks(limit = 50) {
183
+ return this._query(
184
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
185
+ FROM scheduled_messages
186
+ WHERE status = 'pending' AND next_send_at <= NOW()
187
+ ORDER BY next_send_at ASC
188
+ LIMIT $1`,
189
+ [limit]
190
+ );
191
+ }
192
+
193
+ async listTasks(userId, opts = {}) {
194
+ const { status = 'all', limit = 20, offset = 0 } = opts;
195
+
196
+ const conditions = ['user_id = $1'];
197
+ const params = [userId];
198
+ let idx = 2;
199
+
200
+ if (status !== 'all') {
201
+ conditions.push(`status = $${idx}`);
202
+ params.push(status);
203
+ idx++;
204
+ }
205
+
206
+ const where = conditions.join(' AND ');
207
+
208
+ const countRows = await this._query(
209
+ `SELECT COUNT(*) as count FROM scheduled_messages WHERE ${where}`,
210
+ params
211
+ );
212
+ const total = parseInt(countRows[0].count, 10);
213
+
214
+ const taskParams = [...params, limit, offset];
215
+ const tasks = await this._query(
216
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
217
+ FROM scheduled_messages
218
+ WHERE ${where}
219
+ ORDER BY next_send_at ASC
220
+ LIMIT $${idx} OFFSET $${idx + 1}`,
221
+ taskParams
222
+ );
223
+
224
+ return { tasks, total };
225
+ }
226
+
227
+ async cleanupOldTasks(days = 7) {
228
+ const safeDays = Math.max(1, Math.floor(Number(days)));
229
+ const rows = await this._query(
230
+ `DELETE FROM scheduled_messages
231
+ WHERE status IN ('sent', 'failed')
232
+ AND updated_at < NOW() - make_interval(days => $1)
233
+ RETURNING id`,
234
+ [safeDays]
235
+ );
236
+ return rows.length;
237
+ }
238
+
239
+ async getTaskStatus(uuid, userId) {
240
+ const rows = await this._query(
241
+ 'SELECT status FROM scheduled_messages WHERE uuid = $1 AND user_id = $2 LIMIT 1',
242
+ [uuid, userId]
243
+ );
244
+ return rows.length > 0 ? rows[0].status : null;
245
+ }
246
+ }
247
+
248
+ export { PgAdapter };
@@ -0,0 +1,210 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+ var _chunkM2CNZRROcjs = require('./chunk-M2CNZRRO.cjs');
7
+
8
+ // src/server/adapters/pg.js
9
+ var _pg = require('pg');
10
+ var PgAdapter = class {
11
+ /** @param {string} connectionString */
12
+ constructor(connectionString) {
13
+ this._connectionString = connectionString;
14
+ this._pool = null;
15
+ }
16
+ /** @private */
17
+ _getPool() {
18
+ if (!this._pool) {
19
+ this._pool = new (0, _pg.Pool)({ connectionString: this._connectionString });
20
+ }
21
+ return this._pool;
22
+ }
23
+ /** @private */
24
+ async _query(text, params) {
25
+ const pool = this._getPool();
26
+ const result = await pool.query(text, params);
27
+ return result.rows;
28
+ }
29
+ async initSchema() {
30
+ await this._query(_chunkM2CNZRROcjs.TABLE_SQL);
31
+ const indexResults = [];
32
+ for (const index of _chunkM2CNZRROcjs.INDEXES) {
33
+ try {
34
+ await this._query(index.sql);
35
+ indexResults.push({
36
+ name: index.name,
37
+ status: "success",
38
+ description: index.description,
39
+ critical: !!index.critical
40
+ });
41
+ } catch (error) {
42
+ indexResults.push({
43
+ name: index.name,
44
+ status: "failed",
45
+ description: index.description,
46
+ critical: !!index.critical,
47
+ error: error.message
48
+ });
49
+ }
50
+ }
51
+ const criticalFailures = indexResults.filter((index) => index.critical && index.status === "failed");
52
+ if (criticalFailures.length > 0) {
53
+ const failedNames = criticalFailures.map((index) => index.name).join(", ");
54
+ throw new Error(
55
+ `Critical index creation failed (${failedNames}). Please remove duplicate UUID rows and run initSchema again.`
56
+ );
57
+ }
58
+ const tableCheck = await this._query(_chunkM2CNZRROcjs.VERIFY_TABLE_SQL);
59
+ if (tableCheck.length === 0) {
60
+ throw new Error("Table creation verification failed");
61
+ }
62
+ const columns = await this._query(_chunkM2CNZRROcjs.COLUMNS_SQL);
63
+ return {
64
+ columnsCreated: columns.length,
65
+ indexesCreated: indexResults.filter((r) => r.status === "success").length,
66
+ indexesFailed: indexResults.filter((r) => r.status === "failed").length,
67
+ columns: columns.map((c) => ({ name: c.column_name, type: c.data_type, nullable: c.is_nullable === "YES" })),
68
+ indexes: indexResults
69
+ };
70
+ }
71
+ async dropSchema() {
72
+ await this._query("DROP TABLE IF EXISTS scheduled_messages CASCADE");
73
+ }
74
+ async createTask(params) {
75
+ const rows = await this._query(
76
+ `INSERT INTO scheduled_messages
77
+ (user_id, uuid, encrypted_payload, next_send_at, message_type, status, retry_count, created_at, updated_at)
78
+ VALUES ($1, $2, $3, $4, $5, 'pending', 0, NOW(), NOW())
79
+ RETURNING id, uuid, next_send_at, status, created_at`,
80
+ [params.user_id, params.uuid, params.encrypted_payload, params.next_send_at, params.message_type]
81
+ );
82
+ return rows[0] || null;
83
+ }
84
+ async getTaskByUuid(uuid, userId) {
85
+ const rows = await this._query(
86
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
87
+ FROM scheduled_messages
88
+ WHERE uuid = $1 AND user_id = $2 AND status = 'pending'
89
+ LIMIT 1`,
90
+ [uuid, userId]
91
+ );
92
+ return rows[0] || null;
93
+ }
94
+ async getTaskByUuidOnly(uuid) {
95
+ const rows = await this._query(
96
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
97
+ FROM scheduled_messages
98
+ WHERE uuid = $1 AND status = 'pending'
99
+ LIMIT 1`,
100
+ [uuid]
101
+ );
102
+ return rows[0] || null;
103
+ }
104
+ async updateTaskById(taskId, updates) {
105
+ const sets = [];
106
+ const values = [];
107
+ let idx = 1;
108
+ for (const [key, value] of Object.entries(updates)) {
109
+ sets.push(`${key} = $${idx}`);
110
+ values.push(value);
111
+ idx++;
112
+ }
113
+ sets.push("updated_at = NOW()");
114
+ values.push(taskId);
115
+ const rows = await this._query(
116
+ `UPDATE scheduled_messages SET ${sets.join(", ")} WHERE id = $${idx} RETURNING *`,
117
+ values
118
+ );
119
+ return rows[0] || null;
120
+ }
121
+ async updateTaskByUuid(uuid, userId, encryptedPayload, extraFields) {
122
+ const sets = ["encrypted_payload = $1", "updated_at = NOW()"];
123
+ const values = [encryptedPayload];
124
+ let idx = 2;
125
+ if (extraFields) {
126
+ for (const [key, value] of Object.entries(extraFields)) {
127
+ sets.push(`${key} = $${idx}`);
128
+ values.push(value);
129
+ idx++;
130
+ }
131
+ }
132
+ values.push(uuid, userId);
133
+ const rows = await this._query(
134
+ `UPDATE scheduled_messages SET ${sets.join(", ")}
135
+ WHERE uuid = $${idx} AND user_id = $${idx + 1} AND status = 'pending'
136
+ RETURNING uuid, updated_at`,
137
+ values
138
+ );
139
+ return rows[0] || null;
140
+ }
141
+ async deleteTaskById(taskId) {
142
+ const rows = await this._query("DELETE FROM scheduled_messages WHERE id = $1 RETURNING id", [taskId]);
143
+ return rows.length > 0;
144
+ }
145
+ async deleteTaskByUuid(uuid, userId) {
146
+ const rows = await this._query(
147
+ "DELETE FROM scheduled_messages WHERE uuid = $1 AND user_id = $2 RETURNING id",
148
+ [uuid, userId]
149
+ );
150
+ return rows.length > 0;
151
+ }
152
+ async getPendingTasks(limit = 50) {
153
+ return this._query(
154
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
155
+ FROM scheduled_messages
156
+ WHERE status = 'pending' AND next_send_at <= NOW()
157
+ ORDER BY next_send_at ASC
158
+ LIMIT $1`,
159
+ [limit]
160
+ );
161
+ }
162
+ async listTasks(userId, opts = {}) {
163
+ const { status = "all", limit = 20, offset = 0 } = opts;
164
+ const conditions = ["user_id = $1"];
165
+ const params = [userId];
166
+ let idx = 2;
167
+ if (status !== "all") {
168
+ conditions.push(`status = $${idx}`);
169
+ params.push(status);
170
+ idx++;
171
+ }
172
+ const where = conditions.join(" AND ");
173
+ const countRows = await this._query(
174
+ `SELECT COUNT(*) as count FROM scheduled_messages WHERE ${where}`,
175
+ params
176
+ );
177
+ const total = parseInt(countRows[0].count, 10);
178
+ const taskParams = [...params, limit, offset];
179
+ const tasks = await this._query(
180
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
181
+ FROM scheduled_messages
182
+ WHERE ${where}
183
+ ORDER BY next_send_at ASC
184
+ LIMIT $${idx} OFFSET $${idx + 1}`,
185
+ taskParams
186
+ );
187
+ return { tasks, total };
188
+ }
189
+ async cleanupOldTasks(days = 7) {
190
+ const safeDays = Math.max(1, Math.floor(Number(days)));
191
+ const rows = await this._query(
192
+ `DELETE FROM scheduled_messages
193
+ WHERE status IN ('sent', 'failed')
194
+ AND updated_at < NOW() - make_interval(days => $1)
195
+ RETURNING id`,
196
+ [safeDays]
197
+ );
198
+ return rows.length;
199
+ }
200
+ async getTaskStatus(uuid, userId) {
201
+ const rows = await this._query(
202
+ "SELECT status FROM scheduled_messages WHERE uuid = $1 AND user_id = $2 LIMIT 1",
203
+ [uuid, userId]
204
+ );
205
+ return rows.length > 0 ? rows[0].status : null;
206
+ }
207
+ };
208
+
209
+
210
+ exports.PgAdapter = PgAdapter;
@@ -0,0 +1,210 @@
1
+ import {
2
+ COLUMNS_SQL,
3
+ INDEXES,
4
+ TABLE_SQL,
5
+ VERIFY_TABLE_SQL
6
+ } from "./chunk-YKLDHUZZ.mjs";
7
+
8
+ // src/server/adapters/pg.js
9
+ import { Pool } from "pg";
10
+ var PgAdapter = class {
11
+ /** @param {string} connectionString */
12
+ constructor(connectionString) {
13
+ this._connectionString = connectionString;
14
+ this._pool = null;
15
+ }
16
+ /** @private */
17
+ _getPool() {
18
+ if (!this._pool) {
19
+ this._pool = new Pool({ connectionString: this._connectionString });
20
+ }
21
+ return this._pool;
22
+ }
23
+ /** @private */
24
+ async _query(text, params) {
25
+ const pool = this._getPool();
26
+ const result = await pool.query(text, params);
27
+ return result.rows;
28
+ }
29
+ async initSchema() {
30
+ await this._query(TABLE_SQL);
31
+ const indexResults = [];
32
+ for (const index of INDEXES) {
33
+ try {
34
+ await this._query(index.sql);
35
+ indexResults.push({
36
+ name: index.name,
37
+ status: "success",
38
+ description: index.description,
39
+ critical: !!index.critical
40
+ });
41
+ } catch (error) {
42
+ indexResults.push({
43
+ name: index.name,
44
+ status: "failed",
45
+ description: index.description,
46
+ critical: !!index.critical,
47
+ error: error.message
48
+ });
49
+ }
50
+ }
51
+ const criticalFailures = indexResults.filter((index) => index.critical && index.status === "failed");
52
+ if (criticalFailures.length > 0) {
53
+ const failedNames = criticalFailures.map((index) => index.name).join(", ");
54
+ throw new Error(
55
+ `Critical index creation failed (${failedNames}). Please remove duplicate UUID rows and run initSchema again.`
56
+ );
57
+ }
58
+ const tableCheck = await this._query(VERIFY_TABLE_SQL);
59
+ if (tableCheck.length === 0) {
60
+ throw new Error("Table creation verification failed");
61
+ }
62
+ const columns = await this._query(COLUMNS_SQL);
63
+ return {
64
+ columnsCreated: columns.length,
65
+ indexesCreated: indexResults.filter((r) => r.status === "success").length,
66
+ indexesFailed: indexResults.filter((r) => r.status === "failed").length,
67
+ columns: columns.map((c) => ({ name: c.column_name, type: c.data_type, nullable: c.is_nullable === "YES" })),
68
+ indexes: indexResults
69
+ };
70
+ }
71
+ async dropSchema() {
72
+ await this._query("DROP TABLE IF EXISTS scheduled_messages CASCADE");
73
+ }
74
+ async createTask(params) {
75
+ const rows = await this._query(
76
+ `INSERT INTO scheduled_messages
77
+ (user_id, uuid, encrypted_payload, next_send_at, message_type, status, retry_count, created_at, updated_at)
78
+ VALUES ($1, $2, $3, $4, $5, 'pending', 0, NOW(), NOW())
79
+ RETURNING id, uuid, next_send_at, status, created_at`,
80
+ [params.user_id, params.uuid, params.encrypted_payload, params.next_send_at, params.message_type]
81
+ );
82
+ return rows[0] || null;
83
+ }
84
+ async getTaskByUuid(uuid, userId) {
85
+ const rows = await this._query(
86
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
87
+ FROM scheduled_messages
88
+ WHERE uuid = $1 AND user_id = $2 AND status = 'pending'
89
+ LIMIT 1`,
90
+ [uuid, userId]
91
+ );
92
+ return rows[0] || null;
93
+ }
94
+ async getTaskByUuidOnly(uuid) {
95
+ const rows = await this._query(
96
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
97
+ FROM scheduled_messages
98
+ WHERE uuid = $1 AND status = 'pending'
99
+ LIMIT 1`,
100
+ [uuid]
101
+ );
102
+ return rows[0] || null;
103
+ }
104
+ async updateTaskById(taskId, updates) {
105
+ const sets = [];
106
+ const values = [];
107
+ let idx = 1;
108
+ for (const [key, value] of Object.entries(updates)) {
109
+ sets.push(`${key} = $${idx}`);
110
+ values.push(value);
111
+ idx++;
112
+ }
113
+ sets.push("updated_at = NOW()");
114
+ values.push(taskId);
115
+ const rows = await this._query(
116
+ `UPDATE scheduled_messages SET ${sets.join(", ")} WHERE id = $${idx} RETURNING *`,
117
+ values
118
+ );
119
+ return rows[0] || null;
120
+ }
121
+ async updateTaskByUuid(uuid, userId, encryptedPayload, extraFields) {
122
+ const sets = ["encrypted_payload = $1", "updated_at = NOW()"];
123
+ const values = [encryptedPayload];
124
+ let idx = 2;
125
+ if (extraFields) {
126
+ for (const [key, value] of Object.entries(extraFields)) {
127
+ sets.push(`${key} = $${idx}`);
128
+ values.push(value);
129
+ idx++;
130
+ }
131
+ }
132
+ values.push(uuid, userId);
133
+ const rows = await this._query(
134
+ `UPDATE scheduled_messages SET ${sets.join(", ")}
135
+ WHERE uuid = $${idx} AND user_id = $${idx + 1} AND status = 'pending'
136
+ RETURNING uuid, updated_at`,
137
+ values
138
+ );
139
+ return rows[0] || null;
140
+ }
141
+ async deleteTaskById(taskId) {
142
+ const rows = await this._query("DELETE FROM scheduled_messages WHERE id = $1 RETURNING id", [taskId]);
143
+ return rows.length > 0;
144
+ }
145
+ async deleteTaskByUuid(uuid, userId) {
146
+ const rows = await this._query(
147
+ "DELETE FROM scheduled_messages WHERE uuid = $1 AND user_id = $2 RETURNING id",
148
+ [uuid, userId]
149
+ );
150
+ return rows.length > 0;
151
+ }
152
+ async getPendingTasks(limit = 50) {
153
+ return this._query(
154
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
155
+ FROM scheduled_messages
156
+ WHERE status = 'pending' AND next_send_at <= NOW()
157
+ ORDER BY next_send_at ASC
158
+ LIMIT $1`,
159
+ [limit]
160
+ );
161
+ }
162
+ async listTasks(userId, opts = {}) {
163
+ const { status = "all", limit = 20, offset = 0 } = opts;
164
+ const conditions = ["user_id = $1"];
165
+ const params = [userId];
166
+ let idx = 2;
167
+ if (status !== "all") {
168
+ conditions.push(`status = $${idx}`);
169
+ params.push(status);
170
+ idx++;
171
+ }
172
+ const where = conditions.join(" AND ");
173
+ const countRows = await this._query(
174
+ `SELECT COUNT(*) as count FROM scheduled_messages WHERE ${where}`,
175
+ params
176
+ );
177
+ const total = parseInt(countRows[0].count, 10);
178
+ const taskParams = [...params, limit, offset];
179
+ const tasks = await this._query(
180
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
181
+ FROM scheduled_messages
182
+ WHERE ${where}
183
+ ORDER BY next_send_at ASC
184
+ LIMIT $${idx} OFFSET $${idx + 1}`,
185
+ taskParams
186
+ );
187
+ return { tasks, total };
188
+ }
189
+ async cleanupOldTasks(days = 7) {
190
+ const safeDays = Math.max(1, Math.floor(Number(days)));
191
+ const rows = await this._query(
192
+ `DELETE FROM scheduled_messages
193
+ WHERE status IN ('sent', 'failed')
194
+ AND updated_at < NOW() - make_interval(days => $1)
195
+ RETURNING id`,
196
+ [safeDays]
197
+ );
198
+ return rows.length;
199
+ }
200
+ async getTaskStatus(uuid, userId) {
201
+ const rows = await this._query(
202
+ "SELECT status FROM scheduled_messages WHERE uuid = $1 AND user_id = $2 LIMIT 1",
203
+ [uuid, userId]
204
+ );
205
+ return rows.length > 0 ? rows[0].status : null;
206
+ }
207
+ };
208
+ export {
209
+ PgAdapter
210
+ };