@zhin.js/plugin-reminder 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -110,3 +110,7 @@ schedules/poll/index.ts # 正式 Schedule Feature 入口
110
110
  | `reminder_cancel` | 按 id 取消;需批准 |
111
111
 
112
112
  与原生 `schedule_add` 的区别:本插件到点**向会话发送提醒文案**;`schedule_*` 到点**再次唤醒 Agent**(可带 cron / 工作日等)。绝对时间仍须带 `Z` 或 `±hh:mm`。
113
+
114
+ ### 事务化任务存储
115
+
116
+ 提醒与管理员 RSS 的任务、认领、操作回执和文章明细使用独立数据表,在一个事务中提交;原 CAS JSON 只用于首次迁移。支持 SQLite、MySQL、PostgreSQL,升级须完整重启,不能与旧版本进程混写。`unknown` 投递仍不会自动重试。迁移和字段说明见[结构化存储](../../../docs/operations/structured-storage.md)。
@@ -0,0 +1,4 @@
1
+ import { type PluginDatabaseHost } from 'zhin.js/plugin-runtime';
2
+ /** Both pools keep their existing CAS API but persist independent rows atomically. */
3
+ export declare function createReminderState(database: PluginDatabaseHost): import("zhin.js").PluginStateStore;
4
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EAA6B,MAAM,wBAAwB,CAAC;AA8CrF,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,kBAAkB,sCAE/D"}
package/lib/storage.js ADDED
@@ -0,0 +1,49 @@
1
+ import { createRelationalStateStore, TASK_TARGET_COLUMNS, TASK_RECEIPT_COLUMNS, encodeTaskTarget, decodeTaskTarget, encodeTaskReceipts, decodeTaskReceipts, encodeStorageIdentity, decodeStorageIdentity } from 'zhin.js/plugin-runtime';
2
+ import { REMINDER_STATE_KEY } from './service.js';
3
+ import { ADMIN_REMINDER_STATE_KEY } from './admin-tasks.js';
4
+ const taskColumns = {
5
+ id: { type: 'text', length: 36, primary: true }, position: { type: 'integer', nullable: false },
6
+ ...TASK_TARGET_COLUMNS, sender_id: { type: 'text', nullable: true }, creation: { type: 'text', nullable: true },
7
+ content: { type: 'text', nullable: false }, state: { type: 'text', length: 24, nullable: false },
8
+ created_at: { type: 'bigint', nullable: false }, next_run_at: { type: 'bigint', nullable: false },
9
+ interval_ms: { type: 'bigint', nullable: true }, claim_id: { type: 'text', nullable: true },
10
+ last_run_at: { type: 'bigint', nullable: true }, detail: { type: 'text', nullable: false },
11
+ };
12
+ function codec(admin) {
13
+ const table = admin ? 'admin_reminder_tasks' : 'reminder_tasks';
14
+ const receipts = 'admin_reminder_receipts';
15
+ return {
16
+ key: admin ? ADMIN_REMINDER_STATE_KEY : REMINDER_STATE_KEY,
17
+ header: { table: `${table}_book`, definition: {
18
+ version: { type: 'integer', nullable: true }, revision: { type: 'text', nullable: true },
19
+ } },
20
+ tables: { [table]: taskColumns, ...(admin ? { [receipts]: TASK_RECEIPT_COLUMNS } : {}) },
21
+ encode(value) {
22
+ const book = value;
23
+ if (book.version !== 1 || !Array.isArray(book.tasks))
24
+ throw new Error('Unsupported reminder book; original state was preserved');
25
+ return { header: { version: book.version, revision: 'revision' in book ? book.revision : null }, rows: {
26
+ [table]: book.tasks.map((task, position) => ({ id: task.id, position, ...encodeTaskTarget(task.target),
27
+ sender_id: 'senderId' in task ? encodeStorageIdentity(task.senderId) : null,
28
+ creation: 'creation' in task ? task.creation : null, content: encodeStorageIdentity(task.text),
29
+ state: 'status' in task ? task.status : task.state, created_at: task.createdAt, next_run_at: task.nextRunAt,
30
+ interval_ms: task.intervalMs, claim_id: task.claimId, last_run_at: task.lastRunAt, detail: encodeStorageIdentity(task.detail) })),
31
+ ...(admin ? { [receipts]: encodeTaskReceipts(book.receipts) } : {}),
32
+ } };
33
+ },
34
+ decode(header, rows) {
35
+ return { version: Number(header.version), ...(admin ? { revision: String(header.revision) } : {}),
36
+ tasks: rows[table].map(row => ({ id: String(row.id), target: decodeTaskTarget(row), text: decodeStorageIdentity(row.content),
37
+ createdAt: Number(row.created_at), nextRunAt: Number(row.next_run_at), intervalMs: row.interval_ms == null ? null : Number(row.interval_ms),
38
+ claimId: row.claim_id == null ? null : String(row.claim_id), lastRunAt: row.last_run_at == null ? null : Number(row.last_run_at), detail: decodeStorageIdentity(row.detail),
39
+ ...(admin ? { creation: String(row.creation), state: String(row.state) } : { senderId: decodeStorageIdentity(row.sender_id), status: String(row.state) }),
40
+ })), ...(admin ? { receipts: decodeTaskReceipts(rows[receipts]) } : {}),
41
+ };
42
+ },
43
+ };
44
+ }
45
+ /** Both pools keep their existing CAS API but persist independent rows atomically. */
46
+ export function createReminderState(database) {
47
+ return createRelationalStateStore(database, [codec(false), codec(true)]);
48
+ }
49
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-reminder",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Persistent personal reminders for Zhin Plugin Runtime",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -22,15 +22,15 @@
22
22
  "schema.json"
