@rei-standard/amsg-server 2.6.0-next.25 → 2.6.0-next.27

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.
@@ -216,8 +216,11 @@ export class D1Adapter {
216
216
  * @param {number} [now] - 服务端当前时刻(epoch 毫秒),判定「这行来自未来」用的
217
217
  * 就是它。调用方(lib/client-state-store.js)传下来,测试可以钉住一个假时钟;
218
218
  * 自定义调用方不传时退回本机时钟。
219
- * @returns {Promise<{ upserted: number, skipped: number, outcomes: boolean[] }>}
219
+ * @returns {Promise<{ upserted: number, skipped: number, outcomes: boolean[], cleanupOutcomes?: Array<boolean|null> }>}
220
220
  * `outcomes[i]` 对应 entries[i] 是否真的写入(changes > 0)。
221
+ * `cleanupOutcomes[i]` 对应 cleanups[i](传了 cleanups 才有):精确 key 形态
222
+ * 回 `true` = 这个 key 的行已经不在(删掉了,或本来就没有)、`false` = 行还在
223
+ * (库里那行更新,删除被条件写拦下);前缀形态不探测,回 `null`。
221
224
  */
222
225
  upsertClientState(userId: string, entries: Array<{
223
226
  namespace: string;
@@ -233,6 +236,7 @@ export class D1Adapter {
233
236
  upserted: number;
234
237
  skipped: number;
235
238
  outcomes: boolean[];
239
+ cleanupOutcomes?: Array<boolean | null>;
236
240
  }>;
237
241
  /**
238
242
  * All entries of one namespace (values still encrypted).
@@ -147,6 +147,9 @@ export type DbAdapter = {
147
147
  * 写入与删除都对这种行放行,否则那一行会一直卡到真实时间追上去为止。忽略这个参数的自定义
148
148
  * adapter 功能不变,只是没有这道解锁。
149
149
  * `outcomes` 逐条报告 entries[i] 是否真的写入(缺席时调用方按物理行计数兜底)。
150
+ * `cleanupOutcomes`(可选)逐条报告 cleanups[i] 的结局:精确 key 形态回 `true`(这个 key 的行已不在——
151
+ * 删掉了或本来就没有)/ `false`(行还在,库里那行更新、删除被条件写拦下);前缀形态回 `null`。
152
+ * 调用方靠它把被拦下的删除计入 skippedEntries;缺席时删除一律按「已删」计。
150
153
  */
151
154
  upsertClientState?: (userId: string, entries: Array<{
152
155
  namespace: string;
@@ -162,6 +165,7 @@ export type DbAdapter = {
162
165
  upserted: number;
163
166
  skipped: number;
164
167
  outcomes?: boolean[];
168
+ cleanupOutcomes?: Array<boolean | null>;
165
169
  }>;
166
170
  /**
167
171
  * (optional; single-user/D1 only) All entries of one namespace; values still encrypted.
@@ -31,10 +31,28 @@ export const SQLITE_INDEXES: {
31
31
  critical: boolean;
32
32
  }[];
33
33
  export const CLIENT_STATE_TABLE_SQL: "\n CREATE TABLE IF NOT EXISTS client_state (\n user_id TEXT NOT NULL,\n namespace TEXT NOT NULL,\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n PRIMARY KEY (user_id, namespace, key)\n )\n";
34
+ export const CLIENT_STATE_INDEXES: {
35
+ name: string;
36
+ sql: string;
37
+ description: string;
38
+ critical: boolean;
39
+ }[];
34
40
  export const PUSH_SUBSCRIPTION_TABLE_SQL: "\n CREATE TABLE IF NOT EXISTS push_subscriptions (\n user_id TEXT PRIMARY KEY,\n subscription TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n )\n";
35
41
  export const LLM_CREDENTIALS_TABLE_SQL: "\n CREATE TABLE IF NOT EXISTS llm_credentials (\n user_id TEXT NOT NULL,\n cred_id TEXT NOT NULL,\n encrypted_value TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n PRIMARY KEY (user_id, cred_id)\n )\n";
36
42
  export const MESSAGE_OUTBOX_TABLE_SQL: "\n CREATE TABLE IF NOT EXISTS message_outbox (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n user_id TEXT NOT NULL,\n message_id TEXT NOT NULL,\n task_uuid TEXT,\n session_id TEXT,\n message_index INTEGER,\n total_messages INTEGER,\n payload TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n delivered_at INTEGER,\n acked_at INTEGER,\n UNIQUE (user_id, message_id)\n )\n";
37
- export const MESSAGE_OUTBOX_INDEX_SQL: "\n CREATE INDEX IF NOT EXISTS idx_outbox_unacked\n ON message_outbox (user_id, id)\n WHERE acked_at IS NULL\n";
43
+ export const MESSAGE_OUTBOX_INDEXES: {
44
+ name: string;
45
+ sql: string;
46
+ description: string;
47
+ critical: boolean;
48
+ }[];
49
+ /** 三张表的索引合在一起,initSchema 按这份顺序逐条建。 */
50
+ export const SQLITE_ALL_INDEXES: {
51
+ name: string;
52
+ sql: string;
53
+ description: string;
54
+ critical: boolean;
55
+ }[];
38
56
  /**
39
57
  * 这一版代码跑起来需要的表 / 列 / 索引。
40
58
  *
@@ -1060,25 +1060,27 @@ function planClientStateCleanup(ttl, now) {
1060
1060
  }
1061
1061
  async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
1062
1062
  const nowFn = typeof now === "function" ? now : Date.now;
1063
+ const at = nowFn();
1063
1064
  const physicalRows = [];
1064
1065
  const cleanups = [];
1065
1066
  const rootRowIndexes = [];
1066
1067
  const rootRowEntries = [];
1067
- let deleted = 0;
1068
+ const deletions = [];
1068
1069
  for (const entry of entries) {
1069
- const guardAt = Number.isInteger(entry.version) && entry.version > 0 ? entry.version : entry.updatedAt;
1070
+ const rawGuardAt = Number.isInteger(entry.version) && entry.version > 0 ? entry.version : entry.updatedAt;
1071
+ const guardAt = Math.min(rawGuardAt, at);
1070
1072
  cleanups.push({
1071
1073
  namespace: chunkNamespaceFor(entry.namespace),
1072
1074
  keyPrefix: chunkKeyPrefixFor(entry.key),
1073
1075
  updatedAt: guardAt
1074
1076
  });
1075
1077
  if (entry.value === null) {
1078
+ deletions.push({ cleanupIndex: cleanups.length, entry });
1076
1079
  cleanups.push({
1077
1080
  namespace: entry.namespace,
1078
1081
  key: entry.key,
1079
1082
  updatedAt: guardAt
1080
1083
  });
1081
- deleted++;
1082
1084
  continue;
1083
1085
  }
1084
1086
  rootRowIndexes.push(physicalRows.length);
@@ -1112,9 +1114,10 @@ async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
1112
1114
  if (physicalRows.length === 0 && cleanups.length === 0) {
1113
1115
  return { upserted: 0, skipped: 0, deleted: 0, skippedEntries: [] };
1114
1116
  }
1115
- const result = await db.upsertClientState(userId, physicalRows, cleanups, nowFn());
1117
+ const result = await db.upsertClientState(userId, physicalRows, cleanups, at);
1116
1118
  let upserted = 0;
1117
1119
  let skipped = 0;
1120
+ let deleted = 0;
1118
1121
  const skippedEntries = [];
1119
1122
  if (Array.isArray(result.outcomes) && result.outcomes.length === physicalRows.length) {
1120
1123
  for (let i = 0; i < rootRowIndexes.length; i++) {
@@ -1130,6 +1133,15 @@ async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
1130
1133
  upserted = result.upserted;
1131
1134
  skipped = result.skipped;
1132
1135
  }
1136
+ const cleanupOutcomes = Array.isArray(result.cleanupOutcomes) && result.cleanupOutcomes.length === cleanups.length ? result.cleanupOutcomes : null;
1137
+ for (const { cleanupIndex, entry } of deletions) {
1138
+ if (cleanupOutcomes && cleanupOutcomes[cleanupIndex] === false) {
1139
+ skipped++;
1140
+ skippedEntries.push({ namespace: entry.namespace, key: entry.key });
1141
+ } else {
1142
+ deleted++;
1143
+ }
1144
+ }
1133
1145
  return { upserted, skipped, deleted, skippedEntries };
1134
1146
  }
1135
1147
 
@@ -4126,6 +4138,19 @@ var CLIENT_STATE_TABLE_SQL = `
4126
4138
  PRIMARY KEY (user_id, namespace, key)
4127
4139
  )
4128
4140
  `;
4141
+ var CLIENT_STATE_INDEXES = [
4142
+ {
4143
+ name: "idx_client_state_cleanup",
4144
+ // 服务 cleanupClientState 的
4145
+ // DELETE FROM client_state WHERE namespace = ? AND updated_at < ?
4146
+ // 主键 (user_id, namespace, key) 最左列是 user_id,按 namespace 起头的条件
4147
+ // 吃不到它。
4148
+ sql: `CREATE INDEX IF NOT EXISTS idx_client_state_cleanup
4149
+ ON client_state (namespace, updated_at)`,
4150
+ description: "TTL cleanup index (cleanupClientState by namespace + updated_at)",
4151
+ critical: false
4152
+ }
4153
+ ];
4129
4154
  var PUSH_SUBSCRIPTION_TABLE_SQL = `
4130
4155
  CREATE TABLE IF NOT EXISTS push_subscriptions (
4131
4156
  user_id TEXT PRIMARY KEY,
@@ -4159,11 +4184,57 @@ var MESSAGE_OUTBOX_TABLE_SQL = `
4159
4184
  UNIQUE (user_id, message_id)
4160
4185
  )
4161
4186
  `;
4162
- var MESSAGE_OUTBOX_INDEX_SQL = `
4163
- CREATE INDEX IF NOT EXISTS idx_outbox_unacked
4164
- ON message_outbox (user_id, id)
4165
- WHERE acked_at IS NULL
4166
- `;
4187
+ var MESSAGE_OUTBOX_INDEXES = [
4188
+ {
4189
+ name: "idx_outbox_unacked",
4190
+ // 服务 listUnackedOutbox 的
4191
+ // SELECT … WHERE user_id = ? AND acked_at IS NULL AND id > ? ORDER BY id
4192
+ sql: `CREATE INDEX IF NOT EXISTS idx_outbox_unacked
4193
+ ON message_outbox (user_id, id)
4194
+ WHERE acked_at IS NULL`,
4195
+ description: "Unacked outbox paging index (GET /outbox)",
4196
+ critical: false
4197
+ },
4198
+ {
4199
+ name: "idx_outbox_created",
4200
+ // 服务 cleanupOutbox 的
4201
+ // DELETE FROM message_outbox WHERE created_at < ?
4202
+ sql: `CREATE INDEX IF NOT EXISTS idx_outbox_created
4203
+ ON message_outbox (created_at)`,
4204
+ description: "Outbox retention cleanup index (cleanupOutbox by created_at)",
4205
+ critical: false
4206
+ },
4207
+ {
4208
+ name: "idx_outbox_acked",
4209
+ // 服务 cleanupOutbox 的
4210
+ // DELETE FROM message_outbox WHERE acked_at IS NOT NULL AND acked_at < ?
4211
+ // 部分索引只收已 ack 的行:未 ack 的那部分本来就不是这条语句的目标,
4212
+ // idx_outbox_unacked 的 WHERE 条件与它正好互补。
4213
+ sql: `CREATE INDEX IF NOT EXISTS idx_outbox_acked
4214
+ ON message_outbox (acked_at)
4215
+ WHERE acked_at IS NOT NULL`,
4216
+ description: "Acked outbox cleanup index (cleanupOutbox by acked_at)",
4217
+ critical: false
4218
+ },
4219
+ {
4220
+ name: "idx_outbox_task_undelivered",
4221
+ // 服务 discardUndeliveredOutboxForTask 的
4222
+ // DELETE FROM message_outbox
4223
+ // WHERE user_id = ? AND task_uuid = ? AND delivered_at IS NULL AND acked_at IS NULL
4224
+ // 没有它这条只能靶着 user_id 走 (user_id, message_id) 或 idx_outbox_unacked,
4225
+ // 单用户部署下 user_id 对每一行都成立,等于把整个未 ack 积压扫一遍。
4226
+ sql: `CREATE INDEX IF NOT EXISTS idx_outbox_task_undelivered
4227
+ ON message_outbox (user_id, task_uuid)
4228
+ WHERE delivered_at IS NULL AND acked_at IS NULL`,
4229
+ description: "Undelivered-by-task discard index (cancel / supersede)",
4230
+ critical: false
4231
+ }
4232
+ ];
4233
+ var SQLITE_ALL_INDEXES = [
4234
+ ...SQLITE_INDEXES,
4235
+ ...CLIENT_STATE_INDEXES,
4236
+ ...MESSAGE_OUTBOX_INDEXES
4237
+ ];
4167
4238
  function parseTableName(sql) {
4168
4239
  const match = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([A-Za-z_][A-Za-z0-9_]*)/i.exec(sql);
4169
4240
  return match ? match[1] : "";
@@ -4198,7 +4269,7 @@ var SQLITE_REQUIRED_SCHEMA = Object.freeze({
4198
4269
  describeTable(LLM_CREDENTIALS_TABLE_SQL),
4199
4270
  describeTable(MESSAGE_OUTBOX_TABLE_SQL)
4200
4271
  ])),
4201
- indexes: Object.freeze(SQLITE_INDEXES.filter((index) => index.critical).map((index) => index.name))
4272
+ indexes: Object.freeze(SQLITE_ALL_INDEXES.filter((index) => index.critical).map((index) => index.name))
4202
4273
  });
4203
4274
 
4204
4275
  // src/server/adapters/d1.js
@@ -4303,7 +4374,6 @@ var D1Adapter = class {
4303
4374
  await this._db.prepare(PUSH_SUBSCRIPTION_TABLE_SQL).run();
4304
4375
  await this._db.prepare(LLM_CREDENTIALS_TABLE_SQL).run();
4305
4376
  await this._db.prepare(MESSAGE_OUTBOX_TABLE_SQL).run();
4306
- await this._db.prepare(MESSAGE_OUTBOX_INDEX_SQL).run();
4307
4377
  for (const migration of SQLITE_MIGRATIONS) {
4308
4378
  try {
4309
4379
  await this._db.prepare(migration.sql).run();
@@ -4312,7 +4382,7 @@ var D1Adapter = class {
4312
4382
  }
4313
4383
  }
4314
4384
  const indexResults = [];
4315
- for (const index of SQLITE_INDEXES) {
4385
+ for (const index of SQLITE_ALL_INDEXES) {
4316
4386
  try {
4317
4387
  await this._db.prepare(index.sql).run();
4318
4388
  indexResults.push({ name: index.name, status: "success", description: index.description, critical: !!index.critical });
@@ -4681,8 +4751,11 @@ var D1Adapter = class {
4681
4751
  * @param {number} [now] - 服务端当前时刻(epoch 毫秒),判定「这行来自未来」用的
4682
4752
  * 就是它。调用方(lib/client-state-store.js)传下来,测试可以钉住一个假时钟;
4683
4753
  * 自定义调用方不传时退回本机时钟。
4684
- * @returns {Promise<{ upserted: number, skipped: number, outcomes: boolean[] }>}
4754
+ * @returns {Promise<{ upserted: number, skipped: number, outcomes: boolean[], cleanupOutcomes?: Array<boolean|null> }>}
4685
4755
  * `outcomes[i]` 对应 entries[i] 是否真的写入(changes > 0)。
4756
+ * `cleanupOutcomes[i]` 对应 cleanups[i](传了 cleanups 才有):精确 key 形态
4757
+ * 回 `true` = 这个 key 的行已经不在(删掉了,或本来就没有)、`false` = 行还在
4758
+ * (库里那行更新,删除被条件写拦下);前缀形态不探测,回 `null`。
4686
4759
  */
4687
4760
  async upsertClientState(userId, entries, cleanups = [], now = Date.now()) {
4688
4761
  const UPSERT_SQL = `INSERT INTO client_state (user_id, namespace, key, value, updated_at)
@@ -4698,29 +4771,53 @@ var D1Adapter = class {
4698
4771
  const CLEANUP_KEY_SQL = `DELETE FROM client_state
4699
4772
  WHERE user_id = ? AND namespace = ? AND key = ?
4700
4773
  AND (updated_at <= ? OR updated_at > ?)`;
4701
- const buildStatements = () => [
4702
- ...cleanups.map((c) => typeof c.key === "string" ? this._db.prepare(CLEANUP_KEY_SQL).bind(userId, c.namespace, c.key, c.updatedAt, now) : this._db.prepare(CLEANUP_PREFIX_SQL).bind(userId, c.namespace, c.keyPrefix, prefixRangeEnd(c.keyPrefix), c.updatedAt, now)),
4703
- ...entries.map(
4704
- (entry) => this._db.prepare(UPSERT_SQL).bind(userId, entry.namespace, entry.key, entry.value, entry.updatedAt, now)
4705
- )
4706
- ];
4774
+ const PROBE_KEY_SQL = `SELECT COUNT(*) AS n FROM client_state
4775
+ WHERE user_id = ? AND namespace = ? AND key = ?`;
4776
+ const statements = [];
4777
+ const probeIndexes = [];
4778
+ for (const c of cleanups) {
4779
+ if (typeof c.key === "string") {
4780
+ statements.push(this._db.prepare(CLEANUP_KEY_SQL).bind(userId, c.namespace, c.key, c.updatedAt, now));
4781
+ probeIndexes.push(statements.length);
4782
+ statements.push(this._db.prepare(PROBE_KEY_SQL).bind(userId, c.namespace, c.key));
4783
+ } else {
4784
+ statements.push(
4785
+ this._db.prepare(CLEANUP_PREFIX_SQL).bind(userId, c.namespace, c.keyPrefix, prefixRangeEnd(c.keyPrefix), c.updatedAt, now)
4786
+ );
4787
+ probeIndexes.push(null);
4788
+ }
4789
+ }
4790
+ const upsertStart = statements.length;
4791
+ for (const entry of entries) {
4792
+ statements.push(
4793
+ this._db.prepare(UPSERT_SQL).bind(userId, entry.namespace, entry.key, entry.value, entry.updatedAt, now)
4794
+ );
4795
+ }
4707
4796
  let results;
4708
4797
  if (typeof this._db.batch === "function") {
4709
- results = await this._db.batch(buildStatements());
4798
+ results = await this._db.batch(statements);
4710
4799
  } else {
4711
4800
  results = [];
4712
- for (const stmt of buildStatements()) {
4801
+ for (const stmt of statements) {
4713
4802
  results.push(await stmt.run());
4714
4803
  }
4715
4804
  }
4716
- const outcomes = results.slice(cleanups.length).map((res) => res.meta.changes > 0);
4805
+ const outcomes = results.slice(upsertStart).map((res) => res.meta.changes > 0);
4717
4806
  let upserted = 0;
4718
4807
  let skipped = 0;
4719
4808
  for (const wrote of outcomes) {
4720
4809
  if (wrote) upserted++;
4721
4810
  else skipped++;
4722
4811
  }
4723
- return { upserted, skipped, outcomes };
4812
+ const result = { upserted, skipped, outcomes };
4813
+ if (cleanups.length > 0) {
4814
+ result.cleanupOutcomes = probeIndexes.map((probeAt) => {
4815
+ if (probeAt === null) return null;
4816
+ const row = results[probeAt] && Array.isArray(results[probeAt].results) ? results[probeAt].results[0] : null;
4817
+ return Number(row?.n ?? 0) === 0;
4818
+ });
4819
+ }
4820
+ return result;
4724
4821
  }
4725
4822
  /**
4726
4823
  * All entries of one namespace (values still encrypted).
@@ -5209,12 +5306,14 @@ function validateEntry(entry, index, maxValueBytes) {
5209
5306
  if (INTERNAL_STATE_CHAR_RE.test(entry.key)) {
5210
5307
  return rejectEntry(entry, index, "INVALID_STATE_KEY", `entries[${index}].key \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26\uFF08\\u0000-\\u001f \u4E3A\u5E93\u5185\u90E8\u4FDD\u7559\uFF09`);
5211
5308
  }
5212
- if (typeof entry.value !== "string") {
5213
- return rejectEntry(entry, index, "INVALID_STATE_VALUE", `entries[${index}].value \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF08\u5BBF\u4E3B\u81EA\u884C\u5E8F\u5217\u5316\uFF09`);
5309
+ if (entry.value !== null && typeof entry.value !== "string") {
5310
+ return rejectEntry(entry, index, "INVALID_STATE_VALUE", `entries[${index}].value \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF08\u5BBF\u4E3B\u81EA\u884C\u5E8F\u5217\u5316\uFF09\uFF0C\u6216 null \u8868\u793A\u5220\u9664`);
5214
5311
  }
5215
- const bytes = stateValueBytes(entry.value);
5216
- if (bytes > maxValueBytes) {
5217
- return rejectEntry(entry, index, "STATE_VALUE_TOO_LARGE", `entries[${index}].value \u8D85\u8FC7\u5355\u6761\u603B\u4E0A\u9650`, { bytes, maxBytes: maxValueBytes });
5312
+ if (typeof entry.value === "string") {
5313
+ const bytes = stateValueBytes(entry.value);
5314
+ if (bytes > maxValueBytes) {
5315
+ return rejectEntry(entry, index, "STATE_VALUE_TOO_LARGE", `entries[${index}].value \u8D85\u8FC7\u5355\u6761\u603B\u4E0A\u9650`, { bytes, maxBytes: maxValueBytes });
5316
+ }
5218
5317
  }
5219
5318
  if (!Number.isInteger(entry.updatedAt) || entry.updatedAt <= 0) {
5220
5319
  return rejectEntry(entry, index, "INVALID_STATE_UPDATED_AT", `entries[${index}].updatedAt \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08epoch \u6BEB\u79D2\uFF09`);
@@ -5275,8 +5374,9 @@ function createClientStateHandler(ctx) {
5275
5374
  if (typeof db.upsertClientState !== "function") {
5276
5375
  return err3(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
5277
5376
  }
5278
- const { upserted, skipped, skippedEntries } = await writeClientStateEntries({ db, userId, userKey, entries: accepted });
5377
+ const { upserted, skipped, deleted, skippedEntries } = await writeClientStateEntries({ db, userId, userKey, entries: accepted });
5279
5378
  const data = { upserted, skipped };
5379
+ if (deleted > 0) data.deleted = deleted;
5280
5380
  if (skippedEntries.length > 0) data.skippedEntries = skippedEntries;
5281
5381
  if (rejected.length > 0) data.rejected = rejected;
5282
5382
  return { status: 200, body: { success: true, data } };
@@ -5323,7 +5423,7 @@ function createClientStateHandler(ctx) {
5323
5423
  }
5324
5424
 
5325
5425
  // src/server/lib/version.js
5326
- var SERVER_VERSION = true ? "2.6.0-next.25" : "0.0.0-dev";
5426
+ var SERVER_VERSION = true ? "2.6.0-next.27" : "0.0.0-dev";
5327
5427
 
5328
5428
  // src/server/handlers/capabilities.js
5329
5429
  var SERVER_FEATURES = Object.freeze([
@@ -5409,7 +5509,10 @@ var SERVER_FEATURES = Object.freeze([
5409
5509
  // client_state 按命名空间过期清理(config 的 clientStateTtl,cron 每跳顺手做)。
5410
5510
  "client-state-ttl",
5411
5511
  // hook ctx 带 emitResult(payload):往客户端补一条自定义结果(落收件箱 + 推送)。
5412
- "emit-result"
5512
+ "emit-result",
5513
+ // PUT /client-state 的 entry 认 value: null(删掉这个 key,连切片行一起;同一套
5514
+ // last-write-wins,被拦下的进 skippedEntries;删掉的条数在 data.deleted)。
5515
+ "client-state-delete"
5413
5516
  ]);
5414
5517
  function createCapabilitiesHandler(ctx) {
5415
5518
  async function GET(url, headers) {
@@ -1060,25 +1060,27 @@ function planClientStateCleanup(ttl, now) {
1060
1060
  }
1061
1061
  async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
1062
1062
  const nowFn = typeof now === "function" ? now : Date.now;
1063
+ const at = nowFn();
1063
1064
  const physicalRows = [];
1064
1065
  const cleanups = [];
1065
1066
  const rootRowIndexes = [];
1066
1067
  const rootRowEntries = [];
1067
- let deleted = 0;
1068
+ const deletions = [];
1068
1069
  for (const entry of entries) {
1069
- const guardAt = Number.isInteger(entry.version) && entry.version > 0 ? entry.version : entry.updatedAt;
1070
+ const rawGuardAt = Number.isInteger(entry.version) && entry.version > 0 ? entry.version : entry.updatedAt;
1071
+ const guardAt = Math.min(rawGuardAt, at);
1070
1072
  cleanups.push({
1071
1073
  namespace: chunkNamespaceFor(entry.namespace),
1072
1074
  keyPrefix: chunkKeyPrefixFor(entry.key),
1073
1075
  updatedAt: guardAt
1074
1076
  });
1075
1077
  if (entry.value === null) {
1078
+ deletions.push({ cleanupIndex: cleanups.length, entry });
1076
1079
  cleanups.push({
1077
1080
  namespace: entry.namespace,
1078
1081
  key: entry.key,
1079
1082
  updatedAt: guardAt
1080
1083
  });
1081
- deleted++;
1082
1084
  continue;
1083
1085
  }
1084
1086
  rootRowIndexes.push(physicalRows.length);
@@ -1112,9 +1114,10 @@ async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
1112
1114
  if (physicalRows.length === 0 && cleanups.length === 0) {
1113
1115
  return { upserted: 0, skipped: 0, deleted: 0, skippedEntries: [] };
1114
1116
  }
1115
- const result = await db.upsertClientState(userId, physicalRows, cleanups, nowFn());
1117
+ const result = await db.upsertClientState(userId, physicalRows, cleanups, at);
1116
1118
  let upserted = 0;
1117
1119
  let skipped = 0;
1120
+ let deleted = 0;
1118
1121
  const skippedEntries = [];
1119
1122
  if (Array.isArray(result.outcomes) && result.outcomes.length === physicalRows.length) {
1120
1123
  for (let i = 0; i < rootRowIndexes.length; i++) {
@@ -1130,6 +1133,15 @@ async function writeClientStateEntries({ db, userId, userKey, entries, now }) {
1130
1133
  upserted = result.upserted;
1131
1134
  skipped = result.skipped;
1132
1135
  }
1136
+ const cleanupOutcomes = Array.isArray(result.cleanupOutcomes) && result.cleanupOutcomes.length === cleanups.length ? result.cleanupOutcomes : null;
1137
+ for (const { cleanupIndex, entry } of deletions) {
1138
+ if (cleanupOutcomes && cleanupOutcomes[cleanupIndex] === false) {
1139
+ skipped++;
1140
+ skippedEntries.push({ namespace: entry.namespace, key: entry.key });
1141
+ } else {
1142
+ deleted++;
1143
+ }
1144
+ }
1133
1145
  return { upserted, skipped, deleted, skippedEntries };
1134
1146
  }
1135
1147
 
@@ -4126,6 +4138,19 @@ var CLIENT_STATE_TABLE_SQL = `
4126
4138
  PRIMARY KEY (user_id, namespace, key)
4127
4139
  )
4128
4140
  `;
4141
+ var CLIENT_STATE_INDEXES = [
4142
+ {
4143
+ name: "idx_client_state_cleanup",
4144
+ // 服务 cleanupClientState 的
4145
+ // DELETE FROM client_state WHERE namespace = ? AND updated_at < ?
4146
+ // 主键 (user_id, namespace, key) 最左列是 user_id,按 namespace 起头的条件
4147
+ // 吃不到它。
4148
+ sql: `CREATE INDEX IF NOT EXISTS idx_client_state_cleanup
4149
+ ON client_state (namespace, updated_at)`,
4150
+ description: "TTL cleanup index (cleanupClientState by namespace + updated_at)",
4151
+ critical: false
4152
+ }
4153
+ ];
4129
4154
  var PUSH_SUBSCRIPTION_TABLE_SQL = `
4130
4155
  CREATE TABLE IF NOT EXISTS push_subscriptions (
4131
4156
  user_id TEXT PRIMARY KEY,
@@ -4159,11 +4184,57 @@ var MESSAGE_OUTBOX_TABLE_SQL = `
4159
4184
  UNIQUE (user_id, message_id)
4160
4185
  )
4161
4186
  `;
4162
- var MESSAGE_OUTBOX_INDEX_SQL = `
4163
- CREATE INDEX IF NOT EXISTS idx_outbox_unacked
4164
- ON message_outbox (user_id, id)
4165
- WHERE acked_at IS NULL
4166
- `;
4187
+ var MESSAGE_OUTBOX_INDEXES = [
4188
+ {
4189
+ name: "idx_outbox_unacked",
4190
+ // 服务 listUnackedOutbox 的
4191
+ // SELECT … WHERE user_id = ? AND acked_at IS NULL AND id > ? ORDER BY id
4192
+ sql: `CREATE INDEX IF NOT EXISTS idx_outbox_unacked
4193
+ ON message_outbox (user_id, id)
4194
+ WHERE acked_at IS NULL`,
4195
+ description: "Unacked outbox paging index (GET /outbox)",
4196
+ critical: false
4197
+ },
4198
+ {
4199
+ name: "idx_outbox_created",
4200
+ // 服务 cleanupOutbox 的
4201
+ // DELETE FROM message_outbox WHERE created_at < ?
4202
+ sql: `CREATE INDEX IF NOT EXISTS idx_outbox_created
4203
+ ON message_outbox (created_at)`,
4204
+ description: "Outbox retention cleanup index (cleanupOutbox by created_at)",
4205
+ critical: false
4206
+ },
4207
+ {
4208
+ name: "idx_outbox_acked",
4209
+ // 服务 cleanupOutbox 的
4210
+ // DELETE FROM message_outbox WHERE acked_at IS NOT NULL AND acked_at < ?
4211
+ // 部分索引只收已 ack 的行:未 ack 的那部分本来就不是这条语句的目标,
4212
+ // idx_outbox_unacked 的 WHERE 条件与它正好互补。
4213
+ sql: `CREATE INDEX IF NOT EXISTS idx_outbox_acked
4214
+ ON message_outbox (acked_at)
4215
+ WHERE acked_at IS NOT NULL`,
4216
+ description: "Acked outbox cleanup index (cleanupOutbox by acked_at)",
4217
+ critical: false
4218
+ },
4219
+ {
4220
+ name: "idx_outbox_task_undelivered",
4221
+ // 服务 discardUndeliveredOutboxForTask 的
4222
+ // DELETE FROM message_outbox
4223
+ // WHERE user_id = ? AND task_uuid = ? AND delivered_at IS NULL AND acked_at IS NULL
4224
+ // 没有它这条只能靶着 user_id 走 (user_id, message_id) 或 idx_outbox_unacked,
4225
+ // 单用户部署下 user_id 对每一行都成立,等于把整个未 ack 积压扫一遍。
4226
+ sql: `CREATE INDEX IF NOT EXISTS idx_outbox_task_undelivered
4227
+ ON message_outbox (user_id, task_uuid)
4228
+ WHERE delivered_at IS NULL AND acked_at IS NULL`,
4229
+ description: "Undelivered-by-task discard index (cancel / supersede)",
4230
+ critical: false
4231
+ }
4232
+ ];
4233
+ var SQLITE_ALL_INDEXES = [
4234
+ ...SQLITE_INDEXES,
4235
+ ...CLIENT_STATE_INDEXES,
4236
+ ...MESSAGE_OUTBOX_INDEXES
4237
+ ];
4167
4238
  function parseTableName(sql) {
4168
4239
  const match = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([A-Za-z_][A-Za-z0-9_]*)/i.exec(sql);
4169
4240
  return match ? match[1] : "";
@@ -4198,7 +4269,7 @@ var SQLITE_REQUIRED_SCHEMA = Object.freeze({
4198
4269
  describeTable(LLM_CREDENTIALS_TABLE_SQL),
4199
4270
  describeTable(MESSAGE_OUTBOX_TABLE_SQL)
4200
4271
  ])),
4201
- indexes: Object.freeze(SQLITE_INDEXES.filter((index) => index.critical).map((index) => index.name))
4272
+ indexes: Object.freeze(SQLITE_ALL_INDEXES.filter((index) => index.critical).map((index) => index.name))
4202
4273
  });
4203
4274
 
4204
4275
  // src/server/adapters/d1.js
@@ -4303,7 +4374,6 @@ var D1Adapter = class {
4303
4374
  await this._db.prepare(PUSH_SUBSCRIPTION_TABLE_SQL).run();
4304
4375
  await this._db.prepare(LLM_CREDENTIALS_TABLE_SQL).run();
4305
4376
  await this._db.prepare(MESSAGE_OUTBOX_TABLE_SQL).run();
4306
- await this._db.prepare(MESSAGE_OUTBOX_INDEX_SQL).run();
4307
4377
  for (const migration of SQLITE_MIGRATIONS) {
4308
4378
  try {
4309
4379
  await this._db.prepare(migration.sql).run();
@@ -4312,7 +4382,7 @@ var D1Adapter = class {
4312
4382
  }
4313
4383
  }
4314
4384
  const indexResults = [];
4315
- for (const index of SQLITE_INDEXES) {
4385
+ for (const index of SQLITE_ALL_INDEXES) {
4316
4386
  try {
4317
4387
  await this._db.prepare(index.sql).run();
4318
4388
  indexResults.push({ name: index.name, status: "success", description: index.description, critical: !!index.critical });
@@ -4681,8 +4751,11 @@ var D1Adapter = class {
4681
4751
  * @param {number} [now] - 服务端当前时刻(epoch 毫秒),判定「这行来自未来」用的
4682
4752
  * 就是它。调用方(lib/client-state-store.js)传下来,测试可以钉住一个假时钟;
4683
4753
  * 自定义调用方不传时退回本机时钟。
4684
- * @returns {Promise<{ upserted: number, skipped: number, outcomes: boolean[] }>}
4754
+ * @returns {Promise<{ upserted: number, skipped: number, outcomes: boolean[], cleanupOutcomes?: Array<boolean|null> }>}
4685
4755
  * `outcomes[i]` 对应 entries[i] 是否真的写入(changes > 0)。
4756
+ * `cleanupOutcomes[i]` 对应 cleanups[i](传了 cleanups 才有):精确 key 形态
4757
+ * 回 `true` = 这个 key 的行已经不在(删掉了,或本来就没有)、`false` = 行还在
4758
+ * (库里那行更新,删除被条件写拦下);前缀形态不探测,回 `null`。
4686
4759
  */
4687
4760
  async upsertClientState(userId, entries, cleanups = [], now = Date.now()) {
4688
4761
  const UPSERT_SQL = `INSERT INTO client_state (user_id, namespace, key, value, updated_at)
@@ -4698,29 +4771,53 @@ var D1Adapter = class {
4698
4771
  const CLEANUP_KEY_SQL = `DELETE FROM client_state
4699
4772
  WHERE user_id = ? AND namespace = ? AND key = ?
4700
4773
  AND (updated_at <= ? OR updated_at > ?)`;
4701
- const buildStatements = () => [
4702
- ...cleanups.map((c) => typeof c.key === "string" ? this._db.prepare(CLEANUP_KEY_SQL).bind(userId, c.namespace, c.key, c.updatedAt, now) : this._db.prepare(CLEANUP_PREFIX_SQL).bind(userId, c.namespace, c.keyPrefix, prefixRangeEnd(c.keyPrefix), c.updatedAt, now)),
4703
- ...entries.map(
4704
- (entry) => this._db.prepare(UPSERT_SQL).bind(userId, entry.namespace, entry.key, entry.value, entry.updatedAt, now)
4705
- )
4706
- ];
4774
+ const PROBE_KEY_SQL = `SELECT COUNT(*) AS n FROM client_state
4775
+ WHERE user_id = ? AND namespace = ? AND key = ?`;
4776
+ const statements = [];
4777
+ const probeIndexes = [];
4778
+ for (const c of cleanups) {
4779
+ if (typeof c.key === "string") {
4780
+ statements.push(this._db.prepare(CLEANUP_KEY_SQL).bind(userId, c.namespace, c.key, c.updatedAt, now));
4781
+ probeIndexes.push(statements.length);
4782
+ statements.push(this._db.prepare(PROBE_KEY_SQL).bind(userId, c.namespace, c.key));
4783
+ } else {
4784
+ statements.push(
4785
+ this._db.prepare(CLEANUP_PREFIX_SQL).bind(userId, c.namespace, c.keyPrefix, prefixRangeEnd(c.keyPrefix), c.updatedAt, now)
4786
+ );
4787
+ probeIndexes.push(null);
4788
+ }
4789
+ }
4790
+ const upsertStart = statements.length;
4791
+ for (const entry of entries) {
4792
+ statements.push(
4793
+ this._db.prepare(UPSERT_SQL).bind(userId, entry.namespace, entry.key, entry.value, entry.updatedAt, now)
4794
+ );
4795
+ }
4707
4796
  let results;
4708
4797
  if (typeof this._db.batch === "function") {
4709
- results = await this._db.batch(buildStatements());
4798
+ results = await this._db.batch(statements);
4710
4799
  } else {
4711
4800
  results = [];
4712
- for (const stmt of buildStatements()) {
4801
+ for (const stmt of statements) {
4713
4802
  results.push(await stmt.run());
4714
4803
  }
4715
4804
  }
4716
- const outcomes = results.slice(cleanups.length).map((res) => res.meta.changes > 0);
4805
+ const outcomes = results.slice(upsertStart).map((res) => res.meta.changes > 0);
4717
4806
  let upserted = 0;
4718
4807
  let skipped = 0;
4719
4808
  for (const wrote of outcomes) {
4720
4809
  if (wrote) upserted++;
4721
4810
  else skipped++;
4722
4811
  }
4723
- return { upserted, skipped, outcomes };
4812
+ const result = { upserted, skipped, outcomes };
4813
+ if (cleanups.length > 0) {
4814
+ result.cleanupOutcomes = probeIndexes.map((probeAt) => {
4815
+ if (probeAt === null) return null;
4816
+ const row = results[probeAt] && Array.isArray(results[probeAt].results) ? results[probeAt].results[0] : null;
4817
+ return Number(_nullishCoalesce(_optionalChain([row, 'optionalAccess', _3 => _3.n]), () => ( 0))) === 0;
4818
+ });
4819
+ }
4820
+ return result;
4724
4821
  }
4725
4822
  /**
4726
4823
  * All entries of one namespace (values still encrypted).
@@ -5209,12 +5306,14 @@ function validateEntry(entry, index, maxValueBytes) {
5209
5306
  if (INTERNAL_STATE_CHAR_RE.test(entry.key)) {
5210
5307
  return rejectEntry(entry, index, "INVALID_STATE_KEY", `entries[${index}].key \u4E0D\u80FD\u5305\u542B\u63A7\u5236\u5B57\u7B26\uFF08\\u0000-\\u001f \u4E3A\u5E93\u5185\u90E8\u4FDD\u7559\uFF09`);
5211
5308
  }
5212
- if (typeof entry.value !== "string") {
5213
- return rejectEntry(entry, index, "INVALID_STATE_VALUE", `entries[${index}].value \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF08\u5BBF\u4E3B\u81EA\u884C\u5E8F\u5217\u5316\uFF09`);
5309
+ if (entry.value !== null && typeof entry.value !== "string") {
5310
+ return rejectEntry(entry, index, "INVALID_STATE_VALUE", `entries[${index}].value \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\uFF08\u5BBF\u4E3B\u81EA\u884C\u5E8F\u5217\u5316\uFF09\uFF0C\u6216 null \u8868\u793A\u5220\u9664`);
5214
5311
  }
5215
- const bytes = stateValueBytes(entry.value);
5216
- if (bytes > maxValueBytes) {
5217
- return rejectEntry(entry, index, "STATE_VALUE_TOO_LARGE", `entries[${index}].value \u8D85\u8FC7\u5355\u6761\u603B\u4E0A\u9650`, { bytes, maxBytes: maxValueBytes });
5312
+ if (typeof entry.value === "string") {
5313
+ const bytes = stateValueBytes(entry.value);
5314
+ if (bytes > maxValueBytes) {
5315
+ return rejectEntry(entry, index, "STATE_VALUE_TOO_LARGE", `entries[${index}].value \u8D85\u8FC7\u5355\u6761\u603B\u4E0A\u9650`, { bytes, maxBytes: maxValueBytes });
5316
+ }
5218
5317
  }
5219
5318
  if (!Number.isInteger(entry.updatedAt) || entry.updatedAt <= 0) {
5220
5319
  return rejectEntry(entry, index, "INVALID_STATE_UPDATED_AT", `entries[${index}].updatedAt \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08epoch \u6BEB\u79D2\uFF09`);
@@ -5275,8 +5374,9 @@ function createClientStateHandler(ctx) {
5275
5374
  if (typeof db.upsertClientState !== "function") {
5276
5375
  return err3(501, "CLIENT_STATE_NOT_SUPPORTED", "\u5F53\u524D\u6570\u636E\u5E93\u9002\u914D\u5668\u4E0D\u652F\u6301 client_state");
5277
5376
  }
5278
- const { upserted, skipped, skippedEntries } = await writeClientStateEntries({ db, userId, userKey, entries: accepted });
5377
+ const { upserted, skipped, deleted, skippedEntries } = await writeClientStateEntries({ db, userId, userKey, entries: accepted });
5279
5378
  const data = { upserted, skipped };
5379
+ if (deleted > 0) data.deleted = deleted;
5280
5380
  if (skippedEntries.length > 0) data.skippedEntries = skippedEntries;
5281
5381
  if (rejected.length > 0) data.rejected = rejected;
5282
5382
  return { status: 200, body: { success: true, data } };
@@ -5323,7 +5423,7 @@ function createClientStateHandler(ctx) {
5323
5423
  }
5324
5424
 
5325
5425
  // src/server/lib/version.js
5326
- var SERVER_VERSION = true ? "2.6.0-next.25" : "0.0.0-dev";
5426
+ var SERVER_VERSION = true ? "2.6.0-next.27" : "0.0.0-dev";
5327
5427
 
5328
5428
  // src/server/handlers/capabilities.js
5329
5429
  var SERVER_FEATURES = Object.freeze([
@@ -5409,7 +5509,10 @@ var SERVER_FEATURES = Object.freeze([
5409
5509
  // client_state 按命名空间过期清理(config 的 clientStateTtl,cron 每跳顺手做)。
5410
5510
  "client-state-ttl",
5411
5511
  // hook ctx 带 emitResult(payload):往客户端补一条自定义结果(落收件箱 + 推送)。
5412
- "emit-result"
5512
+ "emit-result",
5513
+ // PUT /client-state 的 entry 认 value: null(删掉这个 key,连切片行一起;同一套
5514
+ // last-write-wins,被拦下的进 skippedEntries;删掉的条数在 data.deleted)。
5515
+ "client-state-delete"
5413
5516
  ]);
5414
5517
  function createCapabilitiesHandler(ctx) {
5415
5518
  async function GET(url, headers) {
@@ -26,7 +26,7 @@
26
26
 
27
27
 
28
28
 
29
- var _chunkDZJHAAYScjs = require('./chunk-DZJHAAYS.cjs');
29
+ var _chunkWCXN5AWJcjs = require('./chunk-WCXN5AWJ.cjs');
30
30
  require('./chunk-7NMQFTDJ.cjs');
31
31
 
32
32
 
@@ -56,4 +56,4 @@ require('./chunk-7NMQFTDJ.cjs');
56
56
 
57
57
 
58
58
 
59
- exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkDZJHAAYScjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkDZJHAAYScjs.MAX_PUSH_PAYLOAD_BYTES; exports.NonRetryableError = _chunkDZJHAAYScjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkDZJHAAYScjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkDZJHAAYScjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkDZJHAAYScjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkDZJHAAYScjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkDZJHAAYScjs.advanceOccurrence; exports.createD1Adapter = _chunkDZJHAAYScjs.createD1Adapter; exports.createSingleUserCloudflareWorker = _chunkDZJHAAYScjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkDZJHAAYScjs.createSingleUserServer; exports.createWebCryptoWebPush = _chunkDZJHAAYScjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkDZJHAAYScjs.decryptFromStorage; exports.decryptPayload = _chunkDZJHAAYScjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkDZJHAAYScjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkDZJHAAYScjs.encryptForStorage; exports.ensureSchema = _chunkDZJHAAYScjs.ensureSchema; exports.getSchemaVersion = _chunkDZJHAAYScjs.getSchemaVersion; exports.isNonRetryableError = _chunkDZJHAAYScjs.isNonRetryableError; exports.isValidTimeZoneId = _chunkDZJHAAYScjs.isValidTimeZoneId; exports.measurePushPayload = _chunkDZJHAAYScjs.measurePushPayload; exports.nextFutureOccurrence = _chunkDZJHAAYScjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkDZJHAAYScjs.planNextOccurrence; exports.readRequestBody = _chunkDZJHAAYScjs.readRequestBody; exports.runScheduledTick = _chunkDZJHAAYScjs.runScheduledTick; exports.runTask = _chunkDZJHAAYScjs.runTask; exports.summarizeErrorCause = _chunkDZJHAAYScjs.summarizeErrorCause;
59
+ exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkWCXN5AWJcjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkWCXN5AWJcjs.MAX_PUSH_PAYLOAD_BYTES; exports.NonRetryableError = _chunkWCXN5AWJcjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkWCXN5AWJcjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkWCXN5AWJcjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkWCXN5AWJcjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkWCXN5AWJcjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkWCXN5AWJcjs.advanceOccurrence; exports.createD1Adapter = _chunkWCXN5AWJcjs.createD1Adapter; exports.createSingleUserCloudflareWorker = _chunkWCXN5AWJcjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkWCXN5AWJcjs.createSingleUserServer; exports.createWebCryptoWebPush = _chunkWCXN5AWJcjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkWCXN5AWJcjs.decryptFromStorage; exports.decryptPayload = _chunkWCXN5AWJcjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkWCXN5AWJcjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkWCXN5AWJcjs.encryptForStorage; exports.ensureSchema = _chunkWCXN5AWJcjs.ensureSchema; exports.getSchemaVersion = _chunkWCXN5AWJcjs.getSchemaVersion; exports.isNonRetryableError = _chunkWCXN5AWJcjs.isNonRetryableError; exports.isValidTimeZoneId = _chunkWCXN5AWJcjs.isValidTimeZoneId; exports.measurePushPayload = _chunkWCXN5AWJcjs.measurePushPayload; exports.nextFutureOccurrence = _chunkWCXN5AWJcjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkWCXN5AWJcjs.planNextOccurrence; exports.readRequestBody = _chunkWCXN5AWJcjs.readRequestBody; exports.runScheduledTick = _chunkWCXN5AWJcjs.runScheduledTick; exports.runTask = _chunkWCXN5AWJcjs.runTask; exports.summarizeErrorCause = _chunkWCXN5AWJcjs.summarizeErrorCause;
@@ -26,7 +26,7 @@ import {
26
26
  runScheduledTick,
27
27
  runTask,
28
28
  summarizeErrorCause
29
- } from "./chunk-X2ZEXRBI.mjs";
29
+ } from "./chunk-FPVXATA4.mjs";
30
30
  import "./chunk-GN44PST5.mjs";
31
31
  export {
32
32
  DEFAULT_MAX_REQUEST_BODY_BYTES,
package/dist/index.cjs CHANGED
@@ -71,7 +71,7 @@
71
71
 
72
72
 
73
73
 
74
- var _chunkDZJHAAYScjs = require('./chunk-DZJHAAYS.cjs');
74
+ var _chunkWCXN5AWJcjs = require('./chunk-WCXN5AWJ.cjs');
75
75
  require('./chunk-7NMQFTDJ.cjs');
76
76
 
77
77
  // src/server/adapters/factory.js
@@ -105,7 +105,7 @@ async function createAdapter(config) {
105
105
  // src/server/handlers/init-tenant.js
106
106
  function createInitTenantHandler(ctx) {
107
107
  async function POST(headers, body) {
108
- const initSecret = _chunkDZJHAAYScjs.getHeader.call(void 0, headers, "x-init-secret");
108
+ const initSecret = _chunkWCXN5AWJcjs.getHeader.call(void 0, headers, "x-init-secret");
109
109
  if (ctx.tenant.initSecret && initSecret !== ctx.tenant.initSecret) {
110
110
  return {
111
111
  status: 401,
@@ -118,7 +118,7 @@ function createInitTenantHandler(ctx) {
118
118
  }
119
119
  };
120
120
  }
121
- const parsed = _chunkDZJHAAYScjs.parseJsonBody.call(void 0, body);
121
+ const parsed = _chunkWCXN5AWJcjs.parseJsonBody.call(void 0, body);
122
122
  if (!parsed.ok) {
123
123
  return {
124
124
  status: 400,
@@ -132,7 +132,7 @@ function createInitTenantHandler(ctx) {
132
132
  const tenantId = payload.tenantId || void 0;
133
133
  const driver = payload.driver;
134
134
  const databaseUrl = payload.databaseUrl || payload.connectionString;
135
- if (tenantId && !_chunkDZJHAAYScjs.isValidUUIDv4.call(void 0, tenantId)) {
135
+ if (tenantId && !_chunkWCXN5AWJcjs.isValidUUIDv4.call(void 0, tenantId)) {
136
136
  return {
137
137
  status: 400,
138
138
  body: {
@@ -252,7 +252,7 @@ function createSendNotificationsHandler(ctx) {
252
252
  }
253
253
  };
254
254
  }
255
- const data = await _chunkDZJHAAYScjs.runScheduledTick.call(void 0, { ...ctx, db, masterKey });
255
+ const data = await _chunkWCXN5AWJcjs.runScheduledTick.call(void 0, { ...ctx, db, masterKey });
256
256
  return { status: 200, body: { success: true, data } };
257
257
  }
258
258
  return { POST };
@@ -274,16 +274,16 @@ function encryptConfig(config, kekBuffer) {
274
274
  const plaintext = Buffer.from(JSON.stringify(config), "utf8");
275
275
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
276
276
  const authTag = cipher.getAuthTag();
277
- return `v1.${_chunkDZJHAAYScjs.bytesToBase64Url.call(void 0, iv)}.${_chunkDZJHAAYScjs.bytesToBase64Url.call(void 0, authTag)}.${_chunkDZJHAAYScjs.bytesToBase64Url.call(void 0, ciphertext)}`;
277
+ return `v1.${_chunkWCXN5AWJcjs.bytesToBase64Url.call(void 0, iv)}.${_chunkWCXN5AWJcjs.bytesToBase64Url.call(void 0, authTag)}.${_chunkWCXN5AWJcjs.bytesToBase64Url.call(void 0, ciphertext)}`;
278
278
  }
279
279
  function decryptConfig(encrypted, kekBuffer) {
280
280
  const parts = String(encrypted || "").split(".");
281
281
  if (parts.length !== 4 || parts[0] !== "v1") {
282
282
  throw new Error("INVALID_TENANT_CONFIG");
283
283
  }
284
- const iv = _chunkDZJHAAYScjs.base64UrlToBytes.call(void 0, parts[1]);
285
- const authTag = _chunkDZJHAAYScjs.base64UrlToBytes.call(void 0, parts[2]);
286
- const ciphertext = _chunkDZJHAAYScjs.base64UrlToBytes.call(void 0, parts[3]);
284
+ const iv = _chunkWCXN5AWJcjs.base64UrlToBytes.call(void 0, parts[1]);
285
+ const authTag = _chunkWCXN5AWJcjs.base64UrlToBytes.call(void 0, parts[2]);
286
+ const ciphertext = _chunkWCXN5AWJcjs.base64UrlToBytes.call(void 0, parts[3]);
287
287
  const decipher = _crypto.createDecipheriv.call(void 0, "aes-256-gcm", kekBuffer, iv);
288
288
  decipher.setAuthTag(authTag);
289
289
  const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
@@ -351,7 +351,7 @@ function createTenantBlobStore(options) {
351
351
  // src/server/tenant/token.js
352
352
 
353
353
  function sign(input, secret) {
354
- return _chunkDZJHAAYScjs.bytesToBase64Url.call(void 0, _crypto.createHmac.call(void 0, "sha256", secret).update(input).digest());
354
+ return _chunkWCXN5AWJcjs.bytesToBase64Url.call(void 0, _crypto.createHmac.call(void 0, "sha256", secret).update(input).digest());
355
355
  }
356
356
  function nowEpochSeconds() {
357
357
  return Math.floor(Date.now() / 1e3);
@@ -367,8 +367,8 @@ function createTenantToken(params, secret) {
367
367
  v: 1
368
368
  };
369
369
  const header = { alg: "HS256", typ: "JWT" };
370
- const encodedHeader = _chunkDZJHAAYScjs.bytesToBase64Url.call(void 0, _chunkDZJHAAYScjs.utf8.call(void 0, JSON.stringify(header)));
371
- const encodedPayload = _chunkDZJHAAYScjs.bytesToBase64Url.call(void 0, _chunkDZJHAAYScjs.utf8.call(void 0, JSON.stringify(payload)));
370
+ const encodedHeader = _chunkWCXN5AWJcjs.bytesToBase64Url.call(void 0, _chunkWCXN5AWJcjs.utf8.call(void 0, JSON.stringify(header)));
371
+ const encodedPayload = _chunkWCXN5AWJcjs.bytesToBase64Url.call(void 0, _chunkWCXN5AWJcjs.utf8.call(void 0, JSON.stringify(payload)));
372
372
  const signingInput = `${encodedHeader}.${encodedPayload}`;
373
373
  const signature = sign(signingInput, secret);
374
374
  return `${signingInput}.${signature}`;
@@ -387,17 +387,17 @@ function verifyTenantToken(token, secret, options = {}) {
387
387
  let receivedBytes;
388
388
  let expectedBytes;
389
389
  try {
390
- receivedBytes = _chunkDZJHAAYScjs.base64UrlToBytes.call(void 0, receivedSignature);
391
- expectedBytes = _chunkDZJHAAYScjs.base64UrlToBytes.call(void 0, expectedSignature);
390
+ receivedBytes = _chunkWCXN5AWJcjs.base64UrlToBytes.call(void 0, receivedSignature);
391
+ expectedBytes = _chunkWCXN5AWJcjs.base64UrlToBytes.call(void 0, expectedSignature);
392
392
  } catch (e) {
393
393
  throw new Error("INVALID_TENANT_AUTH");
394
394
  }
395
- if (!_chunkDZJHAAYScjs.timingSafeEqualBytes.call(void 0, receivedBytes, expectedBytes)) {
395
+ if (!_chunkWCXN5AWJcjs.timingSafeEqualBytes.call(void 0, receivedBytes, expectedBytes)) {
396
396
  throw new Error("INVALID_TENANT_AUTH");
397
397
  }
398
398
  let payload;
399
399
  try {
400
- payload = JSON.parse(_chunkDZJHAAYScjs.utf8Decode.call(void 0, _chunkDZJHAAYScjs.base64UrlToBytes.call(void 0, encodedPayload)));
400
+ payload = JSON.parse(_chunkWCXN5AWJcjs.utf8Decode.call(void 0, _chunkWCXN5AWJcjs.base64UrlToBytes.call(void 0, encodedPayload)));
401
401
  } catch (e2) {
402
402
  throw new Error("INVALID_TENANT_AUTH");
403
403
  }
@@ -621,15 +621,15 @@ async function createReiServer(config) {
621
621
  return {
622
622
  handlers: {
623
623
  initTenant: createInitTenantHandler(ctx),
624
- getUserKey: _chunkDZJHAAYScjs.createGetUserKeyHandler.call(void 0, ctx),
625
- scheduleMessage: _chunkDZJHAAYScjs.createScheduleMessageHandler.call(void 0, ctx),
624
+ getUserKey: _chunkWCXN5AWJcjs.createGetUserKeyHandler.call(void 0, ctx),
625
+ scheduleMessage: _chunkWCXN5AWJcjs.createScheduleMessageHandler.call(void 0, ctx),
626
626
  sendNotifications: createSendNotificationsHandler(ctx),
627
- updateMessage: _chunkDZJHAAYScjs.createUpdateMessageHandler.call(void 0, ctx),
628
- cancelMessage: _chunkDZJHAAYScjs.createCancelMessageHandler.call(void 0, ctx),
629
- messages: _chunkDZJHAAYScjs.createMessagesHandler.call(void 0, ctx),
630
- getMessage: _chunkDZJHAAYScjs.createGetMessageHandler.call(void 0, ctx),
631
- pushSubscription: _chunkDZJHAAYScjs.createPushSubscriptionHandler.call(void 0, ctx),
632
- llmCredentials: _chunkDZJHAAYScjs.createLlmCredentialsHandler.call(void 0, ctx)
627
+ updateMessage: _chunkWCXN5AWJcjs.createUpdateMessageHandler.call(void 0, ctx),
628
+ cancelMessage: _chunkWCXN5AWJcjs.createCancelMessageHandler.call(void 0, ctx),
629
+ messages: _chunkWCXN5AWJcjs.createMessagesHandler.call(void 0, ctx),
630
+ getMessage: _chunkWCXN5AWJcjs.createGetMessageHandler.call(void 0, ctx),
631
+ pushSubscription: _chunkWCXN5AWJcjs.createPushSubscriptionHandler.call(void 0, ctx),
632
+ llmCredentials: _chunkWCXN5AWJcjs.createLlmCredentialsHandler.call(void 0, ctx)
633
633
  }
634
634
  };
635
635
  }
@@ -695,4 +695,4 @@ async function createReiServer(config) {
695
695
 
696
696
 
697
697
 
698
- exports.CORS_ALLOW_HEADERS = _chunkDZJHAAYScjs.CORS_ALLOW_HEADERS; exports.CORS_ALLOW_METHODS = _chunkDZJHAAYScjs.CORS_ALLOW_METHODS; exports.CRED_ID_MAX_LENGTH = _chunkDZJHAAYScjs.CRED_ID_MAX_LENGTH; exports.CRED_PUT_BATCH_MAX = _chunkDZJHAAYScjs.CRED_PUT_BATCH_MAX; exports.CRED_REFS_MAX_ENTRIES = _chunkDZJHAAYScjs.CRED_REFS_MAX_ENTRIES; exports.CRED_ROWS_PER_USER_MAX = _chunkDZJHAAYScjs.CRED_ROWS_PER_USER_MAX; exports.DEFAULT_CLAIM_LEASE_MS = _chunkDZJHAAYScjs.DEFAULT_CLAIM_LEASE_MS; exports.DEFAULT_HEARTBEAT_LEASE_TTL_MS = _chunkDZJHAAYScjs.DEFAULT_HEARTBEAT_LEASE_TTL_MS; exports.DEFAULT_LEASE_HEARTBEAT_MS = _chunkDZJHAAYScjs.DEFAULT_LEASE_HEARTBEAT_MS; exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkDZJHAAYScjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE = _chunkDZJHAAYScjs.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE; exports.DEFAULT_MAX_TOOL_ITERATIONS = _chunkDZJHAAYScjs.DEFAULT_MAX_TOOL_ITERATIONS; exports.DEFAULT_TOTAL_TIMEOUT_MS = _chunkDZJHAAYScjs.DEFAULT_TOTAL_TIMEOUT_MS; exports.DeploymentConfigError = _chunkDZJHAAYScjs.DeploymentConfigError; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkDZJHAAYScjs.MAX_PUSH_PAYLOAD_BYTES; exports.MAX_TASK_PAYLOAD_BYTES = _chunkDZJHAAYScjs.MAX_TASK_PAYLOAD_BYTES; exports.MIN_SCHEDULE_LEAD_MS = _chunkDZJHAAYScjs.MIN_SCHEDULE_LEAD_MS; exports.NonRetryableError = _chunkDZJHAAYScjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkDZJHAAYScjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkDZJHAAYScjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkDZJHAAYScjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkDZJHAAYScjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkDZJHAAYScjs.advanceOccurrence; exports.createAdapter = createAdapter; exports.createD1Adapter = _chunkDZJHAAYScjs.createD1Adapter; exports.createReiServer = createReiServer; exports.createSingleUserCloudflareWorker = _chunkDZJHAAYScjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkDZJHAAYScjs.createSingleUserServer; exports.createTenantToken = createTenantToken; exports.createWebCryptoWebPush = _chunkDZJHAAYScjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkDZJHAAYScjs.decryptFromStorage; exports.decryptPayload = _chunkDZJHAAYScjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkDZJHAAYScjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkDZJHAAYScjs.encryptForStorage; exports.ensureSchema = _chunkDZJHAAYScjs.ensureSchema; exports.getSchemaVersion = _chunkDZJHAAYScjs.getSchemaVersion; exports.hasChatCredRef = _chunkDZJHAAYScjs.hasChatCredRef; exports.isNonRetryableError = _chunkDZJHAAYScjs.isNonRetryableError; exports.isValidCredId = _chunkDZJHAAYScjs.isValidCredId; exports.isValidISO8601 = _chunkDZJHAAYScjs.isValidISO8601; exports.isValidTimeZoneId = _chunkDZJHAAYScjs.isValidTimeZoneId; exports.isValidUUID = _chunkDZJHAAYScjs.isValidUUID; exports.isValidUUIDv4 = _chunkDZJHAAYScjs.isValidUUIDv4; exports.isValidUrl = _chunkDZJHAAYScjs.isValidUrl; exports.measurePushPayload = _chunkDZJHAAYScjs.measurePushPayload; exports.nextFutureOccurrence = _chunkDZJHAAYScjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkDZJHAAYScjs.planNextOccurrence; exports.readRequestBody = _chunkDZJHAAYScjs.readRequestBody; exports.resolveLlmCredential = _chunkDZJHAAYScjs.resolveLlmCredential; exports.runScheduledTick = _chunkDZJHAAYScjs.runScheduledTick; exports.runTask = _chunkDZJHAAYScjs.runTask; exports.sanitizeErrorSummary = _chunkDZJHAAYScjs.sanitizeErrorSummary; exports.summarizeErrorCause = _chunkDZJHAAYScjs.summarizeErrorCause; exports.supportsLlmCredentialsStore = _chunkDZJHAAYScjs.supportsLlmCredentialsStore; exports.validateAvatarUrl = _chunkDZJHAAYScjs.validateAvatarUrl; exports.validateCredRefs = _chunkDZJHAAYScjs.validateCredRefs; exports.validateCredValue = _chunkDZJHAAYScjs.validateCredValue; exports.validateLlmMessagesArray = _chunkDZJHAAYScjs.validateLlmMessagesArray; exports.validateScheduleMessagePayload = _chunkDZJHAAYScjs.validateScheduleMessagePayload; exports.validateSplitPattern = _chunkDZJHAAYScjs.validateSplitPattern; exports.verifyTenantToken = verifyTenantToken;
698
+ exports.CORS_ALLOW_HEADERS = _chunkWCXN5AWJcjs.CORS_ALLOW_HEADERS; exports.CORS_ALLOW_METHODS = _chunkWCXN5AWJcjs.CORS_ALLOW_METHODS; exports.CRED_ID_MAX_LENGTH = _chunkWCXN5AWJcjs.CRED_ID_MAX_LENGTH; exports.CRED_PUT_BATCH_MAX = _chunkWCXN5AWJcjs.CRED_PUT_BATCH_MAX; exports.CRED_REFS_MAX_ENTRIES = _chunkWCXN5AWJcjs.CRED_REFS_MAX_ENTRIES; exports.CRED_ROWS_PER_USER_MAX = _chunkWCXN5AWJcjs.CRED_ROWS_PER_USER_MAX; exports.DEFAULT_CLAIM_LEASE_MS = _chunkWCXN5AWJcjs.DEFAULT_CLAIM_LEASE_MS; exports.DEFAULT_HEARTBEAT_LEASE_TTL_MS = _chunkWCXN5AWJcjs.DEFAULT_HEARTBEAT_LEASE_TTL_MS; exports.DEFAULT_LEASE_HEARTBEAT_MS = _chunkWCXN5AWJcjs.DEFAULT_LEASE_HEARTBEAT_MS; exports.DEFAULT_MAX_REQUEST_BODY_BYTES = _chunkWCXN5AWJcjs.DEFAULT_MAX_REQUEST_BODY_BYTES; exports.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE = _chunkWCXN5AWJcjs.DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE; exports.DEFAULT_MAX_TOOL_ITERATIONS = _chunkWCXN5AWJcjs.DEFAULT_MAX_TOOL_ITERATIONS; exports.DEFAULT_TOTAL_TIMEOUT_MS = _chunkWCXN5AWJcjs.DEFAULT_TOTAL_TIMEOUT_MS; exports.DeploymentConfigError = _chunkWCXN5AWJcjs.DeploymentConfigError; exports.MAX_PUSH_PAYLOAD_BYTES = _chunkWCXN5AWJcjs.MAX_PUSH_PAYLOAD_BYTES; exports.MAX_TASK_PAYLOAD_BYTES = _chunkWCXN5AWJcjs.MAX_TASK_PAYLOAD_BYTES; exports.MIN_SCHEDULE_LEAD_MS = _chunkWCXN5AWJcjs.MIN_SCHEDULE_LEAD_MS; exports.NonRetryableError = _chunkWCXN5AWJcjs.NonRetryableError; exports.PUSH_ENVELOPE_RESERVED_BYTES = _chunkWCXN5AWJcjs.PUSH_ENVELOPE_RESERVED_BYTES; exports.SCHEMA_VERSION = _chunkWCXN5AWJcjs.SCHEMA_VERSION; exports.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES = _chunkWCXN5AWJcjs.WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES; exports.WEB_PUSH_MAX_BODY_BYTES = _chunkWCXN5AWJcjs.WEB_PUSH_MAX_BODY_BYTES; exports.advanceOccurrence = _chunkWCXN5AWJcjs.advanceOccurrence; exports.createAdapter = createAdapter; exports.createD1Adapter = _chunkWCXN5AWJcjs.createD1Adapter; exports.createReiServer = createReiServer; exports.createSingleUserCloudflareWorker = _chunkWCXN5AWJcjs.createSingleUserCloudflareWorker; exports.createSingleUserServer = _chunkWCXN5AWJcjs.createSingleUserServer; exports.createTenantToken = createTenantToken; exports.createWebCryptoWebPush = _chunkWCXN5AWJcjs.createWebCryptoWebPush; exports.decryptFromStorage = _chunkWCXN5AWJcjs.decryptFromStorage; exports.decryptPayload = _chunkWCXN5AWJcjs.decryptPayload; exports.deriveUserEncryptionKey = _chunkWCXN5AWJcjs.deriveUserEncryptionKey; exports.encryptForStorage = _chunkWCXN5AWJcjs.encryptForStorage; exports.ensureSchema = _chunkWCXN5AWJcjs.ensureSchema; exports.getSchemaVersion = _chunkWCXN5AWJcjs.getSchemaVersion; exports.hasChatCredRef = _chunkWCXN5AWJcjs.hasChatCredRef; exports.isNonRetryableError = _chunkWCXN5AWJcjs.isNonRetryableError; exports.isValidCredId = _chunkWCXN5AWJcjs.isValidCredId; exports.isValidISO8601 = _chunkWCXN5AWJcjs.isValidISO8601; exports.isValidTimeZoneId = _chunkWCXN5AWJcjs.isValidTimeZoneId; exports.isValidUUID = _chunkWCXN5AWJcjs.isValidUUID; exports.isValidUUIDv4 = _chunkWCXN5AWJcjs.isValidUUIDv4; exports.isValidUrl = _chunkWCXN5AWJcjs.isValidUrl; exports.measurePushPayload = _chunkWCXN5AWJcjs.measurePushPayload; exports.nextFutureOccurrence = _chunkWCXN5AWJcjs.nextFutureOccurrence; exports.planNextOccurrence = _chunkWCXN5AWJcjs.planNextOccurrence; exports.readRequestBody = _chunkWCXN5AWJcjs.readRequestBody; exports.resolveLlmCredential = _chunkWCXN5AWJcjs.resolveLlmCredential; exports.runScheduledTick = _chunkWCXN5AWJcjs.runScheduledTick; exports.runTask = _chunkWCXN5AWJcjs.runTask; exports.sanitizeErrorSummary = _chunkWCXN5AWJcjs.sanitizeErrorSummary; exports.summarizeErrorCause = _chunkWCXN5AWJcjs.summarizeErrorCause; exports.supportsLlmCredentialsStore = _chunkWCXN5AWJcjs.supportsLlmCredentialsStore; exports.validateAvatarUrl = _chunkWCXN5AWJcjs.validateAvatarUrl; exports.validateCredRefs = _chunkWCXN5AWJcjs.validateCredRefs; exports.validateCredValue = _chunkWCXN5AWJcjs.validateCredValue; exports.validateLlmMessagesArray = _chunkWCXN5AWJcjs.validateLlmMessagesArray; exports.validateScheduleMessagePayload = _chunkWCXN5AWJcjs.validateScheduleMessagePayload; exports.validateSplitPattern = _chunkWCXN5AWJcjs.validateSplitPattern; exports.verifyTenantToken = verifyTenantToken;
package/dist/index.mjs CHANGED
@@ -71,7 +71,7 @@ import {
71
71
  validateLlmMessagesArray,
72
72
  validateScheduleMessagePayload,
73
73
  validateSplitPattern
74
- } from "./chunk-X2ZEXRBI.mjs";
74
+ } from "./chunk-FPVXATA4.mjs";
75
75
  import "./chunk-GN44PST5.mjs";
76
76
 
77
77
  // src/server/adapters/factory.js
@@ -53,9 +53,12 @@ export function planClientStateCleanup(ttl: Record<string, number> | null | unde
53
53
  * @param {() => number} [args.now] - 取服务端当前时刻(测试可注入假时钟)。适配器
54
54
  * 拿它认出库里「来自未来」的脏行。
55
55
  * @returns {Promise<{ upserted: number, skipped: number, deleted: number, skippedEntries: Array<{ namespace: string, key: string }> }>}
56
- * `upserted` / `skipped` 按逻辑条目计(切片行不计);`deleted` 是请求删除的
57
- * key 数,不代表这些 key 原本一定存在。`skippedEntries` 逐条列出被
58
- * last-write-wins 拦下的 key(适配器不回 outcomes 时为空数组——分不清是哪条)。
56
+ * `upserted` / `skipped` / `deleted` 按逻辑条目计(切片行不计),三者之和等于
57
+ * 条目数。`deleted` 是删除之后 key 已不在库里的条数——删掉了、或本来就没有,
58
+ * 两种都算;被 last-write-wins 拦下的删除(库里那行更新)计入 `skipped`。
59
+ * `skippedEntries` 逐条列出被拦下的 key,写入与删除都在里面(适配器不回
60
+ * outcomes / cleanupOutcomes 时分不清是哪条:写入按物理行计数兜底,删除一律
61
+ * 按「已删」计)。
59
62
  */
60
63
  export function writeClientStateEntries({ db, userId, userKey, entries, now }: {
61
64
  db: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-server",
3
- "version": "2.6.0-next.25",
3
+ "version": "2.6.0-next.27",
4
4
  "description": "ReiStandard Active Messaging server SDK with pluggable database adapters. Three-axis push schema (messageKind / messageType / messageSubtype) from @rei-standard/amsg-shared. Auto-emits ReasoningPush when the LLM response carries reasoning_content.",
5
5
  "repository": {
6
6
  "type": "git",