@rei-standard/amsg-server 2.6.0-next.5 → 2.6.0-next.7
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 +42 -0
- package/dist/{chunk-RGECD4OH.cjs → chunk-DCKCAGFV.cjs} +10 -1
- package/dist/{chunk-5ENJGVZX.mjs → chunk-GOBCWCJP.mjs} +9 -0
- package/dist/{chunk-ES2HUXYZ.mjs → chunk-I7EUMYQ4.mjs} +283 -81
- package/dist/{chunk-IFI4EWDW.cjs → chunk-YQPWXPIN.cjs} +284 -82
- package/dist/{cloudflare-DM_Njij8.d.cts → cloudflare-BBPmRS2L.d.cts} +488 -93
- package/dist/{cloudflare-DM_Njij8.d.ts → cloudflare-BBPmRS2L.d.ts} +488 -93
- package/dist/cloudflare.cjs +10 -2
- package/dist/cloudflare.d.cts +1 -1
- package/dist/cloudflare.d.ts +1 -1
- package/dist/cloudflare.mjs +9 -1
- package/dist/index.cjs +21 -13
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +11 -3
- package/dist/{neon-DYvGnCzx.d.ts → neon-D8Z2Nn97.d.cts} +45 -1
- package/dist/{neon-CJl66EGy.d.cts → neon-PqNFJIz2.d.ts} +45 -1
- package/dist/{neon-CU5N3CSW.mjs → neon-QJ7KYHEX.mjs} +45 -1
- package/dist/{neon-BFUS25UX.cjs → neon-V4VOH55N.cjs} +49 -5
- package/dist/{pg-IIH3M4OM.cjs → pg-6X74HKMX.cjs} +48 -5
- package/dist/{pg-Du-pN_UT.d.ts → pg-C48FwCWW.d.cts} +44 -1
- package/dist/{pg-07S-u_H4.d.cts → pg-C5gtWUfU.d.ts} +44 -1
- package/dist/{pg-QO6NKTGL.mjs → pg-DFJSFOJQ.mjs} +44 -1
- package/dist/{schema-C8OnYk6j.d.cts → schema-BHuyH_6Z.d.cts} +15 -1
- package/dist/{schema-C8OnYk6j.d.ts → schema-BHuyH_6Z.d.ts} +15 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,10 +104,32 @@ AI 配置消息的提示词可以用两种形态之一,**互斥二选一**:
|
|
|
104
104
|
- 限制:每项 ≤ 200 字符,数组 ≤ 10 项;非法或无法 `new RegExp(...)` 通过 → `400 INVALID_PARAMETERS`(schedule)/ `400 INVALID_UPDATE_DATA`(update)。
|
|
105
105
|
- `update-message` 显式传 `splitPattern: null` 可重置回默认;不传则保留原值。
|
|
106
106
|
|
|
107
|
+
## 一条 Web Push 能塞多少
|
|
108
|
+
|
|
109
|
+
推送服务(FCM / APNs / Mozilla autopush)限的是**加密后** body 的 4096 字节,超了当场 413 拒收,用户什么也收不到。明文额度要把 aes128gcm 的固定开销减掉——header 86(salt 16 + record size 4 + keyid 长度 1 + 应用服务器公钥 65)+ 填充分隔符 1 + GCM auth tag 16 = 103 字节——所以**一条 push 的 payload 上限是 3993 字节**,按 UTF-8 字节算,不是字符数。
|
|
110
|
+
|
|
111
|
+
`sendWebPush` 会在发出去之前挡下超限的 payload,抛出 `err.code === 'PUSH_PAYLOAD_TOO_LARGE'` 的错误,消息里带实际字节数和上限。
|
|
112
|
+
|
|
113
|
+
组 payload 之前想自己做预算,用导出的常量和工具函数,别写死魔法数字:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
import { MAX_PUSH_PAYLOAD_BYTES, measurePushPayload } from '@rei-standard/amsg-server';
|
|
117
|
+
|
|
118
|
+
const { bytes, remainingBytes, withinLimit } = measurePushPayload(JSON.stringify(push));
|
|
119
|
+
// remainingBytes = 还能再塞多少字节(已超限时为负)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
装不下的内容(长文、附件详情)建议走旁路:正文存进 `client_state`,push 里只带一个引用键,客户端上线后用 `GET /client-state` 取回。单用户 Worker 的 fire-time hook 用 `ctx.writeState()` 写,见 [`examples/cloudflare-single-user/README.md`](https://github.com/Tosd0/ReiStandard/blob/main/packages/rei-standard-amsg/server/examples/cloudflare-single-user/README.md)。
|
|
123
|
+
|
|
107
124
|
## 导出(新增)
|
|
108
125
|
|
|
109
126
|
- `validateLlmMessagesArray(messages)` — 同步预校验 messages 数组,返回 `string | null`(错误信息 / 通过)。和 `@rei-standard/amsg-instant` 的校验规则字节级一致。
|
|
110
127
|
- `validateSplitPattern(value)` — 同步预校验 splitPattern(string / string[] / null),返回 `string | null`。
|
|
128
|
+
- `MAX_PUSH_PAYLOAD_BYTES` — 一条 push 的明文上限,3993 字节。
|
|
129
|
+
- `WEB_PUSH_MAX_BODY_BYTES` / `WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES` — 推送服务的密文 body 上限(4096)与 aes128gcm 固定开销(103),上面那个数就是两者相减。
|
|
130
|
+
- `measurePushPayload(payload)` — 量一段 payload 的字节数与剩余额度,返回 `{ bytes, maxBytes, remainingBytes, withinLimit }`。
|
|
131
|
+
|
|
132
|
+
以上四个在包根和 `@rei-standard/amsg-server/cloudflare` 两个入口都有。
|
|
111
133
|
|
|
112
134
|
## 一体化初始化流程
|
|
113
135
|
|
|
@@ -123,6 +145,22 @@ AI 配置消息的提示词可以用两种形态之一,**互斥二选一**:
|
|
|
123
145
|
- `send-notifications`
|
|
124
146
|
- `Authorization: Bearer <cronToken>` 或 `?token=<cronToken>`
|
|
125
147
|
|
|
148
|
+
## 触发任务时的占位
|
|
149
|
+
|
|
150
|
+
`send-notifications`(以及单用户 Worker 的 `scheduled()`)每条任务开跑前会先占位:在这一行的 `lease_until` 上写下「归我管到现在 + 租期为止」,本次投递期间别的 tick 领不走它;占位改到 0 行说明别人先领走了,本次直接跳过。cron 一分钟一跳而带工具的 AI 任务常常跑过一分钟,没有这层占位同一条任务会被相邻几跳重复触发。
|
|
151
|
+
|
|
152
|
+
租约写在自己的列上,`next_send_at` 全程不动——任务列表读到的一直是用户设的那个时刻,循环任务也按它推进到下一次。投递收尾时租约就放掉,失败重试的退避(2 分钟起)不会被租期压住。
|
|
153
|
+
|
|
154
|
+
领了任务的那一跳中途没了(Worker 被回收之类)就没人来放租约,这条任务要等租约到期才会被后面的 tick 接手。把租期设得比最慢的一次投递长一点即可。
|
|
155
|
+
|
|
156
|
+
租期默认 10 分钟;配了 `totalTimeoutMs` 的话按它 + 2 分钟往上抬。想自己定就在 `runScheduledTick` 的 ctx(或单用户 Worker 的 config)里传 `claimLeaseMs`——注意 `createReiServer` 内置的 `/send-notifications` 处理器不透传这两个值,要调租期就自己调 `runScheduledTick`。`onBeforeFire` 里按次放宽的预算占位时看不到,那种情况也要显式设 `claimLeaseMs`。
|
|
157
|
+
|
|
158
|
+
占位管的是定时触发这条路径。`messageType: 'instant'` 走的是「建行 → 当场投递」,不经过占位。
|
|
159
|
+
|
|
160
|
+
内置适配器都实现了占位。自定义适配器可以不实现 `claimTask`,跑得动,只是回到不占位的行为。
|
|
161
|
+
|
|
162
|
+
`lease_until` 是这次新加的列。走 `POST /init-tenant`(或任何一次 `initSchema`)会自动给已有的表补上;手工建表的看 `examples/cloudflare-single-user/schema.sql`。
|
|
163
|
+
|
|
126
164
|
## 导出 API(Exports)
|
|
127
165
|
|
|
128
166
|
- `createReiServer`
|
|
@@ -134,6 +172,10 @@ AI 配置消息的提示词可以用两种形态之一,**互斥二选一**:
|
|
|
134
172
|
- `encryptForStorage`
|
|
135
173
|
- `decryptFromStorage`
|
|
136
174
|
- `validateScheduleMessagePayload`
|
|
175
|
+
- `measurePushPayload`
|
|
176
|
+
- `MAX_PUSH_PAYLOAD_BYTES`
|
|
177
|
+
- `WEB_PUSH_MAX_BODY_BYTES`
|
|
178
|
+
- `WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES`
|
|
137
179
|
- `isValidISO8601`
|
|
138
180
|
- `isValidUrl`
|
|
139
181
|
- `isValidUUID`
|
|
@@ -7,12 +7,20 @@ var TABLE_SQL = `
|
|
|
7
7
|
encrypted_payload TEXT NOT NULL,
|
|
8
8
|
message_type VARCHAR(50) NOT NULL CHECK (message_type IN ('fixed', 'prompted', 'auto', 'instant')),
|
|
9
9
|
next_send_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
10
|
+
lease_until TIMESTAMP WITH TIME ZONE,
|
|
10
11
|
status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'sent', 'failed')),
|
|
11
12
|
retry_count INTEGER DEFAULT 0,
|
|
12
13
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
13
14
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
14
15
|
)
|
|
15
16
|
`;
|
|
17
|
+
var MIGRATIONS = [
|
|
18
|
+
{
|
|
19
|
+
name: "add_lease_until",
|
|
20
|
+
sql: "ALTER TABLE scheduled_messages ADD COLUMN IF NOT EXISTS lease_until TIMESTAMP WITH TIME ZONE",
|
|
21
|
+
description: "Task claim lease (2.6.0)"
|
|
22
|
+
}
|
|
23
|
+
];
|
|
16
24
|
var INDEXES = [
|
|
17
25
|
{
|
|
18
26
|
name: "idx_pending_tasks_optimized",
|
|
@@ -69,4 +77,5 @@ var COLUMNS_SQL = `
|
|
|
69
77
|
|
|
70
78
|
|
|
71
79
|
|
|
72
|
-
|
|
80
|
+
|
|
81
|
+
exports.TABLE_SQL = TABLE_SQL; exports.MIGRATIONS = MIGRATIONS; exports.INDEXES = INDEXES; exports.VERIFY_TABLE_SQL = VERIFY_TABLE_SQL; exports.COLUMNS_SQL = COLUMNS_SQL;
|
|
@@ -7,12 +7,20 @@ var TABLE_SQL = `
|
|
|
7
7
|
encrypted_payload TEXT NOT NULL,
|
|
8
8
|
message_type VARCHAR(50) NOT NULL CHECK (message_type IN ('fixed', 'prompted', 'auto', 'instant')),
|
|
9
9
|
next_send_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
10
|
+
lease_until TIMESTAMP WITH TIME ZONE,
|
|
10
11
|
status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'sent', 'failed')),
|
|
11
12
|
retry_count INTEGER DEFAULT 0,
|
|
12
13
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
13
14
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
14
15
|
)
|
|
15
16
|
`;
|
|
17
|
+
var MIGRATIONS = [
|
|
18
|
+
{
|
|
19
|
+
name: "add_lease_until",
|
|
20
|
+
sql: "ALTER TABLE scheduled_messages ADD COLUMN IF NOT EXISTS lease_until TIMESTAMP WITH TIME ZONE",
|
|
21
|
+
description: "Task claim lease (2.6.0)"
|
|
22
|
+
}
|
|
23
|
+
];
|
|
16
24
|
var INDEXES = [
|
|
17
25
|
{
|
|
18
26
|
name: "idx_pending_tasks_optimized",
|
|
@@ -66,6 +74,7 @@ var COLUMNS_SQL = `
|
|
|
66
74
|
|
|
67
75
|
export {
|
|
68
76
|
TABLE_SQL,
|
|
77
|
+
MIGRATIONS,
|
|
69
78
|
INDEXES,
|
|
70
79
|
VERIFY_TABLE_SQL,
|
|
71
80
|
COLUMNS_SQL
|