@zhin.js/plugin-rss 1.1.8 → 1.1.9

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
@@ -158,3 +158,7 @@ rss:
158
158
  ## 许可
159
159
 
160
160
  MIT
161
+
162
+ ### 事务化任务存储
163
+
164
+ 提醒与管理员 RSS 的任务、认领、操作回执和文章明细使用独立数据表,在一个事务中提交;原 CAS JSON 只用于首次迁移。支持 SQLite、MySQL、PostgreSQL,升级须完整重启,不能与旧版本进程混写。`unknown` 投递仍不会自动重试。迁移和字段说明见[结构化存储](../../../docs/operations/structured-storage.md)。
@@ -0,0 +1,3 @@
1
+ import { type PluginDatabaseHost } from 'zhin.js/plugin-runtime';
2
+ export declare function createAdministratorRssState(database: PluginDatabaseHost): import("zhin.js").PluginStateStore;
3
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EACiF,KAAK,kBAAkB,EAA6B,MAAM,wBAAwB,CAAC;AAsD3K,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,kBAAkB,sCAEvE"}
package/lib/storage.js ADDED
@@ -0,0 +1,56 @@
1
+ import { createRelationalStateStore, TASK_TARGET_COLUMNS, TASK_RECEIPT_COLUMNS, encodeTaskTarget, decodeTaskTarget, encodeTaskReceipts, decodeTaskReceipts, encodeStorageIdentity, decodeStorageIdentity } from 'zhin.js/plugin-runtime';
2
+ import { ADMIN_RSS_STATE_KEY } from './admin-tasks.js';
3
+ const tasks = 'admin_rss_tasks', seen = 'admin_rss_seen', items = 'admin_rss_pending_items', receipts = 'admin_rss_receipts';
4
+ const text = { type: 'text', nullable: true };
5
+ const time = { type: 'bigint', nullable: true };
6
+ const child = { id: { type: 'text', length: 80, primary: true }, task_id: { type: 'text', length: 36, nullable: false }, position: { type: 'integer', nullable: false } };
7
+ const codec = {
8
+ key: ADMIN_RSS_STATE_KEY,
9
+ header: { table: 'admin_rss_book', definition: { version: { type: 'integer', nullable: true }, revision: text } },
10
+ tables: {
11
+ [tasks]: { id: { type: 'text', length: 36, primary: true }, position: { type: 'integer', nullable: false }, ...TASK_TARGET_COLUMNS,
12
+ creation: text, version: text, url: text, state: text, created_at: time, claim_id: text, last_check_at: time, last_run_at: time, detail: text,
13
+ seen_known: { type: 'boolean', nullable: false }, quiet_timezone: text, quiet_start: text, quiet_end: text,
14
+ pending_kind: text, pending_from: time, pending_to: time, pending_count: { type: 'integer', nullable: true },
15
+ pending_capped: { type: 'boolean', nullable: true } },
16
+ [seen]: { ...child, item_key: { type: 'text', nullable: false } },
17
+ [items]: { ...child, item_key: text, title: text, link: text },
18
+ [receipts]: TASK_RECEIPT_COLUMNS,
19
+ },
20
+ encode(value) {
21
+ const book = value;
22
+ if (![1, 2].includes(book.version) || !Array.isArray(book.tasks) || !Array.isArray(book.receipts))
23
+ throw new Error('Unsupported RSS book; original state was preserved');
24
+ return { header: { version: book.version, revision: book.revision }, rows: {
25
+ [tasks]: book.tasks.map((task, position) => ({ id: task.id, position, ...encodeTaskTarget(task.target),
26
+ creation: task.creation, version: task.version, url: encodeStorageIdentity(task.url), state: task.state, created_at: task.createdAt,
27
+ claim_id: task.claimId, last_check_at: task.lastCheckAt, last_run_at: task.lastRunAt, detail: encodeStorageIdentity(task.detail),
28
+ seen_known: task.seen !== null, quiet_timezone: task.quiet?.timezone ?? null, quiet_start: task.quiet?.start ?? null, quiet_end: task.quiet?.end ?? null,
29
+ pending_kind: task.pending === undefined ? 'absent' : task.pending === null ? 'null' : 'value',
30
+ pending_from: task.pending?.from ?? null, pending_to: task.pending?.to ?? null,
31
+ pending_count: task.pending?.count ?? null, pending_capped: task.pending?.countCapped ?? null,
32
+ })),
33
+ [seen]: book.tasks.flatMap((task) => (task.seen ?? []).map((key, position) => ({ id: `${task.id}:${position}`, task_id: task.id, position, item_key: encodeStorageIdentity(key) }))),
34
+ [items]: book.tasks.flatMap((task) => (task.pending?.items ?? []).map((item, position) => ({ id: `${task.id}:${position}`, task_id: task.id, position, item_key: encodeStorageIdentity(item.key), title: encodeStorageIdentity(item.title), link: encodeStorageIdentity(item.link) }))),
35
+ [receipts]: encodeTaskReceipts(book.receipts),
36
+ } };
37
+ },
38
+ decode(header, rows) {
39
+ return { version: Number(header.version), revision: String(header.revision), receipts: decodeTaskReceipts(rows[receipts]),
40
+ tasks: rows[tasks].map(row => ({ id: String(row.id), target: decodeTaskTarget(row), creation: String(row.creation), version: String(row.version),
41
+ url: decodeStorageIdentity(row.url), state: String(row.state), createdAt: Number(row.created_at), claimId: row.claim_id == null ? null : String(row.claim_id),
42
+ lastCheckAt: row.last_check_at == null ? null : Number(row.last_check_at), lastRunAt: row.last_run_at == null ? null : Number(row.last_run_at), detail: decodeStorageIdentity(row.detail),
43
+ seen: row.seen_known ? rows[seen].filter(item => item.task_id === row.id).map(item => decodeStorageIdentity(item.item_key)) : null,
44
+ ...(row.quiet_timezone == null ? {} : { quiet: { timezone: String(row.quiet_timezone), start: String(row.quiet_start), end: String(row.quiet_end) } }),
45
+ ...(row.pending_kind === 'absent' ? {} : { pending: row.pending_kind === 'null' ? null : {
46
+ from: Number(row.pending_from), to: Number(row.pending_to), count: Number(row.pending_count), countCapped: Boolean(row.pending_capped),
47
+ items: rows[items].filter(item => item.task_id === row.id).map(item => ({ key: decodeStorageIdentity(item.item_key), title: decodeStorageIdentity(item.title), link: decodeStorageIdentity(item.link) })),
48
+ } }),
49
+ })),
50
+ };
51
+ },
52
+ };
53
+ export function createAdministratorRssState(database) {
54
+ return createRelationalStateStore(database, [codec]);
55
+ }
56
+ //# 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-rss",
3
- "version": "1.1.8",
3
+ "version": "1.1.9",
4
4
  "description": "RSS/Atom feed subscription plugin for Zhin.js (Plugin Runtime)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -41,20 +41,20 @@
