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

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.
@@ -6,7 +6,7 @@
6
6
 
7
7
 
8
8
 
9
- var _chunk4SIN2R66cjs = require('./chunk-4SIN2R66.cjs');
9
+ var _chunkV2SDWGUBcjs = require('./chunk-V2SDWGUB.cjs');
10
10
 
11
11
  // src/server/adapters/pg.js
12
12
  var _pg = require('pg');
@@ -30,13 +30,13 @@ var PgAdapter = class {
30
30
  return result.rows;
31
31
  }
32
32
  async initSchema() {
33
- await this._query(_chunk4SIN2R66cjs.TABLE_SQL);
34
- await this._query(_chunk4SIN2R66cjs.PUSH_SUBSCRIPTION_TABLE_SQL);
35
- for (const migration of _chunk4SIN2R66cjs.MIGRATIONS) {
33
+ await this._query(_chunkV2SDWGUBcjs.TABLE_SQL);
34
+ await this._query(_chunkV2SDWGUBcjs.PUSH_SUBSCRIPTION_TABLE_SQL);
35
+ for (const migration of _chunkV2SDWGUBcjs.MIGRATIONS) {
36
36
  await this._query(migration.sql);
37
37
  }
38
38
  const indexResults = [];
39
- for (const index of _chunk4SIN2R66cjs.INDEXES) {
39
+ for (const index of _chunkV2SDWGUBcjs.INDEXES) {
40
40
  try {
41
41
  await this._query(index.sql);
42
42
  indexResults.push({
@@ -62,11 +62,11 @@ var PgAdapter = class {
62
62
  `Critical index creation failed (${failedNames}). Please remove duplicate UUID rows and run initSchema again.`
63
63
  );
64
64
  }
65
- const tableCheck = await this._query(_chunk4SIN2R66cjs.VERIFY_TABLE_SQL);
65
+ const tableCheck = await this._query(_chunkV2SDWGUBcjs.VERIFY_TABLE_SQL);
66
66
  if (tableCheck.length === 0) {
67
67
  throw new Error("Table creation verification failed");
68
68
  }
69
- const columns = await this._query(_chunk4SIN2R66cjs.COLUMNS_SQL);
69
+ const columns = await this._query(_chunkV2SDWGUBcjs.COLUMNS_SQL);
70
70
  return {
71
71
  columnsCreated: columns.length,
72
72
  indexesCreated: indexResults.filter((r) => r.status === "success").length,
@@ -93,7 +93,7 @@ var PgAdapter = class {
93
93
  }
94
94
  async getTaskByUuid(uuid, userId) {
95
95
  const rows = await this._query(
96
- `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
96
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
97
97
  FROM scheduled_messages
98
98
  WHERE uuid = $1 AND user_id = $2 AND status = 'pending'
99
99
  LIMIT 1`,
@@ -116,7 +116,7 @@ var PgAdapter = class {
116
116
  const values = [];
117
117
  let idx = 1;
118
118
  for (const [key, value] of Object.entries(updates)) {
119
- if (!_chunk4SIN2R66cjs.UPDATABLE_COLUMNS.has(key)) {
119
+ if (!_chunkV2SDWGUBcjs.UPDATABLE_COLUMNS.has(key)) {
120
120
  throw new Error(`[amsg-server pg] rejected unknown update column: ${key}`);
121
121
  }
122
122
  sets.push(`${key} = $${idx}`);
@@ -137,7 +137,7 @@ var PgAdapter = class {
137
137
  let idx = 2;
138
138
  if (extraFields) {
139
139
  for (const [key, value] of Object.entries(extraFields)) {
140
- if (!_chunk4SIN2R66cjs.UPDATABLE_COLUMNS.has(key)) {
140
+ if (!_chunkV2SDWGUBcjs.UPDATABLE_COLUMNS.has(key)) {
141
141
  throw new Error(`[amsg-server pg] rejected unknown update column: ${key}`);
142
142
  }
143
143
  sets.push(`${key} = $${idx}`);
@@ -171,6 +171,7 @@ var PgAdapter = class {
171
171
  FROM scheduled_messages
172
172
  WHERE status = 'pending' AND next_send_at <= NOW()
173
173
  AND (lease_until IS NULL OR lease_until <= NOW())
174
+ AND (retry_after IS NULL OR retry_after <= NOW())
174
175
  ORDER BY next_send_at ASC
175
176
  LIMIT $1`,
176
177
  [limit]
@@ -196,21 +197,42 @@ var PgAdapter = class {
196
197
  * 比 next_send_at 时两边都截到毫秒:列是 timestamptz(微秒精度),驱动读
197
198
  * 出来是 JS Date(毫秒精度),原值送回去可能因为亚毫秒差对不上。
198
199
  *
200
+ * 带 serializeGroup 时多一道分组门:同一分组里已经有别的行拿着未到期的租
201
+ * 约,这条就领不走(同一分组同时只跑一条)。判定和写租约在同一条 UPDATE
202
+ * 里完成,「先查再占」的空档天然不存在。分组门只看租约,不看
203
+ * `retry_after`:等着重试的任务其实闲着,不该把同分组的其他任务一起堵住。
204
+ *
199
205
  * @param {number} taskId
200
206
  * @param {string|Date} expectedNextSendAt - 读这行时拿到的 next_send_at 原值
201
207
  * @param {string|Date} leaseUntil - 租期末尾
202
- * @returns {Promise<boolean>} true = 领到了;false = 已被别人领走或行已不是 pending
208
+ * @param {string|null} [serializeGroup] - 串行分组标识;空表示不参与分组串行
209
+ * @returns {Promise<boolean>} true = 领到了;false = 已被别人领走、同分组有
210
+ * 任务正在跑、或行已不是 pending
203
211
  */
204
- async claimTask(taskId, expectedNextSendAt, leaseUntil) {
212
+ 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
+ }
205
227
  const rows = await this._query(
206
228
  `UPDATE scheduled_messages
207
- SET lease_until = $1, updated_at = NOW()
229
+ SET ${setClause}
208
230
  WHERE id = $2 AND status = 'pending'
209
231
  AND date_trunc('milliseconds', next_send_at)
210
232
  = date_trunc('milliseconds', $3::timestamptz)
211
- AND (lease_until IS NULL OR lease_until <= NOW())
233
+ AND (lease_until IS NULL OR lease_until <= NOW())${groupGuard}
212
234
  RETURNING id`,
213
- [leaseUntil, taskId, expectedNextSendAt]
235
+ params
214
236
  );
215
237
  return rows.length > 0;
216
238
  }
@@ -6,7 +6,7 @@ import {
6
6
  TABLE_SQL,
7
7
  UPDATABLE_COLUMNS,
8
8
  VERIFY_TABLE_SQL
9
- } from "./chunk-WAFOSZL3.mjs";
9
+ } from "./chunk-B2FDV7YK.mjs";
10
10
 
11
11
  // src/server/adapters/pg.js
12
12
  import { Pool } from "pg";
@@ -93,7 +93,7 @@ var PgAdapter = class {
93
93
  }
94
94
  async getTaskByUuid(uuid, userId) {
95
95
  const rows = await this._query(
96
- `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
96
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
97
97
  FROM scheduled_messages
98
98
  WHERE uuid = $1 AND user_id = $2 AND status = 'pending'
99
99
  LIMIT 1`,
@@ -171,6 +171,7 @@ var PgAdapter = class {
171
171
  FROM scheduled_messages
172
172
  WHERE status = 'pending' AND next_send_at <= NOW()
173
173
  AND (lease_until IS NULL OR lease_until <= NOW())
174
+ AND (retry_after IS NULL OR retry_after <= NOW())
174
175
  ORDER BY next_send_at ASC
175
176
  LIMIT $1`,
176
177
  [limit]
@@ -196,21 +197,42 @@ var PgAdapter = class {
196
197
  * 比 next_send_at 时两边都截到毫秒:列是 timestamptz(微秒精度),驱动读
197
198
  * 出来是 JS Date(毫秒精度),原值送回去可能因为亚毫秒差对不上。
198
199
  *
200
+ * 带 serializeGroup 时多一道分组门:同一分组里已经有别的行拿着未到期的租
201
+ * 约,这条就领不走(同一分组同时只跑一条)。判定和写租约在同一条 UPDATE
202
+ * 里完成,「先查再占」的空档天然不存在。分组门只看租约,不看
203
+ * `retry_after`:等着重试的任务其实闲着,不该把同分组的其他任务一起堵住。
204
+ *
199
205
  * @param {number} taskId
200
206
  * @param {string|Date} expectedNextSendAt - 读这行时拿到的 next_send_at 原值
201
207
  * @param {string|Date} leaseUntil - 租期末尾
202
- * @returns {Promise<boolean>} true = 领到了;false = 已被别人领走或行已不是 pending
208
+ * @param {string|null} [serializeGroup] - 串行分组标识;空表示不参与分组串行
209
+ * @returns {Promise<boolean>} true = 领到了;false = 已被别人领走、同分组有
210
+ * 任务正在跑、或行已不是 pending
203
211
  */
204
- async claimTask(taskId, expectedNextSendAt, leaseUntil) {
212
+ 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
+ }
205
227
  const rows = await this._query(
206
228
  `UPDATE scheduled_messages
207
- SET lease_until = $1, updated_at = NOW()
229
+ SET ${setClause}
208
230
  WHERE id = $2 AND status = 'pending'
209
231
  AND date_trunc('milliseconds', next_send_at)
210
232
  = date_trunc('milliseconds', $3::timestamptz)
211
- AND (lease_until IS NULL OR lease_until <= NOW())
233
+ AND (lease_until IS NULL OR lease_until <= NOW())${groupGuard}
212
234
  RETURNING id`,
213
- [leaseUntil, taskId, expectedNextSendAt]
235
+ params
214
236
  );
215
237
  return rows.length > 0;
216
238
  }
@@ -40,6 +40,7 @@ export function createSingleUserServer(config: any): {
40
40
  totalTimeoutMs: any;
41
41
  maxStateValueBytes: any;
42
42
  onAfterSend: any;
43
+ onFireSettled: any;
43
44
  maxScheduledTasksPerFire: any;
44
45
  };
45
46
  handlers: {
@@ -61,6 +62,9 @@ export function createSingleUserServer(config: any): {
61
62
  messages: {
62
63
  GET: (url: any, headers: any) => Promise<any>;
63
64
  };
65
+ getMessage: {
66
+ GET: (url: any, headers: any) => Promise<any>;
67
+ };
64
68
  vapidPublicKey: {
65
69
  GET: Function;
66
70
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-server",
3
- "version": "2.6.0-next.11",
3
+ "version": "2.6.0-next.13",
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",