@rei-standard/amsg-server 2.6.0-next.13 → 2.6.0-next.16

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 (38) hide show
  1. package/README.md +109 -1
  2. package/dist/adapters/d1.d.ts +130 -0
  3. package/dist/adapters/interface.d.ts +67 -0
  4. package/dist/adapters/neon.d.ts +20 -58
  5. package/dist/adapters/pg-shared.d.ts +114 -0
  6. package/dist/adapters/pg.d.ts +20 -58
  7. package/dist/adapters/schema.d.ts +1 -1
  8. package/dist/adapters/schema.sqlite.d.ts +15 -1
  9. package/dist/{chunk-B2FDV7YK.mjs → chunk-34I2YSWE.mjs} +10 -0
  10. package/dist/{chunk-5IOYCCFD.cjs → chunk-H4V7OXHE.cjs} +1326 -384
  11. package/dist/{chunk-TV7N3RTH.mjs → chunk-KVHR3RCU.mjs} +1324 -382
  12. package/dist/{chunk-V2SDWGUB.cjs → chunk-OUPM4HAV.cjs} +10 -0
  13. package/dist/chunk-TFFBMYA2.cjs +95 -0
  14. package/dist/chunk-UYROU4L3.mjs +95 -0
  15. package/dist/cloudflare/single-user-worker.d.ts +35 -2
  16. package/dist/cloudflare.cjs +17 -2
  17. package/dist/cloudflare.d.ts +3 -1
  18. package/dist/cloudflare.mjs +18 -3
  19. package/dist/handlers/outbox.d.ts +7 -0
  20. package/dist/index.cjs +72 -36
  21. package/dist/index.d.cts +5 -2
  22. package/dist/index.d.ts +5 -2
  23. package/dist/index.mjs +53 -17
  24. package/dist/lib/client-state-store.d.ts +18 -3
  25. package/dist/lib/errors.d.ts +93 -0
  26. package/dist/lib/message-processor.d.ts +8 -2
  27. package/dist/lib/outbox-store.d.ts +33 -0
  28. package/dist/lib/push-subscription-store.d.ts +10 -2
  29. package/dist/lib/request.d.ts +36 -0
  30. package/dist/lib/run-tick.d.ts +38 -0
  31. package/dist/lib/schema-version.d.ts +50 -0
  32. package/dist/lib/validation.d.ts +1 -7
  33. package/dist/{neon-3ASBLLCM.cjs → neon-G3G4TDAY.cjs} +52 -112
  34. package/dist/{neon-CUIGAB2T.mjs → neon-GBUF4G76.mjs} +44 -104
  35. package/dist/{pg-IEZTAR43.cjs → pg-C3JZKBUM.cjs} +49 -112
  36. package/dist/{pg-KMJPL6QV.mjs → pg-MRXP5MHJ.mjs} +41 -104
  37. package/dist/single-user.d.ts +4 -0
  38. package/package.json +2 -2
@@ -5,8 +5,16 @@
5
5
 
6
6
 
7
7
 
8
+ var _chunkTFFBMYA2cjs = require('./chunk-TFFBMYA2.cjs');
8
9
 
9
- var _chunkV2SDWGUBcjs = require('./chunk-V2SDWGUB.cjs');
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+ var _chunkOUPM4HAVcjs = require('./chunk-OUPM4HAV.cjs');
10
18
 
11
19
  // src/server/adapters/pg.js
12
20
  var _pg = require('pg');
@@ -30,13 +38,13 @@ var PgAdapter = class {
30
38
  return result.rows;
31
39
  }
