@rei-standard/amsg-server 2.6.0-next.34 → 2.6.0-next.35
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/dist/adapters/d1.d.ts +28 -4
- package/dist/adapters/interface.d.ts +1 -0
- package/dist/adapters/schema.sqlite.d.ts +13 -1
- package/dist/{chunk-BACG4KJQ.mjs → chunk-INQD6LRY.mjs} +216 -47
- package/dist/{chunk-5GRXWKQR.cjs → chunk-UG5ZNM5J.cjs} +234 -65
- package/dist/cloudflare.cjs +2 -2
- package/dist/cloudflare.mjs +1 -1
- package/dist/index.cjs +25 -25
- package/dist/index.mjs +1 -1
- package/dist/lib/schema-version.d.ts +1 -1
- package/package.json +1 -1
package/dist/adapters/d1.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ export class D1Adapter {
|
|
|
67
67
|
data: any;
|
|
68
68
|
updatedAt: any;
|
|
69
69
|
}>;
|
|
70
|
-
putCloudDataRecord(userId: any, kind: any, id: any, data: any, { idempotencyKey, createOnly, leaseToken }?: {
|
|
70
|
+
putCloudDataRecord(userId: any, kind: any, id: any, data: any, { idempotencyKey, createOnly, leaseToken, work }?: {
|
|
71
71
|
idempotencyKey?: any;
|
|
72
72
|
createOnly?: boolean;
|
|
73
73
|
}): Promise<{
|
|
@@ -105,8 +105,31 @@ export class D1Adapter {
|
|
|
105
105
|
renewCloudDataRecordLease(userId: any, kind: any, id: any, leaseMs: any, leaseToken: any): Promise<boolean>;
|
|
106
106
|
deleteCloudDataRecord(userId: any, kind: any, id: any): Promise<boolean>;
|
|
107
107
|
cleanupCloudDataRecords(kind: any, beforeMs: any): Promise<any>;
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
listDueCloudDataOperations(now?: number, limit?: number): Promise<any[]>;
|
|
109
|
+
cleanupExpiredCloudDataRecords(now?: number, limit?: number): Promise<any>;
|
|
110
|
+
getCloudMaintenanceBatch(name: any, table: any, limit?: number): Promise<{
|
|
111
|
+
rows: any[];
|
|
112
|
+
completed: boolean;
|
|
113
|
+
upperBound?: undefined;
|
|
114
|
+
cursor?: undefined;
|
|
115
|
+
} | {
|
|
116
|
+
rows: any;
|
|
117
|
+
upperBound: any;
|
|
118
|
+
cursor: any;
|
|
119
|
+
completed: boolean;
|
|
120
|
+
}>;
|
|
121
|
+
finishCloudMaintenanceBatch(name: any, cursor: any, completed: any): Promise<void>;
|
|
122
|
+
indexCloudDataRecordWork(userId: any, kind: any, id: any, work: any, expectedWriteToken?: any): Promise<void>;
|
|
123
|
+
readCloudMaintenanceRecord(row: any): Promise<{
|
|
124
|
+
id: any;
|
|
125
|
+
userId: any;
|
|
126
|
+
kind: any;
|
|
127
|
+
data: any;
|
|
128
|
+
updatedAt: any;
|
|
129
|
+
}>;
|
|
130
|
+
repairCloudResourceMetadata(limit?: number): Promise<void>;
|
|
131
|
+
/** One explicit repair, or a bounded upgrade batch; never called as a recurring sweep. */
|
|
132
|
+
cleanupCloudResourceMetadata(userId?: any, range?: any): Promise<any>;
|
|
110
133
|
listCloudOwners(userId: any): Promise<any>;
|
|
111
134
|
getCloudOwner(userId: any, owner: any): Promise<{
|
|
112
135
|
active: boolean;
|
|
@@ -151,11 +174,12 @@ export class D1Adapter {
|
|
|
151
174
|
* 构变了而老部署没跑过 initSchema 时,cron 会每分钟静默挂在缺的那一列上,
|
|
152
175
|
* 界面上一切正常——这个方法就是让宿主查得出来。
|
|
153
176
|
*
|
|
154
|
-
* @returns {Promise<{ tables: Record<string, string[]>, indexes: string[] }>}
|
|
177
|
+
* @returns {Promise<{ tables: Record<string, string[]>, indexes: string[], triggers: string[] }>}
|
|
155
178
|
*/
|
|
156
179
|
describeSchema(): Promise<{
|
|
157
180
|
tables: Record<string, string[]>;
|
|
158
181
|
indexes: string[];
|
|
182
|
+
triggers: string[];
|
|
159
183
|
}>;
|
|
160
184
|
dropSchema(): Promise<void>;
|
|
161
185
|
createTask(params: any, cloudGuard?: any): Promise<any>;
|
|
@@ -54,15 +54,27 @@ export const SQLITE_ALL_INDEXES: {
|
|
|
54
54
|
critical: boolean;
|
|
55
55
|
}[];
|
|
56
56
|
export const CLOUD_DATA_TABLES_SQL: string[];
|
|
57
|
+
export const CLOUD_DATA_WORK_TABLES_SQL: string[];
|
|
58
|
+
export const CLOUD_DATA_WORK_INDEXES: {
|
|
59
|
+
name: string;
|
|
60
|
+
sql: string;
|
|
61
|
+
description: string;
|
|
62
|
+
critical: boolean;
|
|
63
|
+
}[];
|
|
64
|
+
export const CLOUD_DATA_DELETE_TRIGGERS: {
|
|
65
|
+
name: string;
|
|
66
|
+
sql: string;
|
|
67
|
+
}[];
|
|
57
68
|
/**
|
|
58
69
|
* 这一版代码跑起来需要的表 / 列 / 索引。
|
|
59
70
|
*
|
|
60
71
|
* 索引只列 critical 的那几个(uidx_uuid 之类):其余索引缺了只是慢,缺了它则
|
|
61
72
|
* 是正确性问题。
|
|
62
73
|
*
|
|
63
|
-
* @type {{ tables: Record<string, string[]>, indexes: string[] }}
|
|
74
|
+
* @type {{ tables: Record<string, string[]>, indexes: string[], triggers: string[] }}
|
|
64
75
|
*/
|
|
65
76
|
export const SQLITE_REQUIRED_SCHEMA: {
|
|
66
77
|
tables: Record<string, string[]>;
|
|
67
78
|
indexes: string[];
|
|
79
|
+
triggers: string[];
|
|
68
80
|
};
|
|
@@ -5070,6 +5070,66 @@ var CLOUD_DATA_TABLES_SQL = [
|
|
|
5070
5070
|
lease_allowed INTEGER CONSTRAINT cloud_lease_guard CHECK (lease_allowed = 1)
|
|
5071
5071
|
)`
|
|
5072
5072
|
];
|
|
5073
|
+
var CLOUD_DATA_WORK_TABLES_SQL = [
|
|
5074
|
+
`CREATE TABLE IF NOT EXISTS cloud_data_work (
|
|
5075
|
+
user_id TEXT NOT NULL, kind TEXT NOT NULL, id TEXT NOT NULL,
|
|
5076
|
+
next_run_at INTEGER, expires_at INTEGER,
|
|
5077
|
+
PRIMARY KEY (user_id, kind, id)
|
|
5078
|
+
)`,
|
|
5079
|
+
`CREATE TABLE IF NOT EXISTS cloud_data_maintenance (
|
|
5080
|
+
name TEXT PRIMARY KEY, cursor INTEGER NOT NULL, upper_bound INTEGER NOT NULL,
|
|
5081
|
+
completed INTEGER NOT NULL DEFAULT 0
|
|
5082
|
+
)`
|
|
5083
|
+
];
|
|
5084
|
+
CLOUD_DATA_TABLES_SQL.push(...CLOUD_DATA_WORK_TABLES_SQL);
|
|
5085
|
+
var CLOUD_DATA_WORK_INDEXES = [
|
|
5086
|
+
{
|
|
5087
|
+
name: "idx_cloud_work_due",
|
|
5088
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_cloud_work_due
|
|
5089
|
+
ON cloud_data_work (kind, next_run_at) WHERE next_run_at IS NOT NULL`,
|
|
5090
|
+
description: "Only due cleanup operations",
|
|
5091
|
+
critical: true
|
|
5092
|
+
},
|
|
5093
|
+
{
|
|
5094
|
+
name: "idx_cloud_work_expiry",
|
|
5095
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_cloud_work_expiry
|
|
5096
|
+
ON cloud_data_work (expires_at) WHERE expires_at IS NOT NULL`,
|
|
5097
|
+
description: "Only expired management records",
|
|
5098
|
+
critical: true
|
|
5099
|
+
}
|
|
5100
|
+
];
|
|
5101
|
+
SQLITE_ALL_INDEXES.push(...CLOUD_DATA_WORK_INDEXES);
|
|
5102
|
+
var chunkPrefixSql = "char(31)||'amsg-chunks'||char(31)";
|
|
5103
|
+
var logicalNamespaceSql = `CASE WHEN substr(OLD.namespace,1,13)=${chunkPrefixSql}
|
|
5104
|
+
THEN substr(OLD.namespace,14) ELSE OLD.namespace END`;
|
|
5105
|
+
var logicalKeySql = `CASE WHEN substr(OLD.namespace,1,13)=${chunkPrefixSql} AND instr(OLD.key,char(31))>0
|
|
5106
|
+
THEN substr(OLD.key,1,instr(OLD.key,char(31))-1) ELSE OLD.key END`;
|
|
5107
|
+
var CLOUD_DATA_DELETE_TRIGGERS = [
|
|
5108
|
+
...[
|
|
5109
|
+
["scheduled_messages", "task", "uuid"],
|
|
5110
|
+
["llm_credentials", "credential", "cred_id"],
|
|
5111
|
+
["message_outbox", "outbox", "message_id"],
|
|
5112
|
+
["push_subscriptions", "subscription", "user_id"]
|
|
5113
|
+
].map(([table, type, column]) => ({
|
|
5114
|
+
name: `trg_cloud_metadata_${type}_delete`,
|
|
5115
|
+
sql: `CREATE TRIGGER IF NOT EXISTS trg_cloud_metadata_${type}_delete AFTER DELETE ON ${table}
|
|
5116
|
+
BEGIN DELETE FROM cloud_resource_metadata WHERE user_id=OLD.user_id
|
|
5117
|
+
AND resource_key=json_array('${type}'${column ? ",OLD." + column : ""}); END`
|
|
5118
|
+
})),
|
|
5119
|
+
{ name: "trg_cloud_metadata_state_delete", sql: `CREATE TRIGGER IF NOT EXISTS trg_cloud_metadata_state_delete
|
|
5120
|
+
AFTER DELETE ON client_state BEGIN
|
|
5121
|
+
DELETE FROM cloud_resource_metadata WHERE user_id=OLD.user_id
|
|
5122
|
+
AND resource_key=json_array('state',${logicalNamespaceSql},${logicalKeySql})
|
|
5123
|
+
AND NOT EXISTS (SELECT 1 FROM client_state r WHERE r.user_id=OLD.user_id
|
|
5124
|
+
AND r.namespace=(${logicalNamespaceSql}) AND r.key=(${logicalKeySql}))
|
|
5125
|
+
AND NOT EXISTS (SELECT 1 FROM client_state r WHERE r.user_id=OLD.user_id
|
|
5126
|
+
AND r.namespace=${chunkPrefixSql}||(${logicalNamespaceSql})
|
|
5127
|
+
AND r.key >= (${logicalKeySql})||char(31) AND r.key < (${logicalKeySql})||char(32)); END` },
|
|
5128
|
+
{ name: "trg_cloud_work_delete", sql: `CREATE TRIGGER IF NOT EXISTS trg_cloud_work_delete
|
|
5129
|
+
AFTER DELETE ON cloud_data_records BEGIN
|
|
5130
|
+
DELETE FROM cloud_data_work WHERE user_id=OLD.user_id AND kind=OLD.kind AND id=OLD.id;
|
|
5131
|
+
DELETE FROM cloud_data_record_chunks WHERE user_id=OLD.user_id AND kind=OLD.kind AND id=OLD.id; END` }
|
|
5132
|
+
];
|
|
5073
5133
|
function parseTableName(sql) {
|
|
5074
5134
|
const match = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([A-Za-z_][A-Za-z0-9_]*)/i.exec(sql);
|
|
5075
5135
|
return match ? match[1] : "";
|
|
@@ -5105,7 +5165,8 @@ var SQLITE_REQUIRED_SCHEMA = Object.freeze({
|
|
|
5105
5165
|
describeTable(MESSAGE_OUTBOX_TABLE_SQL),
|
|
5106
5166
|
...CLOUD_DATA_TABLES_SQL.map(describeTable)
|
|
5107
5167
|
])),
|
|
5108
|
-
indexes: Object.freeze(SQLITE_ALL_INDEXES.filter((index) => index.critical).map((index) => index.name))
|
|
5168
|
+
indexes: Object.freeze(SQLITE_ALL_INDEXES.filter((index) => index.critical).map((index) => index.name)),
|
|
5169
|
+
triggers: Object.freeze(CLOUD_DATA_DELETE_TRIGGERS.map((trigger) => trigger.name))
|
|
5109
5170
|
});
|
|
5110
5171
|
|
|
5111
5172
|
// src/server/adapters/d1.js
|
|
@@ -5304,7 +5365,7 @@ var D1Adapter = class {
|
|
|
5304
5365
|
}
|
|
5305
5366
|
return { id: row.id, userId: row.user_id, kind: row.kind, data, updatedAt: row.updated_at };
|
|
5306
5367
|
}
|
|
5307
|
-
async putCloudDataRecord(userId, kind, id, data, { idempotencyKey = null, createOnly = false, leaseToken } = {}) {
|
|
5368
|
+
async putCloudDataRecord(userId, kind, id, data, { idempotencyKey = null, createOnly = false, leaseToken, work } = {}) {
|
|
5308
5369
|
const slices = [];
|
|
5309
5370
|
if (data.length > 200 * 1024) for (let offset = 0; offset < data.length; offset += 200 * 1024) slices.push(data.slice(offset, offset + 200 * 1024));
|
|
5310
5371
|
const rootValue = slices.length ? "amsg-record" + slices.length : data;
|
|
@@ -5331,6 +5392,12 @@ var D1Adapter = class {
|
|
|
5331
5392
|
SELECT ?,?,?,?,?,? WHERE ${rootGate}`
|
|
5332
5393
|
).bind(userId, kind, id, index, slices[index], writeToken, userId, kind, id, writeToken));
|
|
5333
5394
|
}
|
|
5395
|
+
const schedule = work ?? (["inventory", "plan"].includes(kind) ? { nextRunAt: null, expiresAt: now + 36e5 } : null);
|
|
5396
|
+
if (schedule) statements.push(this._db.prepare(
|
|
5397
|
+
`INSERT INTO cloud_data_work (user_id,kind,id,next_run_at,expires_at)
|
|
5398
|
+
SELECT ?,?,?,?,? WHERE ${rootGate}
|
|
5399
|
+
ON CONFLICT (user_id,kind,id) DO UPDATE SET next_run_at=excluded.next_run_at,expires_at=excluded.expires_at`
|
|
5400
|
+
).bind(userId, kind, id, schedule.nextRunAt ?? null, schedule.expiresAt ?? null, userId, kind, id, writeToken));
|
|
5334
5401
|
if (!this.cloudDataManagement) throw new Error("Cloud management requires transactional batches");
|
|
5335
5402
|
const results = await this._db.batch(statements);
|
|
5336
5403
|
if (leaseToken !== void 0 && !results[0].meta.changes) throw Object.assign(new Error("Cloud operation processing lease was lost"), { code: "CLOUD_LEASE_LOST" });
|
|
@@ -5408,23 +5475,85 @@ var D1Adapter = class {
|
|
|
5408
5475
|
]);
|
|
5409
5476
|
return results[1].meta.changes || 0;
|
|
5410
5477
|
}
|
|
5411
|
-
|
|
5412
|
-
|
|
5478
|
+
async listDueCloudDataOperations(now = Date.now(), limit = 25) {
|
|
5479
|
+
const result = await this._db.prepare(`SELECT r.* FROM cloud_data_work w JOIN cloud_data_records r
|
|
5480
|
+
ON r.user_id=w.user_id AND r.kind=w.kind AND r.id=w.id
|
|
5481
|
+
WHERE w.kind='operation' AND w.next_run_at IS NOT NULL AND w.next_run_at<=?
|
|
5482
|
+
AND (r.lease_until IS NULL OR r.lease_until<=?)
|
|
5483
|
+
ORDER BY w.next_run_at LIMIT ?`).bind(now, now, limit).all();
|
|
5484
|
+
return Promise.all((result.results || []).map((row) => this._cloudRecordForList(row)));
|
|
5485
|
+
}
|
|
5486
|
+
async cleanupExpiredCloudDataRecords(now = Date.now(), limit = 100) {
|
|
5487
|
+
const result = await this._db.prepare(`DELETE FROM cloud_data_records WHERE rowid IN (
|
|
5488
|
+
SELECT r.rowid FROM cloud_data_work w JOIN cloud_data_records r
|
|
5489
|
+
ON r.user_id=w.user_id AND r.kind=w.kind AND r.id=w.id
|
|
5490
|
+
WHERE w.expires_at IS NOT NULL AND w.expires_at<=?
|
|
5491
|
+
AND (r.lease_until IS NULL OR r.lease_until<=?) ORDER BY w.expires_at LIMIT ?
|
|
5492
|
+
)`).bind(now, now, limit).run();
|
|
5493
|
+
return result.meta.changes || 0;
|
|
5494
|
+
}
|
|
5495
|
+
async getCloudMaintenanceBatch(name, table, limit = 100) {
|
|
5496
|
+
if (!["cloud_resource_metadata", "cloud_data_records"].includes(table)) throw new Error("Invalid maintenance source");
|
|
5497
|
+
let marker = await this._db.prepare("SELECT * FROM cloud_data_maintenance WHERE name=?").bind(name).first();
|
|
5498
|
+
if (!marker) {
|
|
5499
|
+
await this._db.prepare(`INSERT INTO cloud_data_maintenance (name,cursor,upper_bound,completed)
|
|
5500
|
+
SELECT ?,0,COALESCE(MAX(rowid),0),0 FROM ${table} WHERE 1 ON CONFLICT (name) DO NOTHING`).bind(name).run();
|
|
5501
|
+
marker = await this._db.prepare("SELECT * FROM cloud_data_maintenance WHERE name=?").bind(name).first();
|
|
5502
|
+
}
|
|
5503
|
+
if (marker.completed) return { rows: [], completed: true };
|
|
5504
|
+
const result = await this._db.prepare(`SELECT rowid AS maintenance_rowid,* FROM ${table}
|
|
5505
|
+
WHERE rowid>? AND rowid<=? ORDER BY rowid LIMIT ?`).bind(marker.cursor, marker.upper_bound, limit).all();
|
|
5506
|
+
return { rows: result.results || [], upperBound: marker.upper_bound, cursor: marker.cursor, completed: false };
|
|
5507
|
+
}
|
|
5508
|
+
async finishCloudMaintenanceBatch(name, cursor, completed) {
|
|
5509
|
+
await this._db.prepare(`UPDATE cloud_data_maintenance SET cursor=MAX(cursor,?),completed=MAX(completed,?) WHERE name=?`).bind(cursor, completed ? 1 : 0, name).run();
|
|
5510
|
+
}
|
|
5511
|
+
async indexCloudDataRecordWork(userId, kind, id, work, expectedWriteToken = null) {
|
|
5512
|
+
await this._db.prepare(`INSERT INTO cloud_data_work (user_id,kind,id,next_run_at,expires_at)
|
|
5513
|
+
SELECT user_id,kind,id,?,? FROM cloud_data_records WHERE user_id=? AND kind=? AND id=? AND write_token IS ?
|
|
5514
|
+
ON CONFLICT (user_id,kind,id) DO NOTHING`).bind(work.nextRunAt ?? null, work.expiresAt ?? null, userId, kind, id, expectedWriteToken).run();
|
|
5515
|
+
}
|
|
5516
|
+
async readCloudMaintenanceRecord(row) {
|
|
5517
|
+
return this._cloudRecordForList(row);
|
|
5518
|
+
}
|
|
5519
|
+
async repairCloudResourceMetadata(limit = 100) {
|
|
5520
|
+
const name = "metadata-sidecars-v1";
|
|
5521
|
+
const batch = await this.getCloudMaintenanceBatch(name, "cloud_resource_metadata", limit);
|
|
5522
|
+
if (batch.completed) return;
|
|
5523
|
+
const cursor = batch.rows.at(-1)?.maintenance_rowid ?? batch.upperBound;
|
|
5524
|
+
if (batch.rows.length) await this.cleanupCloudResourceMetadata(null, { after: batch.cursor, through: cursor });
|
|
5525
|
+
await this.finishCloudMaintenanceBatch(name, cursor, batch.rows.length < limit || cursor >= batch.upperBound);
|
|
5526
|
+
}
|
|
5527
|
+
/** One explicit repair, or a bounded upgrade batch; never called as a recurring sweep. */
|
|
5528
|
+
async cleanupCloudResourceMetadata(userId = null, range = null) {
|
|
5413
5529
|
const targets = {
|
|
5414
|
-
task: `SELECT 1 FROM scheduled_messages r WHERE r.user_id=m.user_id AND r.uuid=json_extract(m.resource_key,'$[1]')
|
|
5415
|
-
state:
|
|
5416
|
-
|
|
5417
|
-
|
|
5530
|
+
task: [`SELECT 1 FROM scheduled_messages r WHERE r.user_id=m.user_id AND r.uuid=json_extract(m.resource_key,'$[1]')`],
|
|
5531
|
+
state: [
|
|
5532
|
+
`SELECT 1 FROM client_state r WHERE r.user_id=m.user_id
|
|
5533
|
+
AND r.namespace=json_extract(m.resource_key,'$[1]') AND r.key=json_extract(m.resource_key,'$[2]')`,
|
|
5534
|
+
`SELECT 1 FROM client_state r WHERE r.user_id=m.user_id
|
|
5535
|
+
AND r.namespace=char(31)||'amsg-chunks'||char(31)||json_extract(m.resource_key,'$[1]')
|
|
5418
5536
|
AND r.key >= json_extract(m.resource_key,'$[2]')||char(31)
|
|
5419
|
-
AND r.key < json_extract(m.resource_key,'$[2]')||char(32)
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5537
|
+
AND r.key < json_extract(m.resource_key,'$[2]')||char(32)`
|
|
5538
|
+
],
|
|
5539
|
+
credential: [`SELECT 1 FROM llm_credentials r WHERE r.user_id=m.user_id AND r.cred_id=json_extract(m.resource_key,'$[1]')`],
|
|
5540
|
+
outbox: [`SELECT 1 FROM message_outbox r WHERE r.user_id=m.user_id AND r.message_id=json_extract(m.resource_key,'$[1]')`],
|
|
5541
|
+
subscription: [`SELECT 1 FROM push_subscriptions r WHERE r.user_id=m.user_id`]
|
|
5423
5542
|
};
|
|
5424
|
-
const statements = Object.entries(targets).map(([type, exists]) =>
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5543
|
+
const statements = Object.entries(targets).map(([type, exists]) => {
|
|
5544
|
+
const conditions = ["json_valid(resource_key)", "json_extract(resource_key,'$[0]')=?"];
|
|
5545
|
+
const args = [type];
|
|
5546
|
+
if (userId !== null) {
|
|
5547
|
+
conditions.unshift("user_id=?");
|
|
5548
|
+
args.unshift(userId);
|
|
5549
|
+
}
|
|
5550
|
+
if (range) {
|
|
5551
|
+
conditions.unshift("m.rowid>? AND m.rowid<=?");
|
|
5552
|
+
args.unshift(range.after, range.through);
|
|
5553
|
+
}
|
|
5554
|
+
return this._db.prepare(`DELETE FROM cloud_resource_metadata AS m WHERE ${conditions.join(" AND ")}
|
|
5555
|
+
AND ${exists.map((sql) => `NOT EXISTS (${sql})`).join(" AND ")}`).bind(...args);
|
|
5556
|
+
});
|
|
5428
5557
|
const results = await this._cloudBatch(statements, userId);
|
|
5429
5558
|
return results.reduce((sum, result) => sum + (result.meta.changes || 0), 0);
|
|
5430
5559
|
}
|
|
@@ -5564,6 +5693,7 @@ var D1Adapter = class {
|
|
|
5564
5693
|
indexResults.push({ name: index.name, status: "failed", description: index.description, critical: !!index.critical, error: error.message });
|
|
5565
5694
|
}
|
|
5566
5695
|
}
|
|
5696
|
+
for (const trigger of CLOUD_DATA_DELETE_TRIGGERS) await this._db.prepare(trigger.sql).run();
|
|
5567
5697
|
const criticalFailures = indexResults.filter((i) => i.critical && i.status === "failed");
|
|
5568
5698
|
if (criticalFailures.length > 0) {
|
|
5569
5699
|
const names = criticalFailures.map((i) => i.name).join(", ");
|
|
@@ -5587,7 +5717,7 @@ var D1Adapter = class {
|
|
|
5587
5717
|
* 构变了而老部署没跑过 initSchema 时,cron 会每分钟静默挂在缺的那一列上,
|
|
5588
5718
|
* 界面上一切正常——这个方法就是让宿主查得出来。
|
|
5589
5719
|
*
|
|
5590
|
-
* @returns {Promise<{ tables: Record<string, string[]>, indexes: string[] }>}
|
|
5720
|
+
* @returns {Promise<{ tables: Record<string, string[]>, indexes: string[], triggers: string[] }>}
|
|
5591
5721
|
*/
|
|
5592
5722
|
async describeSchema() {
|
|
5593
5723
|
const tableRes = await this._db.prepare(
|
|
@@ -5604,7 +5734,8 @@ var D1Adapter = class {
|
|
|
5604
5734
|
const indexRes = await this._db.prepare(
|
|
5605
5735
|
`SELECT name FROM sqlite_master WHERE type = 'index' AND name IS NOT NULL`
|
|
5606
5736
|
).all();
|
|
5607
|
-
|
|
5737
|
+
const triggerRes = await this._db.prepare("SELECT name FROM sqlite_master WHERE type = 'trigger'").all();
|
|
5738
|
+
return { tables, indexes: (indexRes.results || []).map((row) => row.name), triggers: (triggerRes.results || []).map((row) => row.name) };
|
|
5608
5739
|
}
|
|
5609
5740
|
async dropSchema() {
|
|
5610
5741
|
await this._db.prepare("DROP TABLE IF EXISTS scheduled_messages").run();
|
|
@@ -5612,7 +5743,7 @@ var D1Adapter = class {
|
|
|
5612
5743
|
await this._db.prepare("DROP TABLE IF EXISTS push_subscriptions").run();
|
|
5613
5744
|
await this._db.prepare("DROP TABLE IF EXISTS llm_credentials").run();
|
|
5614
5745
|
await this._db.prepare("DROP TABLE IF EXISTS message_outbox").run();
|
|
5615
|
-
for (const table of ["cloud_data_record_chunks", "cloud_data_records", "cloud_data_owners", "cloud_resource_metadata", "cloud_guard_assertions"]) {
|
|
5746
|
+
for (const table of ["cloud_data_work", "cloud_data_maintenance", "cloud_data_record_chunks", "cloud_data_records", "cloud_data_owners", "cloud_resource_metadata", "cloud_guard_assertions"]) {
|
|
5616
5747
|
await this._db.prepare(`DROP TABLE IF EXISTS ${table}`).run();
|
|
5617
5748
|
}
|
|
5618
5749
|
}
|
|
@@ -5890,7 +6021,8 @@ var D1Adapter = class {
|
|
|
5890
6021
|
const cutoff = new Date(Date.now() - safeDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
5891
6022
|
const res = await this._db.prepare(
|
|
5892
6023
|
`DELETE FROM scheduled_messages
|
|
5893
|
-
WHERE
|
|
6024
|
+
WHERE rowid IN (SELECT rowid FROM scheduled_messages
|
|
6025
|
+
WHERE status IN ('sent', 'failed') AND updated_at < ? LIMIT 100)`
|
|
5894
6026
|
).bind(cutoff).run();
|
|
5895
6027
|
return res.meta.changes || 0;
|
|
5896
6028
|
}
|
|
@@ -6056,7 +6188,7 @@ var D1Adapter = class {
|
|
|
6056
6188
|
*/
|
|
6057
6189
|
async cleanupClientState(targets = []) {
|
|
6058
6190
|
if (!Array.isArray(targets) || targets.length === 0) return 0;
|
|
6059
|
-
const SQL = "DELETE FROM client_state WHERE namespace = ? AND updated_at < ?";
|
|
6191
|
+
const SQL = "DELETE FROM client_state WHERE rowid IN (SELECT rowid FROM client_state WHERE namespace = ? AND updated_at < ? LIMIT 100)";
|
|
6060
6192
|
const statements = targets.map(
|
|
6061
6193
|
(target) => this._db.prepare(SQL).bind(target.namespace, target.updatedBefore)
|
|
6062
6194
|
);
|
|
@@ -6552,13 +6684,13 @@ var D1Adapter = class {
|
|
|
6552
6684
|
let deleted = 0;
|
|
6553
6685
|
if (Number.isFinite(ackedBeforeMs)) {
|
|
6554
6686
|
const res = await this._db.prepare(
|
|
6555
|
-
"DELETE FROM message_outbox WHERE acked_at IS NOT NULL AND acked_at < ?"
|
|
6687
|
+
"DELETE FROM message_outbox WHERE rowid IN (SELECT rowid FROM message_outbox WHERE acked_at IS NOT NULL AND acked_at < ? LIMIT 100)"
|
|
6556
6688
|
).bind(ackedBeforeMs).run();
|
|
6557
6689
|
deleted += res.meta.changes || 0;
|
|
6558
6690
|
}
|
|
6559
6691
|
if (Number.isFinite(allBeforeMs)) {
|
|
6560
6692
|
const res = await this._db.prepare(
|
|
6561
|
-
"DELETE FROM message_outbox WHERE created_at < ?"
|
|
6693
|
+
"DELETE FROM message_outbox WHERE rowid IN (SELECT rowid FROM message_outbox WHERE created_at < ? LIMIT 100)"
|
|
6562
6694
|
).bind(allBeforeMs).run();
|
|
6563
6695
|
deleted += res.meta.changes || 0;
|
|
6564
6696
|
}
|
|
@@ -6879,7 +7011,7 @@ function createClientStateNamespacesHandler(ctx) {
|
|
|
6879
7011
|
}
|
|
6880
7012
|
|
|
6881
7013
|
// src/server/lib/version.js
|
|
6882
|
-
var SERVER_VERSION = true ? "2.6.0-next.
|
|
7014
|
+
var SERVER_VERSION = true ? "2.6.0-next.35" : "0.0.0-dev";
|
|
6883
7015
|
|
|
6884
7016
|
// src/server/handlers/capabilities.js
|
|
6885
7017
|
var SERVER_FEATURES = Object.freeze([
|
|
@@ -7301,6 +7433,15 @@ async function cloudInventory(ctx, db, userId, userKey) {
|
|
|
7301
7433
|
resolveOwner: ctx.cloudData?.resolveOwner
|
|
7302
7434
|
});
|
|
7303
7435
|
}
|
|
7436
|
+
function operationWork(operation) {
|
|
7437
|
+
if (["pending", "running"].includes(operation.status)) {
|
|
7438
|
+
return { nextRunAt: Number(operation.nextAttemptAt) || 0, expiresAt: null };
|
|
7439
|
+
}
|
|
7440
|
+
if (["completed", "failed"].includes(operation.status)) {
|
|
7441
|
+
return { nextRunAt: null, expiresAt: Number(operation.updatedAt) + 30 * 864e5 };
|
|
7442
|
+
}
|
|
7443
|
+
return { nextRunAt: null, expiresAt: null };
|
|
7444
|
+
}
|
|
7304
7445
|
async function save(db, userId, key, kind, value, options) {
|
|
7305
7446
|
return db.putCloudDataRecord(
|
|
7306
7447
|
userId,
|
|
@@ -7313,7 +7454,7 @@ async function save(db, userId, key, kind, value, options) {
|
|
|
7313
7454
|
}),
|
|
7314
7455
|
key
|
|
7315
7456
|
),
|
|
7316
|
-
options
|
|
7457
|
+
{ ...options, ...kind === "operation" ? { work: operationWork(value) } : {} }
|
|
7317
7458
|
);
|
|
7318
7459
|
}
|
|
7319
7460
|
async function readCloudRecord(db, userId, key, kind, id) {
|
|
@@ -7675,25 +7816,44 @@ async function advanceCleanup(ctx, db, userId, key, operation) {
|
|
|
7675
7816
|
}
|
|
7676
7817
|
return operation;
|
|
7677
7818
|
}
|
|
7819
|
+
async function repairLegacyWorkIndex(ctx) {
|
|
7820
|
+
const db = ctx.db;
|
|
7821
|
+
if (typeof db.getCloudMaintenanceBatch !== "function") return;
|
|
7822
|
+
const name = "management-work-v1";
|
|
7823
|
+
const batch = await db.getCloudMaintenanceBatch(name, "cloud_data_records", 100);
|
|
7824
|
+
if (batch.completed) return;
|
|
7825
|
+
for (const row of batch.rows) {
|
|
7826
|
+
let work;
|
|
7827
|
+
if (["inventory", "plan"].includes(row.kind)) {
|
|
7828
|
+
work = { nextRunAt: null, expiresAt: row.updated_at + 36e5 };
|
|
7829
|
+
} else if (row.kind === "operation") {
|
|
7830
|
+
try {
|
|
7831
|
+
const record = await db.readCloudMaintenanceRecord(row);
|
|
7832
|
+
const key = await deriveUserEncryptionKey(row.user_id, ctx.masterKey);
|
|
7833
|
+
work = operationWork(JSON.parse(await decryptFromStorage(record.data, key)));
|
|
7834
|
+
} catch {
|
|
7835
|
+
console.warn("[amsg] Legacy cleanup operation could not be indexed");
|
|
7836
|
+
continue;
|
|
7837
|
+
}
|
|
7838
|
+
} else continue;
|
|
7839
|
+
await db.indexCloudDataRecordWork(row.user_id, row.kind, row.id, work, row.write_token);
|
|
7840
|
+
}
|
|
7841
|
+
const cursor = batch.rows.at(-1)?.maintenance_rowid ?? batch.upperBound;
|
|
7842
|
+
await db.finishCloudMaintenanceBatch(name, cursor, batch.rows.length < 100 || cursor >= batch.upperBound);
|
|
7843
|
+
}
|
|
7678
7844
|
async function resumeCloudDataCleanups(ctx) {
|
|
7679
7845
|
const db = ctx.db;
|
|
7680
7846
|
if (!db?.cloudDataManagement) return;
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
await db.
|
|
7685
|
-
let processed = 0;
|
|
7847
|
+
await repairLegacyWorkIndex(ctx);
|
|
7848
|
+
if (typeof db.repairCloudResourceMetadata === "function") await db.repairCloudResourceMetadata(100);
|
|
7849
|
+
if (typeof db.cleanupExpiredCloudDataRecords === "function") await db.cleanupExpiredCloudDataRecords(Date.now(), 100);
|
|
7850
|
+
const rows = typeof db.listDueCloudDataOperations === "function" ? await db.listDueCloudDataOperations(Date.now(), 25) : await db.listCloudDataRecordsAcrossUsers("operation");
|
|
7686
7851
|
for (const row of rows) {
|
|
7687
7852
|
try {
|
|
7688
7853
|
const key = await deriveUserEncryptionKey(row.userId, ctx.masterKey);
|
|
7689
7854
|
const operation = JSON.parse(await decryptFromStorage(row.data, key));
|
|
7690
|
-
if (["completed", "failed"].includes(operation.status) && operation.updatedAt < Date.now() - 30 * 864e5) {
|
|
7691
|
-
await db.deleteCloudDataRecord(row.userId, "operation", operation.id);
|
|
7692
|
-
continue;
|
|
7693
|
-
}
|
|
7694
7855
|
if (["pending", "running"].includes(operation.status)) {
|
|
7695
7856
|
await advanceCleanup(ctx, db, row.userId, key, operation);
|
|
7696
|
-
if (++processed >= 100) break;
|
|
7697
7857
|
}
|
|
7698
7858
|
} catch {
|
|
7699
7859
|
console.warn("[amsg] A cloud cleanup operation could not be resumed");
|
|
@@ -7702,6 +7862,19 @@ async function resumeCloudDataCleanups(ctx) {
|
|
|
7702
7862
|
}
|
|
7703
7863
|
|
|
7704
7864
|
// src/server/handlers/cloud-data.js
|
|
7865
|
+
function inventorySummary(inventory) {
|
|
7866
|
+
const resources = inventory.entries.map((entry) => entry.resource);
|
|
7867
|
+
return {
|
|
7868
|
+
total: resources.length,
|
|
7869
|
+
counts: CLOUD_RESOURCE_TYPES.map((type) => ({
|
|
7870
|
+
type,
|
|
7871
|
+
count: resources.filter((resource) => resource.type === type).length,
|
|
7872
|
+
byteSize: resources.filter((resource) => resource.type === type).reduce((sum, resource) => sum + (resource.byteSize || 0), 0)
|
|
7873
|
+
})),
|
|
7874
|
+
complete: inventory.complete,
|
|
7875
|
+
gaps: inventory.gaps
|
|
7876
|
+
};
|
|
7877
|
+
}
|
|
7705
7878
|
function createCloudDataHandler(ctx) {
|
|
7706
7879
|
async function run(url, headers, body, method) {
|
|
7707
7880
|
const tenant = await ctx.tenantManager.resolveTenant(headers);
|
|
@@ -7748,17 +7921,7 @@ function createCloudDataHandler(ctx) {
|
|
|
7748
7921
|
const filter = { ownerType, ownerId, type };
|
|
7749
7922
|
if (path.endsWith("/summary")) {
|
|
7750
7923
|
const inventory = await cloudInventory(ctx, db, userId, key);
|
|
7751
|
-
|
|
7752
|
-
data = {
|
|
7753
|
-
total: resources.length,
|
|
7754
|
-
counts: CLOUD_RESOURCE_TYPES.map((type2) => ({
|
|
7755
|
-
type: type2,
|
|
7756
|
-
count: resources.filter((r) => r.type === type2).length,
|
|
7757
|
-
byteSize: resources.filter((r) => r.type === type2).reduce((sum, r) => sum + (r.byteSize || 0), 0)
|
|
7758
|
-
})),
|
|
7759
|
-
complete: inventory.complete,
|
|
7760
|
-
gaps: inventory.gaps
|
|
7761
|
-
};
|
|
7924
|
+
data = inventorySummary(inventory);
|
|
7762
7925
|
} else {
|
|
7763
7926
|
const limit = Number(params.get("limit") || 50);
|
|
7764
7927
|
if (!Number.isInteger(limit) || limit < 1 || limit > 200)
|
|
@@ -7790,6 +7953,7 @@ function createCloudDataHandler(ctx) {
|
|
|
7790
7953
|
snapshot = {
|
|
7791
7954
|
id: crypto.randomUUID(),
|
|
7792
7955
|
filter,
|
|
7956
|
+
summary: inventorySummary(inventory),
|
|
7793
7957
|
expiresAt: Date.now() + 15 * 60 * 1e3,
|
|
7794
7958
|
resources: inventory.entries.map((e) => e.resource).filter(
|
|
7795
7959
|
(r) => (!type || r.type === type) && (!ownerId || r.owner?.type === ownerType && r.owner.id === ownerId)
|
|
@@ -7806,6 +7970,7 @@ function createCloudDataHandler(ctx) {
|
|
|
7806
7970
|
}
|
|
7807
7971
|
data = {
|
|
7808
7972
|
resources: snapshot.resources.slice(offset, offset + limit),
|
|
7973
|
+
...snapshot.summary ? { summary: snapshot.summary } : {},
|
|
7809
7974
|
nextCursor: offset + limit < snapshot.resources.length ? `${snapshot.id}:${offset + limit}` : null,
|
|
7810
7975
|
complete: snapshot.complete,
|
|
7811
7976
|
gaps: snapshot.gaps
|
|
@@ -8150,7 +8315,7 @@ function createSingleUserServer(config) {
|
|
|
8150
8315
|
}
|
|
8151
8316
|
|
|
8152
8317
|
// src/server/lib/schema-version.js
|
|
8153
|
-
var SCHEMA_VERSION = "2.6.0-cloud-data.
|
|
8318
|
+
var SCHEMA_VERSION = "2.6.0-cloud-data.2";
|
|
8154
8319
|
function requireIntrospection(db) {
|
|
8155
8320
|
if (!db || typeof db.describeSchema !== "function") {
|
|
8156
8321
|
throw new Error(
|
|
@@ -8178,6 +8343,10 @@ async function getSchemaVersion(db) {
|
|
|
8178
8343
|
for (const index of SQLITE_REQUIRED_SCHEMA.indexes) {
|
|
8179
8344
|
if (!liveIndexes.has(index)) missing.push(`index:${index}`);
|
|
8180
8345
|
}
|
|
8346
|
+
const liveTriggers = new Set(live?.triggers || []);
|
|
8347
|
+
for (const name of SQLITE_REQUIRED_SCHEMA.triggers) {
|
|
8348
|
+
if (!liveTriggers.has(name)) missing.push(`trigger:${name}`);
|
|
8349
|
+
}
|
|
8181
8350
|
const ok = missing.length === 0;
|
|
8182
8351
|
return { current: ok ? SCHEMA_VERSION : null, required: SCHEMA_VERSION, ok, missing };
|
|
8183
8352
|
}
|