41
41
  "license": "MIT",
42
42
  "dependencies": {
43
43
  "rss-parser": "^3.13.0",
44
- "@zhin.js/tool": "1.1.8",
45
- "@zhin.js/im-contract": "1.1.5",
46
- "@zhin.js/skill": "1.1.8"
44
+ "@zhin.js/im-contract": "1.1.6",
45
+ "@zhin.js/tool": "1.1.9",
46
+ "@zhin.js/skill": "1.1.9"
47
47
  },
48
48
  "devDependencies": {
49
49
  "typescript": "^6.0.3",
50
50
  "vitest": "^4.1.10",
51
51
  "zod": "^4.4.3",
52
- "zhin.js": "1.1.13"
52
+ "zhin.js": "1.1.15"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "zod": "^4.0.0",
56
- "@zhin.js/command": "^1.1.9",
57
- "zhin.js": "^1.1.13"
56
+ "@zhin.js/command": "^1.1.10",
57
+ "zhin.js": "^1.1.15"
58
58
  },
59
59
  "peerDependenciesMeta": {
60
60
  "@zhin.js/command": {
package/plugin.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { definePlugin, databaseHostToken, outboundHostToken, scheduleHostToken, } from 'zhin.js';
2
+ import { definePlugin, logger, databaseHostToken, outboundHostToken, scheduleHostToken, } from 'zhin.js';
3
3
  import { pluginTaskProviderToken } from 'zhin.js/plugin-runtime';
4
+ import { createAdministratorRssState } from "./lib/storage.js";
4
5
  import { AdministratorRssTasks } from "./lib/admin-tasks.js";
5
6
  import { resolveRssConfig } from "./lib/feed.js";
6
7
  import { createInMemoryRssDb, RSS_SEEN_TABLE, RSS_SUBS_TABLE, } from "./lib/db-store.js";
@@ -69,14 +70,17 @@ export default definePlugin({
69
70
  // Keep optional in-memory/chat-only installations working. Administrator tasks
70
71
  // must have the real durable state port, exact outbound and an active scheduler.
71
72
  let administrator;
72
- if (database && outbound?.sendTarget && context.resources.has(scheduleHostToken)) {
73
+ const structuredTasks = database && ['sqlite', 'mysql', 'pg'].includes(database.dialect);
74
+ if (database && !structuredTasks)
75
+ logger.warn('RSS 管理员任务持久化仅支持 SQLite、MySQL、PostgreSQL;聊天订阅保留现有存储方式');
76
+ if (structuredTasks && database && outbound?.sendTarget && context.resources.has(scheduleHostToken)) {
73
77
  let state;
74
78
  try {
75
79
  state = database.state;
76
80
  }
77
81
  catch { /* No persistent state port: no administrator provider. */ }
78
82
  if (state) {
79
- administrator = new AdministratorRssTasks(state, outbound, config);
83
+ administrator = new AdministratorRssTasks(createAdministratorRssState(database), outbound, config);
80
84
  context.resources.provide(pluginTaskProviderToken, administrator);
81
85
  context.lifecycle.add(() => administrator?.stop());
82
86
  }
package/src/storage.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { createRelationalStateStore, TASK_TARGET_COLUMNS, TASK_RECEIPT_COLUMNS, encodeTaskTarget, decodeTaskTarget,
2
+ encodeTaskReceipts, decodeTaskReceipts, encodeStorageIdentity, decodeStorageIdentity, type PluginDatabaseHost, type RelationalStateCodec } from 'zhin.js/plugin-runtime';
3
+ import { ADMIN_RSS_STATE_KEY, type AdministratorRssBook } from './admin-tasks.js';
4
+
5
+ const tasks = 'admin_rss_tasks', seen = 'admin_rss_seen', items = 'admin_rss_pending_items', receipts = 'admin_rss_receipts';
6
+ const text = { type: 'text', nullable: true } as const;
7
+ const time = { type: 'bigint', nullable: true } as const;
8
+ const child = { id: { type: 'text', length: 80, primary: true }, task_id: { type: 'text', length: 36, nullable: false }, position: { type: 'integer', nullable: false } };
9
+
10
+ const codec: RelationalStateCodec = {
11
+ key: ADMIN_RSS_STATE_KEY,
12
+ header: { table: 'admin_rss_book', definition: { version: { type: 'integer', nullable: true }, revision: text } },
13
+ tables: {
14
+ [tasks]: { id: { type: 'text', length: 36, primary: true }, position: { type: 'integer', nullable: false }, ...TASK_TARGET_COLUMNS,
15
+ creation: text, version: text, url: text, state: text, created_at: time, claim_id: text, last_check_at: time, last_run_at: time, detail: text,
16
+ seen_known: { type: 'boolean', nullable: false }, quiet_timezone: text, quiet_start: text, quiet_end: text,
17
+ pending_kind: text, pending_from: time, pending_to: time, pending_count: { type: 'integer', nullable: true },
18
+ pending_capped: { type: 'boolean', nullable: true } },
19
+ [seen]: { ...child, item_key: { type: 'text', nullable: false } },
20
+ [items]: { ...child, item_key: text, title: text, link: text },
21
+ [receipts]: TASK_RECEIPT_COLUMNS,
22
+ },
23
+ encode(value) {
24
+ const book = value as AdministratorRssBook;
25
+ if (![1, 2].includes(book.version) || !Array.isArray(book.tasks) || !Array.isArray(book.receipts)) throw new Error('Unsupported RSS book; original state was preserved');
26
+ return { header: { version: book.version, revision: book.revision }, rows: {
27
+ [tasks]: book.tasks.map((task, position) => ({ id: task.id, position, ...encodeTaskTarget(task.target),
28
+ creation: task.creation, version: task.version, url: encodeStorageIdentity(task.url), state: task.state, created_at: task.createdAt,
29
+ claim_id: task.claimId, last_check_at: task.lastCheckAt, last_run_at: task.lastRunAt, detail: encodeStorageIdentity(task.detail),
30
+ seen_known: task.seen !== null, quiet_timezone: task.quiet?.timezone ?? null, quiet_start: task.quiet?.start ?? null, quiet_end: task.quiet?.end ?? null,
31
+ pending_kind: task.pending === undefined ? 'absent' : task.pending === null ? 'null' : 'value',
32
+ pending_from: task.pending?.from ?? null, pending_to: task.pending?.to ?? null,
33
+ pending_count: task.pending?.count ?? null, pending_capped: task.pending?.countCapped ?? null,
34
+ })),
35
+ [seen]: book.tasks.flatMap((task: AdministratorRssBook['tasks'][number]) => (task.seen ?? []).map((key, position) => ({ id: `${task.id}:${position}`, task_id: task.id, position, item_key: encodeStorageIdentity(key) }))),
36
+ [items]: book.tasks.flatMap((task: AdministratorRssBook['tasks'][number]) => (task.pending?.items ?? []).map((item, position) => ({ id: `${task.id}:${position}`, task_id: task.id, position, item_key: encodeStorageIdentity(item.key), title: encodeStorageIdentity(item.title), link: encodeStorageIdentity(item.link) }))),
37
+ [receipts]: encodeTaskReceipts(book.receipts),
38
+ } };
39
+ },
40
+ decode(header, rows) {
41
+ return { version: Number(header.version), revision: String(header.revision), receipts: decodeTaskReceipts(rows[receipts]!),
42
+ tasks: rows[tasks]!.map(row => ({ id: String(row.id), target: decodeTaskTarget(row), creation: String(row.creation), version: String(row.version),
43
+ url: decodeStorageIdentity(row.url), state: String(row.state), createdAt: Number(row.created_at), claimId: row.claim_id == null ? null : String(row.claim_id),
44
+ lastCheckAt: row.last_check_at == null ? null : Number(row.last_check_at), lastRunAt: row.last_run_at == null ? null : Number(row.last_run_at), detail: decodeStorageIdentity(row.detail),
45
+ seen: row.seen_known ? rows[seen]!.filter(item => item.task_id === row.id).map(item => decodeStorageIdentity(item.item_key)) : null,
46
+ ...(row.quiet_timezone == null ? {} : { quiet: { timezone: String(row.quiet_timezone), start: String(row.quiet_start), end: String(row.quiet_end) } }),
47
+ ...(row.pending_kind === 'absent' ? {} : { pending: row.pending_kind === 'null' ? null : {
48
+ from: Number(row.pending_from), to: Number(row.pending_to), count: Number(row.pending_count), countCapped: Boolean(row.pending_capped),
49
+ items: rows[items]!.filter(item => item.task_id === row.id).map(item => ({ key: decodeStorageIdentity(item.item_key), title: decodeStorageIdentity(item.title), link: decodeStorageIdentity(item.link) })),
50
+ } }),
51
+ })),
52
+ };
53
+ },
54
+ };
55
+
56
+ export function createAdministratorRssState(database: PluginDatabaseHost) {
57
+ return createRelationalStateStore(database, [codec]);
58
+ }