32
40
  async initSchema() {
33
- await this._query(_chunkV2SDWGUBcjs.TABLE_SQL);
34
- await this._query(_chunkV2SDWGUBcjs.PUSH_SUBSCRIPTION_TABLE_SQL);
35
- for (const migration of _chunkV2SDWGUBcjs.MIGRATIONS) {
41
+ await this._query(_chunkOUPM4HAVcjs.TABLE_SQL);
42
+ await this._query(_chunkOUPM4HAVcjs.PUSH_SUBSCRIPTION_TABLE_SQL);
43
+ for (const migration of _chunkOUPM4HAVcjs.MIGRATIONS) {
36
44
  await this._query(migration.sql);
37
45
  }
38
46
  const indexResults = [];
39
- for (const index of _chunkV2SDWGUBcjs.INDEXES) {
47
+ for (const index of _chunkOUPM4HAVcjs.INDEXES) {
40
48
  try {
41
49
  await this._query(index.sql);
42
50
  indexResults.push({
@@ -62,11 +70,11 @@ var PgAdapter = class {
62
70
  `Critical index creation failed (${failedNames}). Please remove duplicate UUID rows and run initSchema again.`
63
71
  );
64
72
  }
65
- const tableCheck = await this._query(_chunkV2SDWGUBcjs.VERIFY_TABLE_SQL);
73
+ const tableCheck = await this._query(_chunkOUPM4HAVcjs.VERIFY_TABLE_SQL);
66
74
  if (tableCheck.length === 0) {
67
75
  throw new Error("Table creation verification failed");
68
76
  }
69
- const columns = await this._query(_chunkV2SDWGUBcjs.COLUMNS_SQL);
77
+ const columns = await this._query(_chunkOUPM4HAVcjs.COLUMNS_SQL);
70
78
  return {
71
79
  columnsCreated: columns.length,
72
80
  indexesCreated: indexResults.filter((r) => r.status === "success").length,
@@ -93,7 +101,7 @@ var PgAdapter = class {
93
101
  }
94
102
  async getTaskByUuid(uuid, userId) {
95
103
  const rows = await this._query(
96
- `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
104
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, last_error, created_at, updated_at
97
105
  FROM scheduled_messages
98
106
  WHERE uuid = $1 AND user_id = $2 AND status = 'pending'
99
107
  LIMIT 1`,
@@ -111,12 +119,26 @@ var PgAdapter = class {
111
119
  );
112
120
  return rows[0] || null;
113
121
  }
122
+ /**
123
+ * 这条 uuid 现在是什么状态——不限用户,也不限状态(上面那个只看 pending
124
+ * 行)。`runTask` 用它把「这条已经跑完了」和「压根没这条」分开回报。
125
+ *
126
+ * @param {string} uuid
127
+ * @returns {Promise<{ status: string }|null>}
128
+ */
129
+ async getTaskStatusByUuidOnly(uuid) {
130
+ const rows = await this._query(
131
+ `SELECT status FROM scheduled_messages WHERE uuid = $1 LIMIT 1`,
132
+ [uuid]
133
+ );
134
+ return rows[0] ? { status: rows[0].status } : null;
135
+ }
114
136
  async updateTaskById(taskId, updates) {
115
137
  const sets = [];
116
138
  const values = [];
117
139
  let idx = 1;
118
140
  for (const [key, value] of Object.entries(updates)) {
119
- if (!_chunkV2SDWGUBcjs.UPDATABLE_COLUMNS.has(key)) {
141
+ if (!_chunkOUPM4HAVcjs.UPDATABLE_COLUMNS.has(key)) {
120
142
  throw new Error(`[amsg-server pg] rejected unknown update column: ${key}`);
121
143
  }
122
144
  sets.push(`${key} = $${idx}`);
@@ -137,7 +159,7 @@ var PgAdapter = class {
137
159
  let idx = 2;
138
160
  if (extraFields) {
139
161
  for (const [key, value] of Object.entries(extraFields)) {
140
- if (!_chunkV2SDWGUBcjs.UPDATABLE_COLUMNS.has(key)) {
162
+ if (!_chunkOUPM4HAVcjs.UPDATABLE_COLUMNS.has(key)) {
141
163
  throw new Error(`[amsg-server pg] rejected unknown update column: ${key}`);
142
164
  }
143
165
  sets.push(`${key} = $${idx}`);
@@ -167,7 +189,7 @@ var PgAdapter = class {
167
189
  }
168
190
  async getPendingTasks(limit = 50) {
169
191
  return this._query(
170
- `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
192
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, retry_after, status, retry_count
171
193
  FROM scheduled_messages
172
194
  WHERE status = 'pending' AND next_send_at <= NOW()
173
195
  AND (lease_until IS NULL OR lease_until <= NOW())
@@ -177,64 +199,14 @@ var PgAdapter = class {
177
199
  [limit]
178
200
  );
179
201
  }
180
- /**
181
- * 领取一条到点的任务:在 lease_until 上写下「这条归我管到什么时候」,
182
- * 本次投递期间别的 tick 领不走它。
183
- *
184
- * 租约写在自己的列上,next_send_at 全程不动——那一列是用户设的触发时刻,
185
- * 任务列表要读它、循环任务推进下一次也要拿它当基准。
186
- *
187
- * 两个 tick 抢同一行时只有一个改得动,另一个拿不到 RETURNING 行,据此跳
188
- * 过。WHERE 里的两个条件各管一件事:
189
- * - lease_until 为空或已过期:没人正在跑这条。领了任务的 tick 中途没了
190
- * 也不会把行焊死,租约到期后自然可以被接手。
191
- * - next_send_at 等于读这行时看到的值:读出来之后用户又改了排期的话,
192
- * 这一跳就不该再按旧时刻发。
193
- *
194
- * 不加一个 'sending' 状态来表达「正在跑」:status 上有 CHECK 约束,加值
195
- * 要改表。
196
- *
197
- * 比 next_send_at 时两边都截到毫秒:列是 timestamptz(微秒精度),驱动读
198
- * 出来是 JS Date(毫秒精度),原值送回去可能因为亚毫秒差对不上。
199
- *
200
- * 带 serializeGroup 时多一道分组门:同一分组里已经有别的行拿着未到期的租
201
- * 约,这条就领不走(同一分组同时只跑一条)。判定和写租约在同一条 UPDATE
202
- * 里完成,「先查再占」的空档天然不存在。分组门只看租约,不看
203
- * `retry_after`:等着重试的任务其实闲着,不该把同分组的其他任务一起堵住。
204
- *
205
- * @param {number} taskId
206
- * @param {string|Date} expectedNextSendAt - 读这行时拿到的 next_send_at 原值
207
- * @param {string|Date} leaseUntil - 租期末尾
208
- * @param {string|null} [serializeGroup] - 串行分组标识;空表示不参与分组串行
209
- * @returns {Promise<boolean>} true = 领到了;false = 已被别人领走、同分组有
210
- * 任务正在跑、或行已不是 pending
211
- */
202
+ // 领取一条到点的任务。SQL 与并发语义在 pg-shared.js(pg / neon 共用一份,
203
+ // 语义说明也在那里)。
212
204
  async claimTask(taskId, expectedNextSendAt, leaseUntil, serializeGroup = null) {
213
- const grouped = typeof serializeGroup === "string" && serializeGroup.length > 0;
214
- const params = [leaseUntil, taskId, expectedNextSendAt];
215
- let setClause = "lease_until = $1, updated_at = NOW()";
216
- let groupGuard = "";
217
- if (grouped) {
218
- params.push(serializeGroup);
219
- setClause = "lease_until = $1, serialize_group = $4, updated_at = NOW()";
220
- groupGuard = `
221
- AND NOT EXISTS (
222
- SELECT 1 FROM scheduled_messages busy
223
- WHERE busy.serialize_group = $4 AND busy.id <> $2
224
- AND busy.status = 'pending' AND busy.lease_until > NOW()
225
- )`;
226
- }
227
- const rows = await this._query(
228
- `UPDATE scheduled_messages
229
- SET ${setClause}
230
- WHERE id = $2 AND status = 'pending'
231
- AND date_trunc('milliseconds', next_send_at)
232
- = date_trunc('milliseconds', $3::timestamptz)
233
- AND (lease_until IS NULL OR lease_until <= NOW())${groupGuard}
234
- RETURNING id`,
235
- params
236
- );
237
- return rows.length > 0;
205
+ return _chunkTFFBMYA2cjs.claimTask.call(void 0, (text, params) => this._query(text, params), taskId, expectedNextSendAt, leaseUntil, serializeGroup);
206
+ }
207
+ // 投递期间的租约续期(run-tick 心跳;语义见 pg-shared.js)。
208
+ async renewTaskLease(taskId, leaseUntil) {
209
+ return _chunkTFFBMYA2cjs.renewTaskLease.call(void 0, (text, params) => this._query(text, params), taskId, leaseUntil);
238
210
  }
239
211
  async listTasks(userId, opts = {}) {
240
212
  const { status = "all", limit = 20, offset = 0 } = opts;
@@ -254,7 +226,7 @@ var PgAdapter = class {
254
226
  const total = parseInt(countRows[0].count, 10);
255
227
  const taskParams = [...params, limit, offset];
256
228
  const tasks = await this._query(
257
- `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
229
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, last_error, created_at, updated_at
258
230
  FROM scheduled_messages
259
231
  WHERE ${where}
260
232
  ORDER BY next_send_at ASC
@@ -281,54 +253,19 @@ var PgAdapter = class {
281
253
  );
282
254
  return rows.length > 0 ? rows[0].status : null;
283
255
  }
284
- // ── push_subscriptions (user-level Web Push subscription) ──────────────
285
- /**
286
- * 这个用户当前登记的推送订阅(密文原样返回,解密在上层)。
287
- *
288
- * @param {string} userId
289
- * @returns {Promise<{ subscription: string, updated_at: number }|null>}
290
- */
256
+ // 状态 + 失败摘要(GET /message 对已失败的行透出原因;语义见 pg-shared.js)。
257
+ async getTaskStatusInfo(uuid, userId) {
258
+ return _chunkTFFBMYA2cjs.getTaskStatusInfo.call(void 0, (text, params) => this._query(text, params), uuid, userId);
259
+ }
260
+ // ── push_subscriptions(实现见 pg-shared.js,pg / neon 共用一份)─────────
291
261
  async getPushSubscription(userId) {
292
- const rows = await this._query(
293
- "SELECT subscription, updated_at FROM push_subscriptions WHERE user_id = $1 LIMIT 1",
294
- [userId]
295
- );
296
- if (rows.length === 0) return null;
297
- return { subscription: rows[0].subscription, updated_at: Number(rows[0].updated_at) };
262
+ return _chunkTFFBMYA2cjs.getPushSubscription.call(void 0, (text, params) => this._query(text, params), userId);
298
263
  }
299
- /**
300
- * 覆盖写这个用户的订阅。一个用户一行,没有 last-write-wins 之类的比较——
301
- * 客户端拿到的新订阅永远比旧的有效,旧的那份只会 410。
302
- *
303
- * @param {string} userId
304
- * @param {string} encryptedSubscription
305
- * @param {number} updatedAt - epoch 毫秒
306
- * @returns {Promise<boolean>}
307
- */
308
264
  async upsertPushSubscription(userId, encryptedSubscription, updatedAt) {
309
- const rows = await this._query(
310
- `INSERT INTO push_subscriptions (user_id, subscription, updated_at)
311
- VALUES ($1, $2, $3)
312
- ON CONFLICT (user_id) DO UPDATE SET
313
- subscription = EXCLUDED.subscription,
314
- updated_at = EXCLUDED.updated_at
315
- RETURNING user_id`,
316
- [userId, encryptedSubscription, updatedAt]
317
- );
318
- return rows.length > 0;
265
+ return _chunkTFFBMYA2cjs.upsertPushSubscription.call(void 0, (text, params) => this._query(text, params), userId, encryptedSubscription, updatedAt);
319
266
  }
320
- /**
321
- * 删掉这个用户的订阅(设置页「停止接收推送」)。
322
- *
323
- * @param {string} userId
324
- * @returns {Promise<boolean>} true = 确实删掉了一行
325
- */
326
267
  async deletePushSubscription(userId) {
327
- const rows = await this._query(
328
- "DELETE FROM push_subscriptions WHERE user_id = $1 RETURNING user_id",
329
- [userId]
330
- );
331
- return rows.length > 0;
268
+ return _chunkTFFBMYA2cjs.deletePushSubscription.call(void 0, (text, params) => this._query(text, params), userId);
332
269
  }
333
270
  };
334
271
 
@@ -1,3 +1,11 @@
1
+ import {
2
+ claimTask,
3
+ deletePushSubscription,
4
+ getPushSubscription,
5
+ getTaskStatusInfo,
6
+ renewTaskLease,
7
+ upsertPushSubscription
8
+ } from "./chunk-UYROU4L3.mjs";
1
9
  import {
2
10
  COLUMNS_SQL,
3
11
  INDEXES,
@@ -6,7 +14,7 @@ import {
6
14
  TABLE_SQL,
7
15
  UPDATABLE_COLUMNS,
8
16
  VERIFY_TABLE_SQL
9
- } from "./chunk-B2FDV7YK.mjs";
17
+ } from "./chunk-34I2YSWE.mjs";
10
18
 
11
19
  // src/server/adapters/pg.js
12
20
  import { Pool } from "pg";
@@ -93,7 +101,7 @@ var PgAdapter = class {
93
101
  }
94
102
  async getTaskByUuid(uuid, userId) {
95
103
  const rows = await this._query(
96
- `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
104
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, last_error, created_at, updated_at
97
105
  FROM scheduled_messages
98
106
  WHERE uuid = $1 AND user_id = $2 AND status = 'pending'
99
107
  LIMIT 1`,
@@ -111,6 +119,20 @@ var PgAdapter = class {
111
119
  );
112
120
  return rows[0] || null;
113
121
  }
122
+ /**
123
+ * 这条 uuid 现在是什么状态——不限用户,也不限状态(上面那个只看 pending
124
+ * 行)。`runTask` 用它把「这条已经跑完了」和「压根没这条」分开回报。
125
+ *
126
+ * @param {string} uuid
127
+ * @returns {Promise<{ status: string }|null>}
128
+ */
129
+ async getTaskStatusByUuidOnly(uuid) {
130
+ const rows = await this._query(
131
+ `SELECT status FROM scheduled_messages WHERE uuid = $1 LIMIT 1`,
132
+ [uuid]
133
+ );
134
+ return rows[0] ? { status: rows[0].status } : null;
135
+ }
114
136
  async updateTaskById(taskId, updates) {
115
137
  const sets = [];
116
138
  const values = [];
@@ -167,7 +189,7 @@ var PgAdapter = class {
167
189
  }
168
190
  async getPendingTasks(limit = 50) {
169
191
  return this._query(
170
- `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
192
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, retry_after, status, retry_count
171
193
  FROM scheduled_messages
172
194
  WHERE status = 'pending' AND next_send_at <= NOW()
173
195
  AND (lease_until IS NULL OR lease_until <= NOW())
@@ -177,64 +199,14 @@ var PgAdapter = class {
177
199
  [limit]
178
200
  );
179
201
  }
180
- /**
181
- * 领取一条到点的任务:在 lease_until 上写下「这条归我管到什么时候」,
182
- * 本次投递期间别的 tick 领不走它。
183
- *
184
- * 租约写在自己的列上,next_send_at 全程不动——那一列是用户设的触发时刻,
185
- * 任务列表要读它、循环任务推进下一次也要拿它当基准。
186
- *
187
- * 两个 tick 抢同一行时只有一个改得动,另一个拿不到 RETURNING 行,据此跳
188
- * 过。WHERE 里的两个条件各管一件事:
189
- * - lease_until 为空或已过期:没人正在跑这条。领了任务的 tick 中途没了
190
- * 也不会把行焊死,租约到期后自然可以被接手。
191
- * - next_send_at 等于读这行时看到的值:读出来之后用户又改了排期的话,
192
- * 这一跳就不该再按旧时刻发。
193
- *
194
- * 不加一个 'sending' 状态来表达「正在跑」:status 上有 CHECK 约束,加值
195
- * 要改表。
196
- *
197
- * 比 next_send_at 时两边都截到毫秒:列是 timestamptz(微秒精度),驱动读
198
- * 出来是 JS Date(毫秒精度),原值送回去可能因为亚毫秒差对不上。
199
- *
200
- * 带 serializeGroup 时多一道分组门:同一分组里已经有别的行拿着未到期的租
201
- * 约,这条就领不走(同一分组同时只跑一条)。判定和写租约在同一条 UPDATE
202
- * 里完成,「先查再占」的空档天然不存在。分组门只看租约,不看
203
- * `retry_after`:等着重试的任务其实闲着,不该把同分组的其他任务一起堵住。
204
- *
205
- * @param {number} taskId
206
- * @param {string|Date} expectedNextSendAt - 读这行时拿到的 next_send_at 原值
207
- * @param {string|Date} leaseUntil - 租期末尾
208
- * @param {string|null} [serializeGroup] - 串行分组标识;空表示不参与分组串行
209
- * @returns {Promise<boolean>} true = 领到了;false = 已被别人领走、同分组有
210
- * 任务正在跑、或行已不是 pending
211
- */
202
+ // 领取一条到点的任务。SQL 与并发语义在 pg-shared.js(pg / neon 共用一份,
203
+ // 语义说明也在那里)。
212
204
  async claimTask(taskId, expectedNextSendAt, leaseUntil, serializeGroup = null) {
213
- const grouped = typeof serializeGroup === "string" && serializeGroup.length > 0;
214
- const params = [leaseUntil, taskId, expectedNextSendAt];
215
- let setClause = "lease_until = $1, updated_at = NOW()";
216
- let groupGuard = "";
217
- if (grouped) {
218
- params.push(serializeGroup);
219
- setClause = "lease_until = $1, serialize_group = $4, updated_at = NOW()";
220
- groupGuard = `
221
- AND NOT EXISTS (
222
- SELECT 1 FROM scheduled_messages busy
223
- WHERE busy.serialize_group = $4 AND busy.id <> $2
224
- AND busy.status = 'pending' AND busy.lease_until > NOW()
225
- )`;
226
- }
227
- const rows = await this._query(
228
- `UPDATE scheduled_messages
229
- SET ${setClause}
230
- WHERE id = $2 AND status = 'pending'
231
- AND date_trunc('milliseconds', next_send_at)
232
- = date_trunc('milliseconds', $3::timestamptz)
233
- AND (lease_until IS NULL OR lease_until <= NOW())${groupGuard}
234
- RETURNING id`,
235
- params
236
- );
237
- return rows.length > 0;
205
+ return claimTask((text, params) => this._query(text, params), taskId, expectedNextSendAt, leaseUntil, serializeGroup);
206
+ }
207
+ // 投递期间的租约续期(run-tick 心跳;语义见 pg-shared.js)。
208
+ async renewTaskLease(taskId, leaseUntil) {
209
+ return renewTaskLease((text, params) => this._query(text, params), taskId, leaseUntil);
238
210
  }
239
211
  async listTasks(userId, opts = {}) {
240
212
  const { status = "all", limit = 20, offset = 0 } = opts;
@@ -254,7 +226,7 @@ var PgAdapter = class {
254
226
  const total = parseInt(countRows[0].count, 10);
255
227
  const taskParams = [...params, limit, offset];
256
228
  const tasks = await this._query(
257
- `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
229
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, last_error, created_at, updated_at
258
230
  FROM scheduled_messages
259
231
  WHERE ${where}
260
232
  ORDER BY next_send_at ASC
@@ -281,54 +253,19 @@ var PgAdapter = class {
281
253
  );
282
254
  return rows.length > 0 ? rows[0].status : null;
283
255
  }
284
- // ── push_subscriptions (user-level Web Push subscription) ──────────────
285
- /**
286
- * 这个用户当前登记的推送订阅(密文原样返回,解密在上层)。
287
- *
288
- * @param {string} userId
289
- * @returns {Promise<{ subscription: string, updated_at: number }|null>}
290
- */
256
+ // 状态 + 失败摘要(GET /message 对已失败的行透出原因;语义见 pg-shared.js)。
257
+ async getTaskStatusInfo(uuid, userId) {
258
+ return getTaskStatusInfo((text, params) => this._query(text, params), uuid, userId);
259
+ }
260
+ // ── push_subscriptions(实现见 pg-shared.js,pg / neon 共用一份)─────────
291
261
  async getPushSubscription(userId) {
292
- const rows = await this._query(
293
- "SELECT subscription, updated_at FROM push_subscriptions WHERE user_id = $1 LIMIT 1",
294
- [userId]
295
- );
296
- if (rows.length === 0) return null;
297
- return { subscription: rows[0].subscription, updated_at: Number(rows[0].updated_at) };
262
+ return getPushSubscription((text, params) => this._query(text, params), userId);
298
263
  }
299
- /**
300
- * 覆盖写这个用户的订阅。一个用户一行,没有 last-write-wins 之类的比较——
301
- * 客户端拿到的新订阅永远比旧的有效,旧的那份只会 410。
302
- *
303
- * @param {string} userId
304
- * @param {string} encryptedSubscription
305
- * @param {number} updatedAt - epoch 毫秒
306
- * @returns {Promise<boolean>}
307
- */
308
264
  async upsertPushSubscription(userId, encryptedSubscription, updatedAt) {
309
- const rows = await this._query(
310
- `INSERT INTO push_subscriptions (user_id, subscription, updated_at)
311
- VALUES ($1, $2, $3)
312
- ON CONFLICT (user_id) DO UPDATE SET
313
- subscription = EXCLUDED.subscription,
314
- updated_at = EXCLUDED.updated_at
315
- RETURNING user_id`,
316
- [userId, encryptedSubscription, updatedAt]
317
- );
318
- return rows.length > 0;
265
+ return upsertPushSubscription((text, params) => this._query(text, params), userId, encryptedSubscription, updatedAt);
319
266
  }
320
- /**
321
- * 删掉这个用户的订阅(设置页「停止接收推送」)。
322
- *
323
- * @param {string} userId
324
- * @returns {Promise<boolean>} true = 确实删掉了一行
325
- */
326
267
  async deletePushSubscription(userId) {
327
- const rows = await this._query(
328
- "DELETE FROM push_subscriptions WHERE user_id = $1 RETURNING user_id",
329
- [userId]
330
- );
331
- return rows.length > 0;
268
+ return deletePushSubscription((text, params) => this._query(text, params), userId);
332
269
  }
333
270
  };
334
271
  export {
@@ -81,5 +81,9 @@ export function createSingleUserServer(config: any): {
81
81
  capabilities: {
82
82
  GET: (url: any, headers: any) => Promise<any>;
83
83
  };
84
+ outbox: {
85
+ GET: (url: any, headers: any) => Promise<any>;
86
+ POST: (headers: any, body: any) => Promise<any>;
87
+ };
84
88
  };
85
89
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-server",
3
- "version": "2.6.0-next.13",
3
+ "version": "2.6.0-next.16",
4
4
  "description": "ReiStandard Active Messaging server SDK with pluggable database adapters. Three-axis push schema (messageKind / messageType / messageSubtype) from @rei-standard/amsg-shared. Auto-emits ReasoningPush when the LLM response carries reasoning_content.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@netlify/blobs": "^8.1.0",
42
- "@rei-standard/amsg-shared": "^0.4.0-next.3",
42
+ "@rei-standard/amsg-shared": "^0.4.0-next.5",
43
43
  "web-push": "^3.6.7"
44
44
  },
45
45
  "peerDependencies": {