@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.
- package/README.md +68 -3
- package/dist/adapters/d1.d.ts +10 -2
- package/dist/adapters/interface.d.ts +12 -5
- package/dist/adapters/neon.d.ts +9 -2
- package/dist/adapters/pg.d.ts +9 -2
- package/dist/adapters/schema.d.ts +12 -1
- package/dist/adapters/schema.sqlite.d.ts +4 -1
- package/dist/{chunk-QGJSWMQP.cjs → chunk-5IOYCCFD.cjs} +302 -38
- package/dist/{chunk-WAFOSZL3.mjs → chunk-B2FDV7YK.mjs} +21 -0
- package/dist/{chunk-HZDUURIR.mjs → chunk-TV7N3RTH.mjs} +301 -37
- package/dist/{chunk-4SIN2R66.cjs → chunk-V2SDWGUB.cjs} +21 -0
- package/dist/cloudflare.cjs +2 -2
- package/dist/cloudflare.mjs +1 -1
- package/dist/handlers/get-message.d.ts +3 -0
- package/dist/index.cjs +23 -21
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.mjs +5 -3
- package/dist/lib/agentic-fire.d.ts +20 -0
- package/dist/lib/run-tick.d.ts +15 -0
- package/dist/lib/task-projection.d.ts +14 -1
- package/dist/{neon-2CS4TLNS.cjs → neon-3ASBLLCM.cjs} +37 -15
- package/dist/{neon-YUKD2B77.mjs → neon-CUIGAB2T.mjs} +29 -7
- package/dist/{pg-PM5RYRWH.cjs → pg-IEZTAR43.cjs} +37 -15
- package/dist/{pg-JNQ6VMXC.mjs → pg-KMJPL6QV.mjs} +29 -7
- package/dist/single-user.d.ts +4 -0
- package/package.json +1 -1
package/dist/cloudflare.cjs
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
var
|
|
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 =
|
|
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;
|
package/dist/cloudflare.mjs
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
|
|
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-
|
|
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-
|
|
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 =
|
|
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 =
|
|
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 && !
|
|
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
|
|
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
|
|
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 =
|
|
345
|
-
const encodedPayload =
|
|
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 =
|
|
365
|
-
expectedBytes =
|
|
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 (!
|
|
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(
|
|
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:
|
|
586
|
-
scheduleMessage:
|
|
586
|
+
getUserKey: _chunk5IOYCCFDcjs.createGetUserKeyHandler.call(void 0, ctx),
|
|
587
|
+
scheduleMessage: _chunk5IOYCCFDcjs.createScheduleMessageHandler.call(void 0, ctx),
|
|
587
588
|
sendNotifications: createSendNotificationsHandler(ctx),
|
|
588
|
-
updateMessage:
|
|
589
|
-
cancelMessage:
|
|
590
|
-
messages:
|
|
591
|
-
|
|
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 =
|
|
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-
|
|
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-
|
|
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-
|
|
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;
|
package/dist/lib/run-tick.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
29
|
-
await sql.query(
|
|
30
|
-
for (const migration of
|
|
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
|
|
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(
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
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
|
-
* @
|
|
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
|
|
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
|
-
|
|
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-
|
|
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
|
-
* @
|
|
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
|
|
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
|
-
|
|
240
|
+
params
|
|
219
241
|
);
|
|
220
242
|
return rows.length > 0;
|
|
221
243
|
}
|