23
23
  ],
24
24
  "dependencies": {
25
- "@zhin.js/im-contract": "1.1.5",
26
- "@zhin.js/skill": "1.1.8",
27
- "@zhin.js/tool": "1.1.8"
25
+ "@zhin.js/im-contract": "1.1.6",
26
+ "@zhin.js/skill": "1.1.9",
27
+ "@zhin.js/tool": "1.1.9"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "zod": "^4.0.0",
31
- "zhin.js": "^1.1.13",
32
- "@zhin.js/command": "^1.1.9",
33
- "@zhin.js/schedule-feature": "^1.1.7"
31
+ "zhin.js": "^1.1.15",
32
+ "@zhin.js/command": "^1.1.10",
33
+ "@zhin.js/schedule-feature": "^1.1.8"
34
34
  },
35
35
  "peerDependenciesMeta": {
36
36
  "@zhin.js/command": {
@@ -47,8 +47,8 @@
47
47
  "typescript": "^6.0.3",
48
48
  "vitest": "^4.1.10",
49
49
  "zod": "^4.4.3",
50
- "zhin.js": "1.1.13",
51
- "@zhin.js/schedule-feature": "1.1.7"
50
+ "zhin.js": "1.1.15",
51
+ "@zhin.js/schedule-feature": "1.1.8"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22.19.0"
package/plugin.js CHANGED
@@ -3,6 +3,7 @@ import { databaseHostToken, definePlugin, outboundHostToken } from 'zhin.js';
3
3
  import { pluginVerificationToken, pluginTaskProviderToken } from 'zhin.js/plugin-runtime';
4
4
  import { randomUUID } from 'node:crypto';
5
5
  import { assertReminderTimezone } from "./lib/config.js";
6
+ import { createReminderState } from "./lib/storage.js";
6
7
  import { ReminderService } from "./lib/service.js";
7
8
  import { reminderServiceToken } from "./lib/runtime.js";
8
9
  export default definePlugin({
@@ -12,11 +13,13 @@ export default definePlugin({
12
13
  setup(context) {
13
14
  const config = context.config.get();
14
15
  assertReminderTimezone(config.timezone);
15
- const state = context.resources.use(databaseHostToken).state;
16
+ const database = context.resources.use(databaseHostToken);
17
+ const state = database.state;
18
+ const taskState = createReminderState(database);
16
19
  const outbound = context.resources.use(outboundHostToken);
17
20
  if (!outbound.sendTarget)
18
21
  throw new Error('提醒需要支持精确 DeliveryTarget 的 Outbound Host');
19
- const service = new ReminderService(state, outbound, config);
22
+ const service = new ReminderService(taskState, outbound, config);
20
23
  context.resources.provide(reminderServiceToken, service);
21
24
  context.resources.provide(pluginTaskProviderToken, service.administratorTasks);
22
25
  context.resources.provide(pluginVerificationToken, {
package/src/storage.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { createRelationalStateStore, TASK_TARGET_COLUMNS, TASK_RECEIPT_COLUMNS, encodeTaskTarget, decodeTaskTarget,
2
+ encodeTaskReceipts, decodeTaskReceipts, encodeStorageIdentity, decodeStorageIdentity,
3
+ type PluginDatabaseHost, type RelationalStateCodec } from 'zhin.js/plugin-runtime';
4
+ import { REMINDER_STATE_KEY, type ReminderBook } from './service.js';
5
+ import { ADMIN_REMINDER_STATE_KEY, type AdminReminderBook } from './admin-tasks.js';
6
+
7
+ const taskColumns = {
8
+ id: { type: 'text', length: 36, primary: true }, position: { type: 'integer', nullable: false },
9
+ ...TASK_TARGET_COLUMNS, sender_id: { type: 'text', nullable: true }, creation: { type: 'text', nullable: true },
10
+ content: { type: 'text', nullable: false }, state: { type: 'text', length: 24, nullable: false },
11
+ created_at: { type: 'bigint', nullable: false }, next_run_at: { type: 'bigint', nullable: false },
12
+ interval_ms: { type: 'bigint', nullable: true }, claim_id: { type: 'text', nullable: true },
13
+ last_run_at: { type: 'bigint', nullable: true }, detail: { type: 'text', nullable: false },
14
+ };
15
+
16
+ function codec(admin: boolean): RelationalStateCodec {
17
+ const table = admin ? 'admin_reminder_tasks' : 'reminder_tasks';
18
+ const receipts = 'admin_reminder_receipts';
19
+ return {
20
+ key: admin ? ADMIN_REMINDER_STATE_KEY : REMINDER_STATE_KEY,
21
+ header: { table: `${table}_book`, definition: {
22
+ version: { type: 'integer', nullable: true }, revision: { type: 'text', nullable: true },
23
+ } },
24
+ tables: { [table]: taskColumns, ...(admin ? { [receipts]: TASK_RECEIPT_COLUMNS } : {}) },
25
+ encode(value) {
26
+ const book = value as ReminderBook | AdminReminderBook;
27
+ if (book.version !== 1 || !Array.isArray(book.tasks)) throw new Error('Unsupported reminder book; original state was preserved');
28
+ return { header: { version: book.version, revision: 'revision' in book ? book.revision : null }, rows: {
29
+ [table]: book.tasks.map((task, position) => ({ id: task.id, position, ...encodeTaskTarget(task.target),
30
+ sender_id: 'senderId' in task ? encodeStorageIdentity(task.senderId) : null,
31
+ creation: 'creation' in task ? task.creation : null, content: encodeStorageIdentity(task.text),
32
+ state: 'status' in task ? task.status : task.state, created_at: task.createdAt, next_run_at: task.nextRunAt,
33
+ interval_ms: task.intervalMs, claim_id: task.claimId, last_run_at: task.lastRunAt, detail: encodeStorageIdentity(task.detail) })),
34
+ ...(admin ? { [receipts]: encodeTaskReceipts((book as AdminReminderBook).receipts) } : {}),
35
+ } };
36
+ },
37
+ decode(header, rows) {
38
+ return { version: Number(header.version), ...(admin ? { revision: String(header.revision) } : {}),
39
+ tasks: rows[table]!.map(row => ({ id: String(row.id), target: decodeTaskTarget(row), text: decodeStorageIdentity(row.content),
40
+ createdAt: Number(row.created_at), nextRunAt: Number(row.next_run_at), intervalMs: row.interval_ms == null ? null : Number(row.interval_ms),
41
+ claimId: row.claim_id == null ? null : String(row.claim_id), lastRunAt: row.last_run_at == null ? null : Number(row.last_run_at), detail: decodeStorageIdentity(row.detail),
42
+ ...(admin ? { creation: String(row.creation), state: String(row.state) } : { senderId: decodeStorageIdentity(row.sender_id), status: String(row.state) }),
43
+ })), ...(admin ? { receipts: decodeTaskReceipts(rows[receipts]!) } : {}),
44
+ };
45
+ },
46
+ };
47
+ }
48
+
49
+ /** Both pools keep their existing CAS API but persist independent rows atomically. */
50
+ export function createReminderState(database: PluginDatabaseHost) {
51
+ return createRelationalStateStore(database, [codec(false), codec(true)]);
52
+ }