@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.
@@ -17,7 +17,7 @@
17
17
 
18
18
 
19
19
 
20
- var _chunkQGJSWMQPcjs = require('./chunk-QGJSWMQP.cjs');
20
+ var _chunk5IOYCCFDcjs = require('./chunk-5IOYCCFD.cjs');
21
21
 
22
22
 
23
23
 
@@ -37,4 +37,4 @@ var _chunkQGJSWMQPcjs = require('./chunk-QGJSWMQP.cjs');
37
37
 
38
38
 
39
39
 
40
- exports.MAX_PUSH_PAYLOAD_BYTES = _chunkQGJSWMQPcjs.MAX_PUSH_PAYLOAD_BYTES; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkQGJSWMQPcjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkQGJSWMQPcjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkQGJSWMQPcjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkQGJSWMQPcjs.advanceOccurrence; exports.createD1Adapter = _chunkQGJSWMQPcjs.createD1Adapter; exports.createSingleUserCloudflareWorker = _chunkQGJSWMQPcjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkQGJSWMQPcjs.createSingleUserServer; exports.createWebCryptoWebPush = _chunkQGJSWMQPcjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkQGJSWMQPcjs.decryptFromStorage; exports.decryptPayload = _chunkQGJSWMQPcjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkQGJSWMQPcjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkQGJSWMQPcjs.encryptForStorage; exports.isValidTimeZoneId = _chunkQGJSWMQPcjs.isValidTimeZoneId; exports.measurePushPayload = _chunkQGJSWMQPcjs.measurePushPayload; exports.nextFutureOccurrence = _chunkQGJSWMQPcjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkQGJSWMQPcjs.planNextOccurrence; exports.runScheduledTick = _chunkQGJSWMQPcjs.runScheduledTick;
40
+ exports.MAX_PUSH_PAYLOAD_BYTES = _chunk5IOYCCFDcjs.MAX_PUSH_PAYLOAD_BYTES; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunk5IOYCCFDcjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunk5IOYCCFDcjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunk5IOYCCFDcjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunk5IOYCCFDcjs.advanceOccurrence; exports.createD1Adapter = _chunk5IOYCCFDcjs.createD1Adapter; exports.createSingleUserCloudflareWorker = _chunk5IOYCCFDcjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunk5IOYCCFDcjs.createSingleUserServer; exports.createWebCryptoWebPush = _chunk5IOYCCFDcjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunk5IOYCCFDcjs.decryptFromStorage; exports.decryptPayload = _chunk5IOYCCFDcjs.decryptPayload; exports.deriveUserEncryptionKey = _chunk5IOYCCFDcjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunk5IOYCCFDcjs.encryptForStorage; exports.isValidTimeZoneId = _chunk5IOYCCFDcjs.isValidTimeZoneId; exports.measurePushPayload = _chunk5IOYCCFDcjs.measurePushPayload; exports.nextFutureOccurrence = _chunk5IOYCCFDcjs.nextFutureOccurrence; exports.planNextOccurrence = _chunk5IOYCCFDcjs.planNextOccurrence; exports.runScheduledTick = _chunk5IOYCCFDcjs.runScheduledTick;
@@ -17,7 +17,7 @@ import {
17
17
  nextFutureOccurrence,
18
18
  planNextOccurrence,
19
19
  runScheduledTick
20
- } from "./chunk-HZDUURIR.mjs";
20
+ } from "./chunk-TV7N3RTH.mjs";
21
21
  export {
22
22
  MAX_PUSH_PAYLOAD_BYTES,
23
23
  PUSH_ENVELOPE_RESERVED_BYTES,
@@ -0,0 +1,3 @@
1
+ export function createGetMessageHandler(ctx: any): {
2
+ GET: (url: any, headers: any) => Promise<any>;
3
+ };
package/dist/index.cjs CHANGED
@@ -38,7 +38,8 @@
38
38
 
39
39
 
40
40
 
41
- var _chunkQGJSWMQPcjs = require('./chunk-QGJSWMQP.cjs');
41
+
42
+ var _chunk5IOYCCFDcjs = require('./chunk-5IOYCCFD.cjs');
42
43
 
43
44
  // src/server/adapters/factory.js
44
45
  async function createAdapter(config) {
@@ -54,11 +55,11 @@ async function createAdapter(config) {
54
55
  }
55
56
  switch (config.driver) {
56
57
  case "neon": {
57
- const { NeonAdapter } = await Promise.resolve().then(() => _interopRequireWildcard(require("./neon-2CS4TLNS.cjs")));
58
+ const { NeonAdapter } = await Promise.resolve().then(() => _interopRequireWildcard(require("./neon-3ASBLLCM.cjs")));
58
59
  return new NeonAdapter(config.connectionString);
59
60
  }
60
61
  case "pg": {
61
- const { PgAdapter } = await Promise.resolve().then(() => _interopRequireWildcard(require("./pg-PM5RYRWH.cjs")));
62
+ const { PgAdapter } = await Promise.resolve().then(() => _interopRequireWildcard(require("./pg-IEZTAR43.cjs")));
62
63
  return new PgAdapter(config.connectionString);
63
64
  }
64
65
  default:
@@ -71,7 +72,7 @@ async function createAdapter(config) {
71
72
  // src/server/handlers/init-tenant.js
72
73
  function createInitTenantHandler(ctx) {
73
74
  async function POST(headers, body) {
74
- const initSecret = _chunkQGJSWMQPcjs.getHeader.call(void 0, headers, "x-init-secret");
75
+ const initSecret = _chunk5IOYCCFDcjs.getHeader.call(void 0, headers, "x-init-secret");
75
76
  if (ctx.tenant.initSecret && initSecret !== ctx.tenant.initSecret) {
76
77
  return {
77
78
  status: 401,
@@ -84,7 +85,7 @@ function createInitTenantHandler(ctx) {
84
85
  }
85
86
  };
86
87
  }
87
- const parsed = _chunkQGJSWMQPcjs.parseJsonBody.call(void 0, body);
88
+ const parsed = _chunk5IOYCCFDcjs.parseJsonBody.call(void 0, body);
88
89
  if (!parsed.ok) {
89
90
  return {
90
91
  status: 400,
@@ -98,7 +99,7 @@ function createInitTenantHandler(ctx) {
98
99
  const tenantId = payload.tenantId || void 0;
99
100
  const driver = payload.driver;
100
101
  const databaseUrl = payload.databaseUrl || payload.connectionString;
101
- if (tenantId && !_chunkQGJSWMQPcjs.isValidUUIDv4.call(void 0, tenantId)) {
102
+ if (tenantId && !_chunk5IOYCCFDcjs.isValidUUIDv4.call(void 0, tenantId)) {
102
103
  return {
103
104
  status: 400,
104
105
  body: {
@@ -218,7 +219,7 @@ function createSendNotificationsHandler(ctx) {
218
219
  }
219
220
  };
220
221
  }
221
- const data = await _chunkQGJSWMQPcjs.runScheduledTick.call(void 0, { ...ctx, db, masterKey });
222
+ const data = await _chunk5IOYCCFDcjs.runScheduledTick.call(void 0, { ...ctx, db, masterKey });
222
223
  return { status: 200, body: { success: true, data } };
223
224
  }
224
225
  return { POST };
@@ -325,7 +326,7 @@ function createTenantBlobStore(options) {
325
326
  // src/server/tenant/token.js
326
327
 
327
328
  function sign(input, secret) {
328
- return _chunkQGJSWMQPcjs.bytesToBase64Url.call(void 0, _crypto.createHmac.call(void 0, "sha256", secret).update(input).digest());
329
+ return _chunk5IOYCCFDcjs.bytesToBase64Url.call(void 0, _crypto.createHmac.call(void 0, "sha256", secret).update(input).digest());
329
330
  }
330
331
  function nowEpochSeconds() {
331
332
  return Math.floor(Date.now() / 1e3);
@@ -341,8 +342,8 @@ function createTenantToken(params, secret) {
341
342
  v: 1
342
343
  };
343
344
  const header = { alg: "HS256", typ: "JWT" };
344
- const encodedHeader = _chunkQGJSWMQPcjs.bytesToBase64Url.call(void 0, _chunkQGJSWMQPcjs.utf8.call(void 0, JSON.stringify(header)));
345
- const encodedPayload = _chunkQGJSWMQPcjs.bytesToBase64Url.call(void 0, _chunkQGJSWMQPcjs.utf8.call(void 0, JSON.stringify(payload)));
345
+ const encodedHeader = _chunk5IOYCCFDcjs.bytesToBase64Url.call(void 0, _chunk5IOYCCFDcjs.utf8.call(void 0, JSON.stringify(header)));
346
+ const encodedPayload = _chunk5IOYCCFDcjs.bytesToBase64Url.call(void 0, _chunk5IOYCCFDcjs.utf8.call(void 0, JSON.stringify(payload)));
346
347
  const signingInput = `${encodedHeader}.${encodedPayload}`;
347
348
  const signature = sign(signingInput, secret);
348
349
  return `${signingInput}.${signature}`;
@@ -361,17 +362,17 @@ function verifyTenantToken(token, secret, options = {}) {
361
362
  let receivedBytes;
362
363
  let expectedBytes;
363
364
  try {
364
- receivedBytes = _chunkQGJSWMQPcjs.base64UrlToBytes.call(void 0, receivedSignature);
365
- expectedBytes = _chunkQGJSWMQPcjs.base64UrlToBytes.call(void 0, expectedSignature);
365
+ receivedBytes = _chunk5IOYCCFDcjs.base64UrlToBytes.call(void 0, receivedSignature);
366
+ expectedBytes = _chunk5IOYCCFDcjs.base64UrlToBytes.call(void 0, expectedSignature);
366
367
  } catch (e) {
367
368
  throw new Error("INVALID_TENANT_AUTH");
368
369
  }
369
- if (!_chunkQGJSWMQPcjs.timingSafeEqualBytes.call(void 0, receivedBytes, expectedBytes)) {
370
+ if (!_chunk5IOYCCFDcjs.timingSafeEqualBytes.call(void 0, receivedBytes, expectedBytes)) {
370
371
  throw new Error("INVALID_TENANT_AUTH");
371
372
  }
372
373
  let payload;
373
374
  try {
374
- payload = JSON.parse(_chunkQGJSWMQPcjs.utf8Decode.call(void 0, _chunkQGJSWMQPcjs.base64UrlToBytes.call(void 0, encodedPayload)));
375
+ payload = JSON.parse(_chunk5IOYCCFDcjs.utf8Decode.call(void 0, _chunk5IOYCCFDcjs.base64UrlToBytes.call(void 0, encodedPayload)));
375
376
  } catch (e2) {
376
377
  throw new Error("INVALID_TENANT_AUTH");
377
378
  }
@@ -582,13 +583,14 @@ async function createReiServer(config) {
582
583
  return {
583
584
  handlers: {
584
585
  initTenant: createInitTenantHandler(ctx),
585
- getUserKey: _chunkQGJSWMQPcjs.createGetUserKeyHandler.call(void 0, ctx),
586
- scheduleMessage: _chunkQGJSWMQPcjs.createScheduleMessageHandler.call(void 0, ctx),
586
+ getUserKey: _chunk5IOYCCFDcjs.createGetUserKeyHandler.call(void 0, ctx),
587
+ scheduleMessage: _chunk5IOYCCFDcjs.createScheduleMessageHandler.call(void 0, ctx),
587
588
  sendNotifications: createSendNotificationsHandler(ctx),
588
- updateMessage: _chunkQGJSWMQPcjs.createUpdateMessageHandler.call(void 0, ctx),
589
- cancelMessage: _chunkQGJSWMQPcjs.createCancelMessageHandler.call(void 0, ctx),
590
- messages: _chunkQGJSWMQPcjs.createMessagesHandler.call(void 0, ctx),
591
- pushSubscription: _chunkQGJSWMQPcjs.createPushSubscriptionHandler.call(void 0, ctx)
589
+ updateMessage: _chunk5IOYCCFDcjs.createUpdateMessageHandler.call(void 0, ctx),
590
+ cancelMessage: _chunk5IOYCCFDcjs.createCancelMessageHandler.call(void 0, ctx),
591
+ messages: _chunk5IOYCCFDcjs.createMessagesHandler.call(void 0, ctx),
592
+ getMessage: _chunk5IOYCCFDcjs.createGetMessageHandler.call(void 0, ctx),
593
+ pushSubscription: _chunk5IOYCCFDcjs.createPushSubscriptionHandler.call(void 0, ctx)
592
594
  }
593
595
  };
594
596
  }
@@ -623,4 +625,4 @@ async function createReiServer(config) {
623
625
 
624
626
 
625
627
 
626
- exports.MAX_PUSH_PAYLOAD_BYTES = _chunkQGJSWMQPcjs.MAX_PUSH_PAYLOAD_BYTES; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkQGJSWMQPcjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkQGJSWMQPcjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkQGJSWMQPcjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkQGJSWMQPcjs.advanceOccurrence; exports.createAdapter = createAdapter; exports.createD1Adapter = _chunkQGJSWMQPcjs.createD1Adapter; exports.createReiServer = createReiServer; exports.createSingleUserCloudflareWorker = _chunkQGJSWMQPcjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkQGJSWMQPcjs.createSingleUserServer; exports.createTenantToken = createTenantToken; exports.createWebCryptoWebPush = _chunkQGJSWMQPcjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkQGJSWMQPcjs.decryptFromStorage; exports.decryptPayload = _chunkQGJSWMQPcjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkQGJSWMQPcjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkQGJSWMQPcjs.encryptForStorage; exports.isValidISO8601 = _chunkQGJSWMQPcjs.isValidISO8601; exports.isValidTimeZoneId = _chunkQGJSWMQPcjs.isValidTimeZoneId; exports.isValidUUID = _chunkQGJSWMQPcjs.isValidUUID; exports.isValidUUIDv4 = _chunkQGJSWMQPcjs.isValidUUIDv4; exports.isValidUrl = _chunkQGJSWMQPcjs.isValidUrl; exports.measurePushPayload = _chunkQGJSWMQPcjs.measurePushPayload; exports.nextFutureOccurrence = _chunkQGJSWMQPcjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkQGJSWMQPcjs.planNextOccurrence; exports.runScheduledTick = _chunkQGJSWMQPcjs.runScheduledTick; exports.validateAvatarUrl = _chunkQGJSWMQPcjs.validateAvatarUrl; exports.validateLlmMessagesArray = _chunkQGJSWMQPcjs.validateLlmMessagesArray; exports.validateScheduleMessagePayload = _chunkQGJSWMQPcjs.validateScheduleMessagePayload; exports.validateSplitPattern = _chunkQGJSWMQPcjs.validateSplitPattern; exports.verifyTenantToken = verifyTenantToken;
628
+ exports.MAX_PUSH_PAYLOAD_BYTES = _chunk5IOYCCFDcjs.MAX_PUSH_PAYLOAD_BYTES; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunk5IOYCCFDcjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunk5IOYCCFDcjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunk5IOYCCFDcjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunk5IOYCCFDcjs.advanceOccurrence; exports.createAdapter = createAdapter; exports.createD1Adapter = _chunk5IOYCCFDcjs.createD1Adapter; exports.createReiServer = createReiServer; exports.createSingleUserCloudflareWorker = _chunk5IOYCCFDcjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunk5IOYCCFDcjs.createSingleUserServer; exports.createTenantToken = createTenantToken; exports.createWebCryptoWebPush = _chunk5IOYCCFDcjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunk5IOYCCFDcjs.decryptFromStorage; exports.decryptPayload = _chunk5IOYCCFDcjs.decryptPayload; exports.deriveUserEncryptionKey = _chunk5IOYCCFDcjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunk5IOYCCFDcjs.encryptForStorage; exports.isValidISO8601 = _chunk5IOYCCFDcjs.isValidISO8601; exports.isValidTimeZoneId = _chunk5IOYCCFDcjs.isValidTimeZoneId; exports.isValidUUID = _chunk5IOYCCFDcjs.isValidUUID; exports.isValidUUIDv4 = _chunk5IOYCCFDcjs.isValidUUIDv4; exports.isValidUrl = _chunk5IOYCCFDcjs.isValidUrl; exports.measurePushPayload = _chunk5IOYCCFDcjs.measurePushPayload; exports.nextFutureOccurrence = _chunk5IOYCCFDcjs.nextFutureOccurrence; exports.planNextOccurrence = _chunk5IOYCCFDcjs.planNextOccurrence; exports.runScheduledTick = _chunk5IOYCCFDcjs.runScheduledTick; exports.validateAvatarUrl = _chunk5IOYCCFDcjs.validateAvatarUrl; exports.validateLlmMessagesArray = _chunk5IOYCCFDcjs.validateLlmMessagesArray; exports.validateScheduleMessagePayload = _chunk5IOYCCFDcjs.validateScheduleMessagePayload; exports.validateSplitPattern = _chunk5IOYCCFDcjs.validateSplitPattern; exports.verifyTenantToken = verifyTenantToken;
package/dist/index.d.cts CHANGED
@@ -28,6 +28,7 @@
28
28
  * @property {{ PUT: function }} updateMessage
29
29
  * @property {{ DELETE: function }} cancelMessage
30
30
  * @property {{ GET: function }} messages
31
+ * @property {{ GET: function }} getMessage
31
32
  * @property {{ PUT: function, GET: function, DELETE: function }} pushSubscription
32
33
  */
33
34
  /**
@@ -121,6 +122,9 @@ export type ReiHandlers = {
121
122
  messages: {
122
123
  GET: Function;
123
124
  };
125
+ getMessage: {
126
+ GET: Function;
127
+ };
124
128
  pushSubscription: {
125
129
  PUT: Function;
126
130
  GET: Function;
package/dist/index.d.ts CHANGED
@@ -28,6 +28,7 @@
28
28
  * @property {{ PUT: function }} updateMessage
29
29
  * @property {{ DELETE: function }} cancelMessage
30
30
  * @property {{ GET: function }} messages
31
+ * @property {{ GET: function }} getMessage
31
32
  * @property {{ PUT: function, GET: function, DELETE: function }} pushSubscription
32
33
  */
33
34
  /**
@@ -121,6 +122,9 @@ export type ReiHandlers = {
121
122
  messages: {
122
123
  GET: Function;
123
124
  };
125
+ getMessage: {
126
+ GET: Function;
127
+ };
124
128
  pushSubscription: {
125
129
  PUT: Function;
126
130
  GET: Function;
package/dist/index.mjs CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  bytesToBase64Url,
9
9
  createCancelMessageHandler,
10
10
  createD1Adapter,
11
+ createGetMessageHandler,
11
12
  createGetUserKeyHandler,
12
13
  createMessagesHandler,
13
14
  createPushSubscriptionHandler,
@@ -38,7 +39,7 @@ import {
38
39
  validateLlmMessagesArray,
39
40
  validateScheduleMessagePayload,
40
41
  validateSplitPattern
41
- } from "./chunk-HZDUURIR.mjs";
42
+ } from "./chunk-TV7N3RTH.mjs";
42
43
 
43
44
  // src/server/adapters/factory.js
44
45
  async function createAdapter(config) {
@@ -54,11 +55,11 @@ async function createAdapter(config) {
54
55
  }
55
56
  switch (config.driver) {
56
57
  case "neon": {
57
- const { NeonAdapter } = await import("./neon-YUKD2B77.mjs");
58
+ const { NeonAdapter } = await import("./neon-CUIGAB2T.mjs");
58
59
  return new NeonAdapter(config.connectionString);
59
60
  }
60
61
  case "pg": {
61
- const { PgAdapter } = await import("./pg-JNQ6VMXC.mjs");
62
+ const { PgAdapter } = await import("./pg-KMJPL6QV.mjs");
62
63
  return new PgAdapter(config.connectionString);
63
64
  }
64
65
  default:
@@ -588,6 +589,7 @@ async function createReiServer(config) {
588
589
  updateMessage: createUpdateMessageHandler(ctx),
589
590
  cancelMessage: createCancelMessageHandler(ctx),
590
591
  messages: createMessagesHandler(ctx),
592
+ getMessage: createGetMessageHandler(ctx),
591
593
  pushSubscription: createPushSubscriptionHandler(ctx)
592
594
  }
593
595
  };
@@ -32,6 +32,23 @@ export function occurrenceMsOf(task: {
32
32
  * @returns {boolean}
33
33
  */
34
34
  export function taskNeedsLlm(decryptedPayload: any): boolean;
35
+ /**
36
+ * Frozen, credential-free view of the task for hook authors.
37
+ *
38
+ * nextSendAt 是这条任务原本的触发时刻。run-tick 领取任务时写的是 lease_until,
39
+ * next_send_at 那一列不动,所以这里给出去的和库里的是同一个值——宿主拿它当
40
+ * 时间锚点(窗口判断、缓存键)时对得上。
41
+ *
42
+ * 导出给 run-tick 用:`serializeBy` 要从任务内容里取分组 key,拿到的就该是
43
+ * 这一份(凭据剔掉、和 onBeforeFire 的 ctx.task 同一个形状),宿主不用为了
44
+ * 分组再学第二套字段。
45
+ */
46
+ export function buildHookTask(task: any, decryptedPayload: any): Readonly<{
47
+ id: any;
48
+ uuid: any;
49
+ nextSendAt: any;
50
+ retryCount: any;
51
+ }>;
35
52
  /**
36
53
  * Try the hook-driven fire path for one task.
37
54
  *
@@ -48,6 +65,9 @@ export function taskNeedsLlm(decryptedPayload: any): boolean;
48
65
  * success handling as the post-LLM skip-push path.
49
66
  * Failures (timeout / loop exceeded / config errors) throw — the caller's
50
67
  * existing error handling turns them into task retry/failure.
68
+ *
69
+ * 收尾回执:onBeforeFire 一旦被调用,上面每一种结局(含抛错)都会调一次
70
+ * `ctx.onFireSettled`,见 notifyFireSettled。
51
71
  */
52
72
  export function runAgenticFire({ task, decryptedPayload, userKey, ctx }: {
53
73
  task: import("../adapters/interface.js").TaskRow;
@@ -1,3 +1,17 @@
1
+ /**
2
+ * 把宿主给的分组 key 变成落库用的分组标识。
3
+ *
4
+ * 不直接存 key 本身:它多半是角色 id / 联系人名这类宿主数据,而任务内容一律
5
+ * 加密落库,多开一列明文出口不合适。这里用该用户的存储密钥做 HMAC——同一个
6
+ * 用户的同一个 key 每次都得到同一个值(分组判定要的就是相等性),换个用户
7
+ * 得到的值必然不同(分组天然按用户隔开,SQL 那边不用再比 user_id),拿到数
8
+ * 据库也反推不回原 key。
9
+ *
10
+ * @param {string} userKey - 该用户的存储密钥(deriveUserEncryptionKey 的结果)
11
+ * @param {string} rawKey - serializeBy 返回的分组 key
12
+ * @returns {Promise<string>} 43 字符的 base64url 串(列宽 64 够放)
13
+ */
14
+ export function deriveSerializeGroup(userKey: string, rawKey: string): Promise<string>;
1
15
  export function runScheduledTick(ctx: any): Promise<{
2
16
  totalTasks: any;
3
17
  successCount: number;
@@ -6,6 +20,7 @@ export function runScheduledTick(ctx: any): Promise<{
6
20
  executionTime: number;
7
21
  details: {
8
22
  claimSkippedTasks: number;
23
+ serializeSkippedTasks: number;
9
24
  deletedOnceOffTasks: number;
10
25
  updatedRecurringTasks: number;
11
26
  staleTasks: any[];
@@ -24,14 +24,23 @@
24
24
  * @property {string|null} updatedAt
25
25
  * @property {string|null} charId - 取自 metadata.charId
26
26
  * @property {string|null} clientTaskId - 取自 metadata.amsgClientTaskId
27
+ * @property {Object|null} [metadata] - 只有 includeMetadata 时才有,见下
27
28
  * @property {{ at: string, occurrence: string, reason: string }|null} lastError
28
29
  */
29
30
  /**
30
31
  * @param {Object} row - 数据库任务行
31
32
  * @param {Object} decryptedPayload - 解密后的任务 payload
33
+ * @param {{ includeMetadata?: boolean }} [options]
34
+ * includeMetadata 时多带一个完整的 `metadata`。列表接口不带:一页最多 100
35
+ * 条,每条都驮着宿主的整份 metadata 会把响应撑得很大,而列表要的只是「有哪
36
+ * 些任务」。要整份 metadata 的是另一件事——`PUT /update-message` 对 metadata
37
+ * 是整体替换,宿主想只改其中一个子字段就必须先读回完整的那份,那条路径走
38
+ * `GET /message?id=<uuid>`(单条,带完整 metadata)。
32
39
  * @returns {TaskProjection}
33
40
  */
34
- export function projectTask(row: any, decryptedPayload: any): TaskProjection;
41
+ export function projectTask(row: any, decryptedPayload: any, options?: {
42
+ includeMetadata?: boolean;
43
+ }): TaskProjection;
35
44
  export type TaskProjection = {
36
45
  id: number | null;
37
46
  uuid: string | null;
@@ -56,6 +65,10 @@ export type TaskProjection = {
56
65
  * - 取自 metadata.amsgClientTaskId
57
66
  */
58
67
  clientTaskId: string | null;
68
+ /**
69
+ * - 只有 includeMetadata 时才有,见下
70
+ */
71
+ metadata?: any | null;
59
72
  lastError: {
60
73
  at: string;
61
74
  occurrence: string;
@@ -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/neon.js
12
12
  var _serverless = require('@neondatabase/serverless');
@@ -25,13 +25,13 @@ var NeonAdapter = class {
25
25
  }
26
26
  async initSchema() {
27
27
  const sql = this._getSql();
28
- await sql.query(_chunk4SIN2R66cjs.TABLE_SQL);
29
- await sql.query(_chunk4SIN2R66cjs.PUSH_SUBSCRIPTION_TABLE_SQL);
30
- for (const migration of _chunk4SIN2R66cjs.MIGRATIONS) {
28
+ await sql.query(_chunkV2SDWGUBcjs.TABLE_SQL);
29
+ await sql.query(_chunkV2SDWGUBcjs.PUSH_SUBSCRIPTION_TABLE_SQL);
30
+ for (const migration of _chunkV2SDWGUBcjs.MIGRATIONS) {
31
31
  await sql.query(migration.sql);
32
32
  }
33
33
  const indexResults = [];
34
- for (const index of _chunk4SIN2R66cjs.INDEXES) {
34
+ for (const index of _chunkV2SDWGUBcjs.INDEXES) {
35
35
  try {
36
36
  await sql.query(index.sql);
37
37
  indexResults.push({
@@ -57,11 +57,11 @@ var NeonAdapter = class {
57
57
  `Critical index creation failed (${failedNames}). Please remove duplicate UUID rows and run initSchema again.`
58
58
  );
59
59
  }
60
- const tableCheck = await sql.query(_chunk4SIN2R66cjs.VERIFY_TABLE_SQL);
60
+ const tableCheck = await sql.query(_chunkV2SDWGUBcjs.VERIFY_TABLE_SQL);
61
61
  if (tableCheck.length === 0) {
62
62
  throw new Error("Table creation verification failed");
63
63
  }
64
- const columns = await sql.query(_chunk4SIN2R66cjs.COLUMNS_SQL);
64
+ const columns = await sql.query(_chunkV2SDWGUBcjs.COLUMNS_SQL);
65
65
  return {
66
66
  columnsCreated: columns.length,
67
67
  indexesCreated: indexResults.filter((r) => r.status === "success").length,
@@ -91,7 +91,7 @@ var NeonAdapter = class {
91
91
  async getTaskByUuid(uuid, userId) {
92
92
  const sql = this._getSql();
93
93
  const rows = await sql.query(
94
- `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
94
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
95
95
  FROM scheduled_messages
96
96
  WHERE uuid = $1 AND user_id = $2 AND status = 'pending'
97
97
  LIMIT 1`,
@@ -116,7 +116,7 @@ var NeonAdapter = 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 neon] rejected unknown update column: ${key}`);
121
121
  }
122
122
  sets.push(`${key} = $${idx}`);
@@ -138,7 +138,7 @@ var NeonAdapter = class {
138
138
  let idx = 2;
139
139
  if (extraFields) {
140
140
  for (const [key, value] of Object.entries(extraFields)) {
141
- if (!_chunk4SIN2R66cjs.UPDATABLE_COLUMNS.has(key)) {
141
+ if (!_chunkV2SDWGUBcjs.UPDATABLE_COLUMNS.has(key)) {
142
142
  throw new Error(`[amsg-server neon] rejected unknown update column: ${key}`);
143
143
  }
144
144
  sets.push(`${key} = $${idx}`);
@@ -175,6 +175,7 @@ var NeonAdapter = class {
175
175
  FROM scheduled_messages
176
176
  WHERE status = 'pending' AND next_send_at <= NOW()
177
177
  AND (lease_until IS NULL OR lease_until <= NOW())
178
+ AND (retry_after IS NULL OR retry_after <= NOW())
178
179
  ORDER BY next_send_at ASC
179
180
  LIMIT $1`,
180
181
  [limit]
@@ -200,22 +201,43 @@ var NeonAdapter = class {
200
201
  * 比 next_send_at 时两边都截到毫秒:列是 timestamptz(微秒精度),驱动读
201
202
  * 出来是 JS Date(毫秒精度),原值送回去可能因为亚毫秒差对不上。
202
203
  *
204
+ * 带 serializeGroup 时多一道分组门:同一分组里已经有别的行拿着未到期的租
205
+ * 约,这条就领不走(同一分组同时只跑一条)。判定和写租约在同一条 UPDATE
206
+ * 里完成,「先查再占」的空档天然不存在。分组门只看租约,不看
207
+ * `retry_after`:等着重试的任务其实闲着,不该把同分组的其他任务一起堵住。
208
+ *
203
209
  * @param {number} taskId
204
210
  * @param {string|Date} expectedNextSendAt - 读这行时拿到的 next_send_at 原值
205
211
  * @param {string|Date} leaseUntil - 租期末尾
206
- * @returns {Promise<boolean>} true = 领到了;false = 别人正拿着租约、排期被改过、或行已不是 pending
212
+ * @param {string|null} [serializeGroup] - 串行分组标识;空表示不参与分组串行
213
+ * @returns {Promise<boolean>} true = 领到了;false = 别人正拿着租约、同分组
214
+ * 有任务正在跑、排期被改过、或行已不是 pending
207
215
  */
208
- async claimTask(taskId, expectedNextSendAt, leaseUntil) {
216
+ async claimTask(taskId, expectedNextSendAt, leaseUntil, serializeGroup = null) {
209
217
  const sql = this._getSql();
218
+ const grouped = typeof serializeGroup === "string" && serializeGroup.length > 0;
219
+ const params = [leaseUntil, taskId, expectedNextSendAt];
220
+ let setClause = "lease_until = $1, updated_at = NOW()";
221
+ let groupGuard = "";
222
+ if (grouped) {
223
+ params.push(serializeGroup);
224
+ setClause = "lease_until = $1, serialize_group = $4, updated_at = NOW()";
225
+ groupGuard = `
226
+ AND NOT EXISTS (
227
+ SELECT 1 FROM scheduled_messages busy
228
+ WHERE busy.serialize_group = $4 AND busy.id <> $2
229
+ AND busy.status = 'pending' AND busy.lease_until > NOW()
230
+ )`;
231
+ }
210
232
  const rows = await sql.query(
211
233
  `UPDATE scheduled_messages
212
- SET lease_until = $1, updated_at = NOW()
234
+ SET ${setClause}
213
235
  WHERE id = $2 AND status = 'pending'
214
236
  AND date_trunc('milliseconds', next_send_at)
215
237
  = date_trunc('milliseconds', $3::timestamptz)
216
- AND (lease_until IS NULL OR lease_until <= NOW())
238
+ AND (lease_until IS NULL OR lease_until <= NOW())${groupGuard}
217
239
  RETURNING id`,
218
- [leaseUntil, taskId, expectedNextSendAt]
240
+ params
219
241
  );
220
242
  return rows.length > 0;
221
243
  }
@@ -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/neon.js
12
12
  import { neon } from "@neondatabase/serverless";
@@ -91,7 +91,7 @@ var NeonAdapter = class {
91
91
  async getTaskByUuid(uuid, userId) {
92
92
  const sql = this._getSql();
93
93
  const rows = await sql.query(
94
- `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count
94
+ `SELECT id, user_id, uuid, encrypted_payload, message_type, next_send_at, status, retry_count, created_at, updated_at
95
95
  FROM scheduled_messages
96
96
  WHERE uuid = $1 AND user_id = $2 AND status = 'pending'
97
97
  LIMIT 1`,
@@ -175,6 +175,7 @@ var NeonAdapter = class {
175
175
  FROM scheduled_messages
176
176
  WHERE status = 'pending' AND next_send_at <= NOW()
177
177
  AND (lease_until IS NULL OR lease_until <= NOW())
178
+ AND (retry_after IS NULL OR retry_after <= NOW())
178
179
  ORDER BY next_send_at ASC
179
180
  LIMIT $1`,
180
181
  [limit]
@@ -200,22 +201,43 @@ var NeonAdapter = class {
200
201
  * 比 next_send_at 时两边都截到毫秒:列是 timestamptz(微秒精度),驱动读
201
202
  * 出来是 JS Date(毫秒精度),原值送回去可能因为亚毫秒差对不上。
202
203
  *
204
+ * 带 serializeGroup 时多一道分组门:同一分组里已经有别的行拿着未到期的租
205
+ * 约,这条就领不走(同一分组同时只跑一条)。判定和写租约在同一条 UPDATE
206
+ * 里完成,「先查再占」的空档天然不存在。分组门只看租约,不看
207
+ * `retry_after`:等着重试的任务其实闲着,不该把同分组的其他任务一起堵住。
208
+ *
203
209
  * @param {number} taskId
204
210
  * @param {string|Date} expectedNextSendAt - 读这行时拿到的 next_send_at 原值
205
211
  * @param {string|Date} leaseUntil - 租期末尾
206
- * @returns {Promise<boolean>} true = 领到了;false = 别人正拿着租约、排期被改过、或行已不是 pending
212
+ * @param {string|null} [serializeGroup] - 串行分组标识;空表示不参与分组串行
213
+ * @returns {Promise<boolean>} true = 领到了;false = 别人正拿着租约、同分组
214
+ * 有任务正在跑、排期被改过、或行已不是 pending
207
215
  */
208
- async claimTask(taskId, expectedNextSendAt, leaseUntil) {
216
+ async claimTask(taskId, expectedNextSendAt, leaseUntil, serializeGroup = null) {
209
217
  const sql = this._getSql();
218
+ const grouped = typeof serializeGroup === "string" && serializeGroup.length > 0;
219
+ const params = [leaseUntil, taskId, expectedNextSendAt];
220
+ let setClause = "lease_until = $1, updated_at = NOW()";
221
+ let groupGuard = "";
222
+ if (grouped) {
223
+ params.push(serializeGroup);
224
+ setClause = "lease_until = $1, serialize_group = $4, updated_at = NOW()";
225
+ groupGuard = `
226
+ AND NOT EXISTS (
227
+ SELECT 1 FROM scheduled_messages busy
228
+ WHERE busy.serialize_group = $4 AND busy.id <> $2
229
+ AND busy.status = 'pending' AND busy.lease_until > NOW()
230
+ )`;
231
+ }
210
232
  const rows = await sql.query(
211
233
  `UPDATE scheduled_messages
212
- SET lease_until = $1, updated_at = NOW()
234
+ SET ${setClause}
213
235
  WHERE id = $2 AND status = 'pending'
214
236
  AND date_trunc('milliseconds', next_send_at)
215
237
  = date_trunc('milliseconds', $3::timestamptz)
216
- AND (lease_until IS NULL OR lease_until <= NOW())
238
+ AND (lease_until IS NULL OR lease_until <= NOW())${groupGuard}
217
239
  RETURNING id`,
218
- [leaseUntil, taskId, expectedNextSendAt]
240
+ params
219
241
  );
220
242
  return rows.length > 0;
221
243
  }