@rei-standard/amsg-server 2.6.0-next.2 → 2.6.0-next.21
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/LICENSE +21 -0
- package/README.md +599 -24
- package/dist/adapters/d1.d.ts +416 -0
- package/dist/adapters/factory.d.ts +39 -0
- package/dist/adapters/interface.d.ts +306 -0
- package/dist/adapters/neon.d.ts +88 -0
- package/dist/adapters/pg-shared.d.ts +161 -0
- package/dist/adapters/pg.d.ts +84 -0
- package/dist/adapters/schema.d.ts +57 -0
- package/dist/adapters/schema.sqlite.d.ts +49 -0
- package/dist/chunk-7NMQFTDJ.cjs +148 -0
- package/dist/chunk-E7OWP3VL.cjs +135 -0
- package/dist/chunk-GN44PST5.mjs +148 -0
- package/dist/chunk-KOJQVOYS.mjs +5847 -0
- package/dist/chunk-QHVID3V2.cjs +5847 -0
- package/dist/chunk-ZGGF4GMA.mjs +135 -0
- package/dist/cloudflare/single-user-worker.d.ts +37 -0
- package/dist/cloudflare.cjs +39 -2
- package/dist/cloudflare.d.ts +10 -2
- package/dist/cloudflare.mjs +40 -3
- package/dist/handlers/cancel-message.d.ts +3 -0
- package/dist/handlers/capabilities.d.ts +4 -0
- package/dist/handlers/client-state.d.ts +7 -0
- package/dist/handlers/get-message.d.ts +3 -0
- package/dist/handlers/get-user-key.d.ts +3 -0
- package/dist/handlers/init-tenant.d.ts +23 -0
- package/dist/handlers/llm-credentials.d.ts +5 -0
- package/dist/handlers/messages.d.ts +3 -0
- package/dist/handlers/outbox.d.ts +7 -0
- package/dist/handlers/push-subscription.d.ts +5 -0
- package/dist/handlers/schedule-message.d.ts +3 -0
- package/dist/handlers/send-notifications.d.ts +3 -0
- package/dist/handlers/single-user-init.d.ts +13 -0
- package/dist/handlers/update-message.d.ts +3 -0
- package/dist/handlers/vapid-public-key.d.ts +19 -0
- package/dist/index.cjs +133 -44
- package/dist/index.d.cts +131 -766
- package/dist/index.d.ts +131 -766
- package/dist/index.mjs +125 -36
- package/dist/lib/agentic-fire.d.ts +108 -0
- package/dist/lib/client-state-store.d.ts +80 -0
- package/dist/lib/constant-time.d.ts +14 -0
- package/dist/lib/db-errors.d.ts +10 -0
- package/dist/lib/encryption.d.ts +48 -0
- package/dist/lib/errors.d.ts +215 -0
- package/dist/lib/llm-credentials-store.d.ts +147 -0
- package/dist/lib/llm.d.ts +1 -0
- package/dist/lib/message-processor.d.ts +73 -0
- package/dist/lib/outbox-store.d.ts +95 -0
- package/dist/lib/push-subscription-store.d.ts +94 -0
- package/dist/lib/recurrence.d.ts +51 -0
- package/dist/lib/request.d.ts +182 -0
- package/dist/lib/result-emitter.d.ts +54 -0
- package/dist/lib/run-tick.d.ts +115 -0
- package/dist/lib/schema-version.d.ts +50 -0
- package/dist/lib/state-accessors.d.ts +70 -0
- package/dist/lib/state-chunks.d.ts +55 -0
- package/dist/lib/task-projection.d.ts +92 -0
- package/dist/lib/validation.d.ts +97 -0
- package/dist/lib/version.d.ts +7 -0
- package/dist/lib/webcrypto-utils.d.ts +1 -0
- package/dist/lib/webpush-webcrypto.d.ts +78 -0
- package/dist/{neon-BFUS25UX.cjs → neon-KP2CPA57.cjs} +110 -10
- package/dist/{neon-CU5N3CSW.mjs → neon-ZIMHALYI.mjs} +106 -6
- package/dist/{pg-QO6NKTGL.mjs → pg-QGC2XHUT.mjs} +94 -5
- package/dist/{pg-IIH3M4OM.cjs → pg-RZPQGXR3.cjs} +98 -9
- package/dist/single-user.d.ts +95 -0
- package/dist/tenant/blob-store.d.ts +30 -0
- package/dist/tenant/context.d.ts +47 -0
- package/dist/tenant/single-user-context.d.ts +29 -0
- package/dist/tenant/token.d.ts +26 -0
- package/package.json +3 -3
- package/dist/chunk-5ENJGVZX.mjs +0 -72
- package/dist/chunk-GN5QTMCL.cjs +0 -1870
- package/dist/chunk-PVNLB6TF.mjs +0 -1870
- package/dist/chunk-RGECD4OH.cjs +0 -72
- package/dist/cloudflare-B-E51VjP.d.cts +0 -2848
- package/dist/cloudflare-B-E51VjP.d.ts +0 -2848
- package/dist/cloudflare.d.cts +0 -2
- package/dist/neon-CJl66EGy.d.cts +0 -251
- package/dist/neon-DYvGnCzx.d.ts +0 -251
- package/dist/pg-07S-u_H4.d.cts +0 -246
- package/dist/pg-Du-pN_UT.d.ts +0 -246
- package/dist/schema-C8OnYk6j.d.cts +0 -74
- package/dist/schema-C8OnYk6j.d.ts +0 -74
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 这个错误是不是「重试也好不了」。认 NonRetryableError,也认任何自带
|
|
3
|
+
* `permanent: true` 的错误对象(跨包/跨版本安全)。
|
|
4
|
+
*
|
|
5
|
+
* @param {unknown} error
|
|
6
|
+
* @returns {boolean}
|
|
7
|
+
*/
|
|
8
|
+
export function isNonRetryableError(error: unknown): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* 把一个已经构造好的错误标成确定性失败(就地改,原样返回),顺带补一个稳定
|
|
11
|
+
* 的 `code`(错误自己带了 code 就不覆盖)。
|
|
12
|
+
*
|
|
13
|
+
* 用在错误由别处构造、又想保住它原本类型的时候——比如契约校验抛的
|
|
14
|
+
* `TypeError`:宿主按 `instanceof TypeError` 分流的代码不该因为库多标了一个
|
|
15
|
+
* 字段就走岔。自己现造错误的地方直接用 {@link NonRetryableError} 更直白。
|
|
16
|
+
*
|
|
17
|
+
* @template T
|
|
18
|
+
* @param {T} error
|
|
19
|
+
* @param {string} [code]
|
|
20
|
+
* @returns {T}
|
|
21
|
+
*/
|
|
22
|
+
export function markPermanent<T>(error: T, code?: string): T;
|
|
23
|
+
/**
|
|
24
|
+
* 这个错误是不是「投递期间任务被取消/顶替」。
|
|
25
|
+
*
|
|
26
|
+
* @param {unknown} error
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
export function isTaskCancelledError(error: unknown): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* 这次投递失败要不要判成永久性的(不再重试)。四个来源:
|
|
32
|
+
* - 已知的永久性错误码(见 {@link PERMANENT_ERROR_CODES});
|
|
33
|
+
* - hook 侧抛出的 NonRetryableError(`permanent`,见 {@link NonRetryableError})——
|
|
34
|
+
* fire_pack 缺失、解析失败这类重试必然同败的错,隔两分钟再试三次只是让用户
|
|
35
|
+
* 多白等十二分钟,还把情绪评估之类的计费重跑三遍;
|
|
36
|
+
* - 推送服务判了这条订阅的死刑(见 {@link TERMINAL_PUSH_STATUSES});
|
|
37
|
+
* - 推送服务说这条 payload 太大(见 {@link PUSH_PAYLOAD_TOO_LARGE_STATUS})。
|
|
38
|
+
*
|
|
39
|
+
* @param {{ permanent?: unknown, errorCode?: unknown, pushStatus?: unknown }} failure
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function isPermanentDeliveryFailure(failure: {
|
|
43
|
+
permanent?: unknown;
|
|
44
|
+
errorCode?: unknown;
|
|
45
|
+
pushStatus?: unknown;
|
|
46
|
+
}): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* 发一条 push,失败时自动标上「这是发 push 那一步抛的」。
|
|
49
|
+
*
|
|
50
|
+
* 发 push 的地方一律走这里,别各自写 `try { send } catch { throw
|
|
51
|
+
* tagPushStatusCode(e) }`:漏标一处,那条路上的 410 就读不出状态码,一条早就
|
|
52
|
+
* 失效的订阅会被当成普通失败,在退避阶梯上把 LLM 重跑三轮。忘了标是没有任何
|
|
53
|
+
* 提示的。
|
|
54
|
+
*
|
|
55
|
+
* @param {{ sendNotification: (subscription: any, payload: string) => Promise<any> }} webpush
|
|
56
|
+
* @param {any} subscription
|
|
57
|
+
* @param {string} payloadJson
|
|
58
|
+
* @returns {Promise<any>}
|
|
59
|
+
*/
|
|
60
|
+
export function sendTaggedPush(webpush: {
|
|
61
|
+
sendNotification: (subscription: any, payload: string) => Promise<any>;
|
|
62
|
+
}, subscription: any, payloadJson: string): Promise<any>;
|
|
63
|
+
/**
|
|
64
|
+
* 读推送服务回的状态码。没被 {@link tagPushStatusCode} 标过 → null。
|
|
65
|
+
*
|
|
66
|
+
* @param {unknown} error
|
|
67
|
+
* @returns {number|null}
|
|
68
|
+
*/
|
|
69
|
+
export function readPushStatusCode(error: unknown): number | null;
|
|
70
|
+
/**
|
|
71
|
+
* lastError 里的机读标注:`errorCode` 是底层错误的稳定 code(如
|
|
72
|
+
* `PUSH_PAYLOAD_TOO_LARGE`),`pushStatus` 是推送服务回的 HTTP 状态码。两个都
|
|
73
|
+
* 没有 → undefined,不往记录里塞空字段。
|
|
74
|
+
*
|
|
75
|
+
* reason 那句是给用户看的人话、措辞随时会变,判断「该让用户重建订阅还是裁短
|
|
76
|
+
* 内容」得读这两个字段。定时任务(run-tick)与 instant 任务
|
|
77
|
+
* (processMessagesByUuid)共用这一份,两条路记下来的形状一致。
|
|
78
|
+
*
|
|
79
|
+
* code 是标识符不是人话,不用脱敏,但也别让一个来路不明的超长 code 撑大明文
|
|
80
|
+
* 列——截到 64 字符,够放所有约定过的码。
|
|
81
|
+
*
|
|
82
|
+
* @param {string|null|undefined} errorCode
|
|
83
|
+
* @param {number|null|undefined} pushStatus
|
|
84
|
+
* @returns {{ errorCode?: string, pushStatus?: number }|undefined}
|
|
85
|
+
*/
|
|
86
|
+
export function buildErrorExtra(errorCode: string | null | undefined, pushStatus: number | null | undefined): {
|
|
87
|
+
errorCode?: string;
|
|
88
|
+
pushStatus?: number;
|
|
89
|
+
} | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* 把错误原因脱敏成能明文落库(任务行 last_error 列)的摘要。
|
|
92
|
+
*
|
|
93
|
+
* 任务内容一律密文落库,last_error 是唯一一列明文的「为什么没发出去」——
|
|
94
|
+
* 错误消息偶尔会回显请求细节(上游 API 的报错带 URL、header 片段),所以
|
|
95
|
+
* 长得像凭据的 token 一律遮掉,再截断。
|
|
96
|
+
*
|
|
97
|
+
* 遮什么、怎么遮由 @rei-standard/amsg-shared 的 `redactCredentials` 说了算,
|
|
98
|
+
* 这里只负责压平空白和截断。
|
|
99
|
+
*
|
|
100
|
+
* @param {unknown} reason
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
export function sanitizeErrorSummary(reason: unknown): string;
|
|
104
|
+
/**
|
|
105
|
+
* @typedef {Object} ErrorCause
|
|
106
|
+
* @property {'config'|'request'|'tick'} stage - 在哪一段炸的
|
|
107
|
+
* @property {string} name - 错误类型(`error.name`,认不出来时是 'Error')
|
|
108
|
+
* @property {string} [message] - 脱敏后的错误消息。降级 500(配置都没建起来那条
|
|
109
|
+
* 路)回给跨域调用方时不带这个字段——那条路的 CORS 头是回显来访 Origin 的,任
|
|
110
|
+
* 意第三方页面都能读,而构建期异常原文里常有 binding 名、内网域名、环境变量
|
|
111
|
+
* 名。同源 / 无 Origin 的请求照旧带全文,`wrangler tail` 里也一直有。
|
|
112
|
+
* @property {string} [code] - 错误自带的 `code` 字符串(有才带)
|
|
113
|
+
*/
|
|
114
|
+
/**
|
|
115
|
+
* 把一个异常整理成能随响应体一起回给调用方的机读原因。
|
|
116
|
+
*
|
|
117
|
+
* 500 只回一句「服务器内部错误」的话,真因(`D1_ERROR: no such table:
|
|
118
|
+
* message_outbox`、存储层写超时……)就只剩 console.error 里那一行,调用方拿不
|
|
119
|
+
* 到,用户看到的也只是「服务器内部错误」——不知道哪儿坏了,也不知道该点哪里。
|
|
120
|
+
* 这个函数把异常压成几个固定字段,让调用方能机读、能展示。
|
|
121
|
+
*
|
|
122
|
+
* 只带错误类型和消息文本。消息先过 `sanitizeErrorSummary`(遮掉长得像凭据的
|
|
123
|
+
* 串、截断到 500 字符);密钥、用户数据、任务正文都不在 `error.message` 上,
|
|
124
|
+
* 也不往这里放。
|
|
125
|
+
*
|
|
126
|
+
* @param {unknown} error - 捕获到的异常,也收 `{ name, message }` 这样的普通对象
|
|
127
|
+
* @param {'config'|'request'|'tick'} stage - 'config' = 构建配置时抛的(少了
|
|
128
|
+
* binding / 环境变量);'request' = 路由或处理器抛的;'tick' = cron 那一跳抛的
|
|
129
|
+
* @returns {ErrorCause}
|
|
130
|
+
*/
|
|
131
|
+
export function summarizeErrorCause(error: unknown, stage: "config" | "request" | "tick"): ErrorCause;
|
|
132
|
+
/**
|
|
133
|
+
* 确定性失败——重试必然同败,别再排退避阶梯。
|
|
134
|
+
*
|
|
135
|
+
* 典型场景全在 fire-time hook 里:onBeforeFire 发现 fire_pack 缺失、解析失
|
|
136
|
+
* 败、缺必要的段……这类错误不会因为过两分钟再跑一次就好,按普通投递失败重
|
|
137
|
+
* 试三轮只是让用户多白等十几分钟,还把 hook 里的计费调用(情绪评估之类)
|
|
138
|
+
* 重复烧三遍。hook 抛这个类(或任何带 `permanent: true` 的错误),run-tick
|
|
139
|
+
* 收到后跳过退避:一次性任务直接标 failed(原因进 last_error / payload 的
|
|
140
|
+
* lastError),循环任务直接作废本次 occurrence。
|
|
141
|
+
*
|
|
142
|
+
* `code`(可选)会透传到 processSingleMessage 的 errorCode,宿主想按错误类
|
|
143
|
+
* 别分流时用它。
|
|
144
|
+
*
|
|
145
|
+
* 用 `permanent` 属性而不是 instanceof 做判定:宿主的 worker 里可能打包了另
|
|
146
|
+
* 一份本库(版本错开、bundler 双实例),跨 realm 的 instanceof 靠不住。
|
|
147
|
+
*/
|
|
148
|
+
export class NonRetryableError extends Error {
|
|
149
|
+
/**
|
|
150
|
+
* @param {string} message
|
|
151
|
+
* @param {{ code?: string, cause?: unknown }} [options]
|
|
152
|
+
*/
|
|
153
|
+
constructor(message: string, options?: {
|
|
154
|
+
code?: string;
|
|
155
|
+
cause?: unknown;
|
|
156
|
+
});
|
|
157
|
+
permanent: boolean;
|
|
158
|
+
code: string;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 部署级的配置 / 适配器能力错误——坏的不是这条任务,是这个部署。
|
|
162
|
+
*
|
|
163
|
+
* 典型场景:`hooks.onBeforeFire` 配了但 `onLLMOutput` 忘了配、`executeToolCalls`
|
|
164
|
+
* 没配、自定义适配器缺 `createTask` / `deleteTaskByUuid` / `upsertClientState`。
|
|
165
|
+
* 这类错误重试确实好不了,但**不能**判终态:同一个坏部署下每条到点的任务都会
|
|
166
|
+
* 撞同一个错,判终态就等于在那段时间里把每一条一次性任务都永久标 `failed`,
|
|
167
|
+
* 运维改好配置重新部署也捞不回来了(行已不在 pending,`PUT /update-message`
|
|
168
|
+
* 回 409)。所以留在退避阶梯上——配置一修好,还在阶梯上的任务下一跳就正常发
|
|
169
|
+
* 出去。这跟 VAPID 配错回的 400/401/403 是同一个道理,见
|
|
170
|
+
* {@link PUSH_PAYLOAD_TOO_LARGE_STATUS} 那段注释。
|
|
171
|
+
*
|
|
172
|
+
* 带 `code` 但不带 `permanent`:失败原因照旧进 last_error 的 `errorCode`,宿主
|
|
173
|
+
* 想分流照样读得到,只是不再跳过重试。
|
|
174
|
+
*/
|
|
175
|
+
export class DeploymentConfigError extends Error {
|
|
176
|
+
/**
|
|
177
|
+
* @param {string} message
|
|
178
|
+
* @param {{ code?: string, cause?: unknown }} [options]
|
|
179
|
+
*/
|
|
180
|
+
constructor(message: string, options?: {
|
|
181
|
+
code?: string;
|
|
182
|
+
cause?: unknown;
|
|
183
|
+
});
|
|
184
|
+
code: string;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 投递期间发现「这条任务已经不归本次投递管了」时,推送侧抛的错误码:行被
|
|
188
|
+
* `DELETE /message` 取消、被 `supersedesUuid` 顶替,或被别的执行者收尾了。
|
|
189
|
+
*
|
|
190
|
+
* 发送方(message-processor 的正文投递、agentic-fire 的 hook 投递)靠它区分
|
|
191
|
+
* 「取消」和「发失败」——两者的收尾完全不同:取消要把这一批还没发出去的
|
|
192
|
+
* outbox 行撤掉,发失败则要把它们留着等补收。
|
|
193
|
+
*/
|
|
194
|
+
export const TASK_CANCELLED_CODE: "TASK_CANCELLED";
|
|
195
|
+
export type ErrorCause = {
|
|
196
|
+
/**
|
|
197
|
+
* - 在哪一段炸的
|
|
198
|
+
*/
|
|
199
|
+
stage: "config" | "request" | "tick";
|
|
200
|
+
/**
|
|
201
|
+
* - 错误类型(`error.name`,认不出来时是 'Error')
|
|
202
|
+
*/
|
|
203
|
+
name: string;
|
|
204
|
+
/**
|
|
205
|
+
* - 脱敏后的错误消息。降级 500(配置都没建起来那条
|
|
206
|
+
* 路)回给跨域调用方时不带这个字段——那条路的 CORS 头是回显来访 Origin 的,任
|
|
207
|
+
* 意第三方页面都能读,而构建期异常原文里常有 binding 名、内网域名、环境变量
|
|
208
|
+
* 名。同源 / 无 Origin 的请求照旧带全文,`wrangler tail` 里也一直有。
|
|
209
|
+
*/
|
|
210
|
+
message?: string;
|
|
211
|
+
/**
|
|
212
|
+
* - 错误自带的 `code` 字符串(有才带)
|
|
213
|
+
*/
|
|
214
|
+
code?: string;
|
|
215
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/** 适配器支不支持用户级凭据存储。 */
|
|
2
|
+
export function supportsLlmCredentialsStore(db: any): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* 一个字符串是不是合法 cred_id:非空、≤128 字符、不含控制字符。
|
|
5
|
+
*
|
|
6
|
+
* @param {unknown} credId
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function isValidCredId(credId: unknown): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 一份凭据 value 的形状:{ apiUrl, apiKey, primaryModel } 三字段全必填。
|
|
12
|
+
* 校验口径对齐 update-message 的凭据刷新:只查 truthy(+ 长度上限),不做
|
|
13
|
+
* 格式校验。
|
|
14
|
+
*
|
|
15
|
+
* @param {unknown} value
|
|
16
|
+
* @returns {string|null} 错误描述,合法时 null
|
|
17
|
+
*/
|
|
18
|
+
export function validateCredValue(value: unknown): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* 任务 payload 里 credRefs 字段的形状校验(schedule-message 与 update-message
|
|
21
|
+
* 共用一份口径)。
|
|
22
|
+
*
|
|
23
|
+
* @param {unknown} credRefs
|
|
24
|
+
* @returns {string|null} 错误描述,合法时 null
|
|
25
|
+
*/
|
|
26
|
+
export function validateCredRefs(credRefs: unknown): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* payload 带没带可用的 chat 凭据引用(`credRefs.chat`)。fire 侧的
|
|
29
|
+
* taskNeedsLlm 和校验共用这一个判据。
|
|
30
|
+
*
|
|
31
|
+
* @param {Object|null|undefined} payload
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
export function hasChatCredRef(payload: any | null | undefined): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* payload 带没带非空的 credRefs 映射(不管里面是什么 purpose)。自排继承要在
|
|
37
|
+
* 「有 chat 引用」和「只有非 chat 引用」之间分支,这是后者用的那半个判据。
|
|
38
|
+
*
|
|
39
|
+
* @param {Object|null|undefined} payload
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function hasCredRefs(payload: any | null | undefined): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* 批量写入(覆盖)这个用户的凭据。value 在这里加密。
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} args
|
|
47
|
+
* @param {import('../adapters/interface.js').DbAdapter} args.db
|
|
48
|
+
* @param {string} args.userId
|
|
49
|
+
* @param {string} args.userKey
|
|
50
|
+
* @param {Array<{ credId: string, value: Object }>} args.credentials
|
|
51
|
+
* @returns {Promise<{ upserted: number }>}
|
|
52
|
+
*/
|
|
53
|
+
export function saveLlmCredentials({ db, userId, userKey, credentials }: {
|
|
54
|
+
db: import("../adapters/interface.js").DbAdapter;
|
|
55
|
+
userId: string;
|
|
56
|
+
userKey: string;
|
|
57
|
+
credentials: Array<{
|
|
58
|
+
credId: string;
|
|
59
|
+
value: any;
|
|
60
|
+
}>;
|
|
61
|
+
}): Promise<{
|
|
62
|
+
upserted: number;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* 排程 / 更新时的存在性检查:credRefs 里引用的 credId 有哪些还不在表里。
|
|
66
|
+
* 空缺不是这里的错误——回给调用方点名,由它组 4xx。
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} args
|
|
69
|
+
* @param {import('../adapters/interface.js').DbAdapter} args.db
|
|
70
|
+
* @param {string} args.userId
|
|
71
|
+
* @param {Object} args.credRefs
|
|
72
|
+
* @returns {Promise<string[]>} 缺失的 credId(去重)
|
|
73
|
+
*/
|
|
74
|
+
export function findMissingCredIds({ db, userId, credRefs }: {
|
|
75
|
+
db: import("../adapters/interface.js").DbAdapter;
|
|
76
|
+
userId: string;
|
|
77
|
+
credRefs: any;
|
|
78
|
+
}): Promise<string[]>;
|
|
79
|
+
/**
|
|
80
|
+
* 按 cred_id 解出一份明文凭据。每次调用返回**新对象**;没有这行 / 解不开
|
|
81
|
+
* (换过 masterKey)→ null。
|
|
82
|
+
*
|
|
83
|
+
* 结果只该被「拿到就用」:合进发给 LLM 的请求对象、或在 hook 里当场发请求。
|
|
84
|
+
* 不要写回 payload / ctx / metadata——那些对象会流向 hook 和 push,凭据跟着
|
|
85
|
+
* 走就把 CREDENTIAL_PAYLOAD_KEYS 那道防线绕空了。
|
|
86
|
+
*
|
|
87
|
+
* @param {Object} args
|
|
88
|
+
* @param {import('../adapters/interface.js').DbAdapter} args.db
|
|
89
|
+
* @param {string} args.userId
|
|
90
|
+
* @param {string} args.userKey
|
|
91
|
+
* @param {string} args.credId
|
|
92
|
+
* @returns {Promise<{ apiUrl: string, apiKey: string, primaryModel: string }|null>}
|
|
93
|
+
*/
|
|
94
|
+
export function resolveLlmCredential({ db, userId, userKey, credId }: {
|
|
95
|
+
db: import("../adapters/interface.js").DbAdapter;
|
|
96
|
+
userId: string;
|
|
97
|
+
userKey: string;
|
|
98
|
+
credId: string;
|
|
99
|
+
}): Promise<{
|
|
100
|
+
apiUrl: string;
|
|
101
|
+
apiKey: string;
|
|
102
|
+
primaryModel: string;
|
|
103
|
+
} | null>;
|
|
104
|
+
/**
|
|
105
|
+
* fire 时的 chat 凭据解析(message-processor 的两条老路径和 agentic 循环共用)。
|
|
106
|
+
*
|
|
107
|
+
* 解析顺序:
|
|
108
|
+
* 1. payload 没带 `credRefs.chat` → 返回 null,调用方按内联三件套的老行为走
|
|
109
|
+
* (存量任务的零开销路径,不多打一次库)。
|
|
110
|
+
* 2. 带了 → 查表取值,查到就用表里那份(表是用户最近一次登记的,比冻结在
|
|
111
|
+
* 行里的新)。
|
|
112
|
+
* 3. 查不到行(被删 / 换过 masterKey / 适配器不支持)→ 退回内联三件套兜底
|
|
113
|
+
* (update-message 给存量任务补 credRefs 时内联还留着,正是给这里用的)。
|
|
114
|
+
* 4. 都没有 → 抛 `code: 'CREDENTIAL_MISSING'`,走任务的常规失败/重试——用户
|
|
115
|
+
* 补传凭据后下一轮自愈。
|
|
116
|
+
*
|
|
117
|
+
* 返回的对象只许合进**发给 LLM 的请求对象**,见 resolveLlmCredential 的红线。
|
|
118
|
+
*
|
|
119
|
+
* @param {Object} args
|
|
120
|
+
* @param {import('../adapters/interface.js').DbAdapter} args.db
|
|
121
|
+
* @param {string} args.userId
|
|
122
|
+
* @param {string} args.userKey
|
|
123
|
+
* @param {Object} args.decryptedPayload
|
|
124
|
+
* @returns {Promise<{ apiUrl: string, apiKey: string, primaryModel: string }|null>}
|
|
125
|
+
*/
|
|
126
|
+
export function resolveFireCredentials({ db, userId, userKey, decryptedPayload }: {
|
|
127
|
+
db: import("../adapters/interface.js").DbAdapter;
|
|
128
|
+
userId: string;
|
|
129
|
+
userKey: string;
|
|
130
|
+
decryptedPayload: any;
|
|
131
|
+
}): Promise<{
|
|
132
|
+
apiUrl: string;
|
|
133
|
+
apiKey: string;
|
|
134
|
+
primaryModel: string;
|
|
135
|
+
} | null>;
|
|
136
|
+
/** cred_id 的长度上限。 */
|
|
137
|
+
export const CRED_ID_MAX_LENGTH: 128;
|
|
138
|
+
/** value 单字段(apiUrl / apiKey / primaryModel)的长度上限。 */
|
|
139
|
+
export const CRED_VALUE_FIELD_MAX_LENGTH: 2048;
|
|
140
|
+
/** PUT 单批最多几条。 */
|
|
141
|
+
export const CRED_PUT_BATCH_MAX: 100;
|
|
142
|
+
/** 单用户最多存几行凭据。 */
|
|
143
|
+
export const CRED_ROWS_PER_USER_MAX: 500;
|
|
144
|
+
/** credRefs 最多几个 purpose 条目。 */
|
|
145
|
+
export const CRED_REFS_MAX_ENTRIES: 16;
|
|
146
|
+
/** credRefs 的 purpose 键长度上限。 */
|
|
147
|
+
export const CRED_REFS_KEY_MAX_LENGTH: 64;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} ProcessorContext
|
|
3
|
+
* @property {Object} webpush - The web-push module instance (already VAPID-configured).
|
|
4
|
+
* @property {Object} vapid - { email, publicKey, privateKey }
|
|
5
|
+
* @property {import('../adapters/interface.js').DbAdapter} db
|
|
6
|
+
* @property {{ maxChunkBytes?: number, maxChunks?: number, maxTotalBytes?: number, ttlMs?: number }} [multipart]
|
|
7
|
+
* 分片传输的限额,宿主传给 `installReiSW` 的那一份原样传过来即可(见
|
|
8
|
+
* resolveMultipartOptions)。不传 = 两边都用默认值。
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Process a single database task row: decrypt → generate content → push.
|
|
12
|
+
*
|
|
13
|
+
* @param {import('../adapters/interface.js').TaskRow} task
|
|
14
|
+
* @param {ProcessorContext} ctx
|
|
15
|
+
* @param {string} [providedMasterKey]
|
|
16
|
+
* @param {{ userKey: string, payload: Object } | null} [predecrypted] - 调用方
|
|
17
|
+
* (run-tick 的预扫描)已经解好的 payload;传了就不再解第二遍。
|
|
18
|
+
* @returns {Promise<{ success: boolean, messagesSent: number, error?: string, errorCode?: string|null, pushStatusCode?: number|null, permanent?: boolean }>}
|
|
19
|
+
* 失败时 `pushStatusCode` 是推送服务回的 HTTP 状态码(不是推送阶段炸的 → null)。
|
|
20
|
+
*/
|
|
21
|
+
export function processSingleMessage(task: import("../adapters/interface.js").TaskRow, ctx: ProcessorContext, providedMasterKey?: string, predecrypted?: {
|
|
22
|
+
userKey: string;
|
|
23
|
+
payload: any;
|
|
24
|
+
} | null): Promise<{
|
|
25
|
+
success: boolean;
|
|
26
|
+
messagesSent: number;
|
|
27
|
+
error?: string;
|
|
28
|
+
errorCode?: string | null;
|
|
29
|
+
pushStatusCode?: number | null;
|
|
30
|
+
permanent?: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Process a single message identified by UUID (used for instant type).
|
|
34
|
+
*
|
|
35
|
+
* @param {string} uuid
|
|
36
|
+
* @param {ProcessorContext} ctx
|
|
37
|
+
* @param {number} [maxRetries=2]
|
|
38
|
+
* @param {string} [userId]
|
|
39
|
+
* @param {string} [providedMasterKey]
|
|
40
|
+
* @returns {Promise<{ success: boolean, messagesSent?: number, retriesUsed?: number, reasoningError?: string, error?: Object }>}
|
|
41
|
+
* `reasoningError` 只在正文都发出去了、思考过程那一条没发成时出现(思考过程是
|
|
42
|
+
* 附赠内容,它发不出去不算这条消息失败)。调用方拿它提示用户这次没有思考过程,
|
|
43
|
+
* 不带这个字段就是整轮都送到了。
|
|
44
|
+
*/
|
|
45
|
+
export function processMessagesByUuid(uuid: string, ctx: ProcessorContext, maxRetries?: number, userId?: string, providedMasterKey?: string): Promise<{
|
|
46
|
+
success: boolean;
|
|
47
|
+
messagesSent?: number;
|
|
48
|
+
retriesUsed?: number;
|
|
49
|
+
reasoningError?: string;
|
|
50
|
+
error?: any;
|
|
51
|
+
}>;
|
|
52
|
+
export { normalizeAiApiUrl } from "./llm.js";
|
|
53
|
+
export type ProcessorContext = {
|
|
54
|
+
/**
|
|
55
|
+
* - The web-push module instance (already VAPID-configured).
|
|
56
|
+
*/
|
|
57
|
+
webpush: any;
|
|
58
|
+
/**
|
|
59
|
+
* - { email, publicKey, privateKey }
|
|
60
|
+
*/
|
|
61
|
+
vapid: any;
|
|
62
|
+
db: import("../adapters/interface.js").DbAdapter;
|
|
63
|
+
/**
|
|
64
|
+
* 分片传输的限额,宿主传给 `installReiSW` 的那一份原样传过来即可(见
|
|
65
|
+
* resolveMultipartOptions)。不传 = 两边都用默认值。
|
|
66
|
+
*/
|
|
67
|
+
multipart?: {
|
|
68
|
+
maxChunkBytes?: number;
|
|
69
|
+
maxChunks?: number;
|
|
70
|
+
maxTotalBytes?: number;
|
|
71
|
+
ttlMs?: number;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/** 适配器支持 outbox 吗(写入侧要用的两个方法都在才算)。 */
|
|
2
|
+
export function supportsOutbox(db: any): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* 发送前把这一批 push 落进 outbox。push 对象须已定稿(messageId / sessionId /
|
|
5
|
+
* messageIndex / totalMessages / 任务身份都已补齐)——落进去的密文就是客户端
|
|
6
|
+
* 补收时拿到的那一份。
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} args
|
|
9
|
+
* @param {Object} args.db
|
|
10
|
+
* @param {string} args.userId
|
|
11
|
+
* @param {CryptoKey|string} args.userKey - per-user 存储密钥
|
|
12
|
+
* @param {Object[]} args.pushes - 定稿后的 push 对象
|
|
13
|
+
* @returns {Promise<boolean>} 是否真的落了行(适配器不支持 / 落行失败 → false)
|
|
14
|
+
*/
|
|
15
|
+
export function appendPushesToOutbox({ db, userId, userKey, pushes }: {
|
|
16
|
+
db: any;
|
|
17
|
+
userId: string;
|
|
18
|
+
userKey: CryptoKey | string;
|
|
19
|
+
pushes: any[];
|
|
20
|
+
}): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* 把 push 对象转成 message_outbox 的行(payload 加密)。
|
|
23
|
+
*
|
|
24
|
+
* 抽出来是给 `ctx.emitResult()` 用的:它落的行要跟推送链路落的完全同构——
|
|
25
|
+
* 列一样、加密方式一样、身份字段的取法一样,客户端补收时才不用区分是谁写的。
|
|
26
|
+
*
|
|
27
|
+
* @param {Object[]} pushes - 定稿后的 push 对象
|
|
28
|
+
* @param {CryptoKey|string} userKey
|
|
29
|
+
* @param {number} createdAt - epoch 毫秒
|
|
30
|
+
* @returns {Promise<Object[]>}
|
|
31
|
+
*/
|
|
32
|
+
export function toOutboxRows(pushes: any[], userKey: CryptoKey | string, createdAt: number): Promise<any[]>;
|
|
33
|
+
/**
|
|
34
|
+
* 把发出去的那部分标成 delivered(发送半途失败时只标已发出的段)。
|
|
35
|
+
*
|
|
36
|
+
* @param {Object} args
|
|
37
|
+
* @param {Object} args.db
|
|
38
|
+
* @param {string} args.userId
|
|
39
|
+
* @param {string[]} args.messageIds
|
|
40
|
+
*/
|
|
41
|
+
export function markPushesDelivered({ db, userId, messageIds }: {
|
|
42
|
+
db: any;
|
|
43
|
+
userId: string;
|
|
44
|
+
messageIds: string[];
|
|
45
|
+
}): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* 把这一批 push 里「没发出去的那些」从 outbox 撤掉。
|
|
48
|
+
*
|
|
49
|
+
* 取消撞上投递时用:整批 push 在开发之前就落进了 outbox,取消只拦住了 Web
|
|
50
|
+
* Push 这一路,剩下的行不撤掉,客户端下一次 GET /outbox 会照样把它们拉回去
|
|
51
|
+
* ——用户看到的就是「取消接口回了成功,消息还是来了」。
|
|
52
|
+
*
|
|
53
|
+
* 「哪些算没发出去」这条判据收在这里:投递侧只要把整批 push 和已发出的
|
|
54
|
+
* messageId 交过来就行。
|
|
55
|
+
*
|
|
56
|
+
* @param {Object} args
|
|
57
|
+
* @param {Object} args.db
|
|
58
|
+
* @param {string} args.userId
|
|
59
|
+
* @param {Array<{ messageId: string }>} args.pushes - 落进 outbox 的整批 push
|
|
60
|
+
* @param {string[]} args.sentIds - 已经发出去的 messageId
|
|
61
|
+
*/
|
|
62
|
+
export function discardUndeliveredPushes({ db, userId, pushes, sentIds }: {
|
|
63
|
+
db: any;
|
|
64
|
+
userId: string;
|
|
65
|
+
pushes: Array<{
|
|
66
|
+
messageId: string;
|
|
67
|
+
}>;
|
|
68
|
+
sentIds: string[];
|
|
69
|
+
}): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* 把某条任务名下「还没发出去的」行从 outbox 撤掉。
|
|
72
|
+
*
|
|
73
|
+
* 用在取消 / 顶替只碰了任务行的那两条路上(`DELETE /message` 与
|
|
74
|
+
* `supersedesUuid`):任务此前投递到一半失败过的话,没发出去的那几段还躺在
|
|
75
|
+
* outbox 里等重试,任务行删掉它们也不会跟着走。不撤的话客户端下一次
|
|
76
|
+
* `GET /outbox` 照样把它们补收回去——用户看到的就是「取消接口回了成功,消息还
|
|
77
|
+
* 是来了」。
|
|
78
|
+
*
|
|
79
|
+
* 判据与 discardUndeliveredPushes 一致:只撤 delivered_at 为 null 的行。已经推
|
|
80
|
+
* 给设备的那几条撤不回来,行留着让客户端照常 ack——取消的意思是「别再发后面
|
|
81
|
+
* 的」,不是「把已经收到的从收件箱里抹掉」。
|
|
82
|
+
*
|
|
83
|
+
* 同样是 best-effort:适配器缺读/删任一侧就静默跳过,出错只记日志。取消 / 顶
|
|
84
|
+
* 替本身已经生效了,不该因为账本没清干净被翻成失败。
|
|
85
|
+
*
|
|
86
|
+
* @param {Object} args
|
|
87
|
+
* @param {Object} args.db
|
|
88
|
+
* @param {string} args.userId
|
|
89
|
+
* @param {string} args.taskUuid - 被取消 / 被顶替的任务 uuid
|
|
90
|
+
*/
|
|
91
|
+
export function discardUndeliveredPushesForTask({ db, userId, taskUuid }: {
|
|
92
|
+
db: any;
|
|
93
|
+
userId: string;
|
|
94
|
+
taskUuid: string;
|
|
95
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** 适配器支不支持用户级订阅存储。 */
|
|
2
|
+
export function supportsPushSubscriptionStore(db: any): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* 一个对象长得像不像 Web Push 订阅。只看投递必需的部分:没有 endpoint 就
|
|
5
|
+
* 谈不上往哪推。keys 缺失留给推送服务去拒,这里不替它判。
|
|
6
|
+
*
|
|
7
|
+
* @param {unknown} subscription
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
export function isPushSubscriptionShape(subscription: unknown): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 写入(覆盖)这个用户的订阅。
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} args
|
|
15
|
+
* @param {import('../adapters/interface.js').DbAdapter} args.db
|
|
16
|
+
* @param {string} args.userId
|
|
17
|
+
* @param {string} args.userKey
|
|
18
|
+
* @param {Object} args.subscription - 明文订阅对象
|
|
19
|
+
* @param {number} [args.updatedAt] - epoch 毫秒,默认取当前时刻
|
|
20
|
+
* @returns {Promise<{ updatedAt: number }>}
|
|
21
|
+
*/
|
|
22
|
+
export function savePushSubscription({ db, userId, userKey, subscription, updatedAt }: {
|
|
23
|
+
db: import("../adapters/interface.js").DbAdapter;
|
|
24
|
+
userId: string;
|
|
25
|
+
userKey: string;
|
|
26
|
+
subscription: any;
|
|
27
|
+
updatedAt?: number;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
updatedAt: number;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* 把一行 push_subscriptions 解成明文订阅。行不在、或订阅列是空的 → null;
|
|
33
|
+
* 密文解不开(换过 masterKey 之类)、解出来不是 JSON → 抛。
|
|
34
|
+
*
|
|
35
|
+
* 单独拎出来是为了让调用方分得开两类失败:`db.getPushSubscription()` 抛出来
|
|
36
|
+
* 的是基础设施问题(表没建、读超时),换个时候重试有救;这里抛出来的是这一
|
|
37
|
+
* 行的密文废了,重试多少次都一样。两者压在同一个 try 里就只能一起处理,读不
|
|
38
|
+
* 到库会被当成「这个用户没登记过」。
|
|
39
|
+
*
|
|
40
|
+
* @param {{ subscription?: unknown, updated_at?: unknown }|null|undefined} row - `getPushSubscription()` 读回的行
|
|
41
|
+
* @param {string} userKey
|
|
42
|
+
* @returns {Promise<{ subscription: Object, updatedAt: number|null }|null>}
|
|
43
|
+
*/
|
|
44
|
+
export function decodePushSubscriptionRow(row: {
|
|
45
|
+
subscription?: unknown;
|
|
46
|
+
updated_at?: unknown;
|
|
47
|
+
} | null | undefined, userKey: string): Promise<{
|
|
48
|
+
subscription: any;
|
|
49
|
+
updatedAt: number | null;
|
|
50
|
+
} | null>;
|
|
51
|
+
/**
|
|
52
|
+
* 读回这个用户的订阅(解密后的明文对象)。没有登记过 → null。
|
|
53
|
+
*
|
|
54
|
+
* 读库失败和解密失败都原样抛出去:投递链路上这两种都得让任务失败,分不分得
|
|
55
|
+
* 开无所谓;要分开处理的调用方(GET /push-subscription)自己走上面两步。
|
|
56
|
+
*
|
|
57
|
+
* @param {Object} args
|
|
58
|
+
* @param {import('../adapters/interface.js').DbAdapter} args.db
|
|
59
|
+
* @param {string} args.userId
|
|
60
|
+
* @param {string} args.userKey
|
|
61
|
+
* @returns {Promise<{ subscription: Object, updatedAt: number|null }|null>}
|
|
62
|
+
*/
|
|
63
|
+
export function loadPushSubscription({ db, userId, userKey }: {
|
|
64
|
+
db: import("../adapters/interface.js").DbAdapter;
|
|
65
|
+
userId: string;
|
|
66
|
+
userKey: string;
|
|
67
|
+
}): Promise<{
|
|
68
|
+
subscription: any;
|
|
69
|
+
updatedAt: number | null;
|
|
70
|
+
} | null>;
|
|
71
|
+
/**
|
|
72
|
+
* 投递前取订阅。取不到就抛——静默不发会让任务「成功」地什么都没做,用户
|
|
73
|
+
* 只看到消息凭空消失。抛出去走既有的重试 / 标记逻辑,原因也会记进 payload
|
|
74
|
+
* 的 lastError,`GET /messages` 上看得见。抛出的错误带稳定的 `code` 属性
|
|
75
|
+
* ('PUSH_SUBSCRIPTION_MISSING' / 'PUSH_SUBSCRIPTION_STORE_UNSUPPORTED'),
|
|
76
|
+
* 按类别分支请用它,别匹配 message 文案。
|
|
77
|
+
*
|
|
78
|
+
* `legacyFallback`:用户级存储里没有订阅时的兜底(升级前创建的任务把订阅
|
|
79
|
+
* 冻结在自己的 payload 里,这份订阅仍然有效)。存储里有订阅时永远用存储的
|
|
80
|
+
* 那份——它是用户最近一次登记的。
|
|
81
|
+
*
|
|
82
|
+
* @param {Object} args
|
|
83
|
+
* @param {import('../adapters/interface.js').DbAdapter} args.db
|
|
84
|
+
* @param {string} args.userId
|
|
85
|
+
* @param {string} args.userKey
|
|
86
|
+
* @param {unknown} [args.legacyFallback] - 旧任务 payload 里内嵌的订阅(可选)
|
|
87
|
+
* @returns {Promise<Object>} 明文订阅对象
|
|
88
|
+
*/
|
|
89
|
+
export function resolvePushSubscription({ db, userId, userKey, legacyFallback }: {
|
|
90
|
+
db: import("../adapters/interface.js").DbAdapter;
|
|
91
|
+
userId: string;
|
|
92
|
+
userKey: string;
|
|
93
|
+
legacyFallback?: unknown;
|
|
94
|
+
}): Promise<any>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 这个字符串能不能当 IANA 时区 id 用。建任务 / 改任务时校验,运行时不再猜。
|
|
3
|
+
*
|
|
4
|
+
* @param {unknown} tzId
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
export function isValidTimeZoneId(tzId: unknown): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* 从名义触发时刻往后推 `periods` 个周期。
|
|
10
|
+
*
|
|
11
|
+
* @param {number} occurrenceMs - 名义触发时刻(epoch 毫秒)
|
|
12
|
+
* @param {'daily'|'weekly'} recurrenceType
|
|
13
|
+
* @param {number} periods - 推几个周期(≥ 1)
|
|
14
|
+
* @param {string|null} [tzId] - IANA 时区 id;缺省按 UTC 推进
|
|
15
|
+
* @returns {number} 推进之后的时刻(epoch 毫秒)
|
|
16
|
+
*/
|
|
17
|
+
export function advanceOccurrence(occurrenceMs: number, recurrenceType: "daily" | "weekly", periods: number, tzId?: string | null): number;
|
|
18
|
+
/**
|
|
19
|
+
* 从名义触发时刻往后推,找到第一个在 `nowMs` 之后的 occurrence,并顺带
|
|
20
|
+
* 报出中间跳过了哪些。
|
|
21
|
+
*
|
|
22
|
+
* 推进基准永远是名义时刻本身(不是「现在」),所以停摆多久都不会漂到别的
|
|
23
|
+
* 钟点上。
|
|
24
|
+
*
|
|
25
|
+
* @param {number} occurrenceMs
|
|
26
|
+
* @param {'daily'|'weekly'} recurrenceType
|
|
27
|
+
* @param {number} nowMs
|
|
28
|
+
* @param {string|null} [tzId]
|
|
29
|
+
* @returns {{ nextMs: number, skippedCount: number, skippedOccurrences: number[], skippedTruncated: boolean }}
|
|
30
|
+
* `skippedCount` 含传进来的这一次(它自己就是没发出去的那一次)。
|
|
31
|
+
* `skippedOccurrences` 最多列 {@link MAX_LISTED_SKIPPED_OCCURRENCES} 个,
|
|
32
|
+
* 超了只给首末两个并把 `skippedTruncated` 置 true。
|
|
33
|
+
*/
|
|
34
|
+
export function planNextOccurrence(occurrenceMs: number, recurrenceType: "daily" | "weekly", nowMs: number, tzId?: string | null): {
|
|
35
|
+
nextMs: number;
|
|
36
|
+
skippedCount: number;
|
|
37
|
+
skippedOccurrences: number[];
|
|
38
|
+
skippedTruncated: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* 下一次触发时刻的 ISO 字符串(写库用)。
|
|
42
|
+
*
|
|
43
|
+
* @param {number} occurrenceMs
|
|
44
|
+
* @param {'daily'|'weekly'} recurrenceType
|
|
45
|
+
* @param {number} nowMs
|
|
46
|
+
* @param {string|null} [tzId]
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
export function nextFutureOccurrence(occurrenceMs: number, recurrenceType: "daily" | "weekly", nowMs: number, tzId?: string | null): string;
|
|
50
|
+
/** 快进时最多逐条列出多少个被跳过的名义时刻,超了只给首末两个。 */
|
|
51
|
+
export const MAX_LISTED_SKIPPED_OCCURRENCES: 32;
|