Files
inkreach-teable-cli/dist/commands/subscription.js
T
yeuimu 1df830adc4 chore: cli subscription command registration + env and deploy docs
- inkreach-cli: subscription command in bin, built dist
- env.development: public origin, plugin service registry, cache
  provider=redis, cold retention days
- skill docs: subscription quick reference, deploy and ops notes
2026-08-22 02:44:16 +00:00

316 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.subscriptionCommand = void 0;
/**
* `inkreach-teable subscription` — 订阅中心全配置项 CRUD + 事件查看 + 立即发送。
*
* 覆盖订阅中心全部配置:name/table/watchCreated/watchUpdated/fieldIds/recordIds/
* displayFieldId/attachCsv/filter(原生 IFilter JSON)/triggerOnEnter(进入条件语义)/
* sendMode/recipients/notifyInApp/scheduleType/scheduleTime/scheduleWeekday/
* scheduleInterval/isActive。
*
* filter 示例(与表格视图筛选同构):
* --filter '{"conjunction":"and","filterSet":[{"fieldId":"fldX","operator":"isExactly","value":"已完成"}]}'
* 日期筛选值结构:{"mode":"exactDate","exactDate":"2026-08-21","timeZone":"Asia/Shanghai"}
* 用户筛选值:["usrXXX"]('Me' 由后端解析为订阅创建者)。
*/
const commander_1 = require("commander");
const ctx_1 = require("../lib/ctx");
const output_1 = require("../lib/output");
function parseJsonFlag(raw, flagName) {
if (raw === undefined)
return undefined;
try {
return JSON.parse(raw);
}
catch (err) {
throw new Error(`--${flagName} is not valid JSON: ${err.message}\n got: ${raw}`);
}
}
/** 'a@x.com,b@y.com' | '["a@x.com"]' → string[] */
function parseRecipients(raw) {
if (raw === undefined)
return undefined;
const trimmed = raw.trim();
if (trimmed.startsWith('[')) {
return parseJsonFlag(raw, 'recipients');
}
return trimmed
.split(',')
.map((s) => s.trim())
.filter(Boolean);
}
/** 从 CLI 选项构建 RO body;create 时 name/tableId 必填。 */
function buildRo(opts, forCreate) {
const body = {};
if (opts.name !== undefined)
body.name = opts.name;
if (opts.tableId !== undefined)
body.tableId = opts.tableId;
if (forCreate) {
if (!body.name)
throw new Error('--name is required for create');
if (!body.tableId)
throw new Error('--table-id is required for create');
}
if (opts.watchCreated !== undefined)
body.watchCreated = opts.watchCreated;
if (opts.watchUpdated !== undefined)
body.watchUpdated = opts.watchUpdated;
const fieldIds = parseJsonFlag(opts.fieldIds, 'field-ids');
if (fieldIds !== undefined)
body.fieldIds = fieldIds;
const recordIds = parseJsonFlag(opts.recordIds, 'record-ids');
if (recordIds !== undefined)
body.recordIds = recordIds;
if (opts.displayFieldId !== undefined) {
body.displayFieldId = opts.displayFieldId === '' ? null : opts.displayFieldId;
}
if (opts.attachCsv !== undefined)
body.attachCsv = opts.attachCsv;
const filter = parseJsonFlag(opts.filter, 'filter');
if (filter !== undefined)
body.filter = filter;
if (opts.triggerOnEnter !== undefined)
body.triggerOnEnter = opts.triggerOnEnter;
if (opts.sendMode !== undefined) {
if (opts.sendMode !== 'digest' && opts.sendMode !== 'instant') {
throw new Error(`--send-mode must be digest|instant, got: ${opts.sendMode}`);
}
body.sendMode = opts.sendMode;
}
const recipients = parseRecipients(opts.recipients);
if (recipients !== undefined)
body.recipients = recipients;
if (opts.notifyInApp !== undefined)
body.notifyInApp = opts.notifyInApp;
if (opts.scheduleType !== undefined) {
const t = opts.scheduleType;
if (!['daily', 'weekly', 'hours', 'minutes'].includes(t)) {
throw new Error(`--schedule-type must be daily|weekly|hours|minutes, got: ${t}`);
}
body.scheduleType = t;
}
if (opts.scheduleTime !== undefined)
body.scheduleTime = opts.scheduleTime;
if (opts.scheduleWeekday !== undefined) {
const n = Number(opts.scheduleWeekday);
if (!Number.isInteger(n) || n < 0 || n > 6) {
throw new Error(`--schedule-weekday must be 0-6 (0=Sunday), got: ${opts.scheduleWeekday}`);
}
body.scheduleWeekday = n;
}
if (opts.scheduleInterval !== undefined) {
const n = Number(opts.scheduleInterval);
if (!Number.isInteger(n) || n < 1) {
throw new Error(`--schedule-interval must be a positive integer, got: ${opts.scheduleInterval}`);
}
body.scheduleInterval = n;
}
if (opts.isActive !== undefined)
body.isActive = opts.isActive;
return body;
}
function addConfigOptions(cmd) {
return cmd
.option('--base-id <id>', 'Base id (overrides default)')
.option('--name <name>', 'Subscription name')
.option('--table-id <id>', 'Watched table id (tblXXX)')
.option('--no-watch-created', 'Disable watching record creation (default: on)')
.option('--no-watch-updated', 'Disable watching record updates (default: on)')
.option('--field-ids <json>', 'Watched field ids as JSON array, e.g. \'["fldA","fldB"]\' (omit = all fields)')
.option('--record-ids <json>', 'Record-level subscription: tracked record ids as JSON array (omit = all records)')
.option('--display-field-id <id>', 'Field shown as record title in emails ("" = primary field)')
.option('--attach-csv', 'Attach CSV of changes to digest emails (needs export permission)')
.option('--no-attach-csv', 'Do not attach CSV')
.option('--filter <json>', 'Native IFilter JSON (same shape as view filters); null/omit = all records')
.option('--trigger-on-enter', 'Only notify on the transition INTO the filter condition (requires --filter)')
.option('--no-trigger-on-enter', 'Notify on every matching change (state semantics)')
.option('--send-mode <mode>', 'digest | instant')
.option('--recipients <list>', 'Recipient emails: comma-separated or JSON array (default = creator)')
.option('--notify-in-app', 'Also send in-app notifications (default)')
.option('--no-notify-in-app', 'Disable in-app notifications')
.option('--schedule-type <type>', 'daily | weekly | hours | minutes')
.option('--schedule-time <hh:mm>', 'Send time for daily/weekly, e.g. 09:00')
.option('--schedule-weekday <n>', '0-6 (0=Sunday), for weekly')
.option('--schedule-interval <n>', 'Interval for hours/minutes')
.option('--active', 'Subscription active (default)')
.option('--inactive', 'Pause the subscription');
}
function sendModeBadge(s) {
return s.sendMode === 'instant' ? 'instant' : 'digest';
}
function subscriptionCommand() {
const cmd = new commander_1.Command('subscription');
cmd.description('Manage the subscription center (record subscriptions) — full config support');
cmd
.command('list')
.description('List subscriptions in the base')
.option('--table-id <id>', 'Filter by table')
.option('--base-id <id>', 'Base id (overrides default)')
.action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
const list = await ctx.client.listSubscriptions(baseId, opts.tableId);
if (ctx.json)
(0, output_1.printJson)(list);
else {
if (!list.length) {
process.stdout.write('No subscriptions found.\n');
return;
}
(0, output_1.printTable)([
{ key: 'id', header: 'ID' },
{ key: 'name', header: 'NAME' },
{ key: 'tableId', header: 'TABLE' },
{ key: 'mode', header: 'MODE' },
{ key: 'onEnter', header: 'ON-ENTER' },
{ key: 'schedule', header: 'SCHEDULE' },
{ key: 'pending', header: 'PENDING' },
{ key: 'active', header: 'ACTIVE' },
], list.map((s) => ({
id: s.id,
name: (0, output_1.truncate)(s.name, 24),
tableId: s.tableId,
mode: sendModeBadge(s),
onEnter: s.triggerOnEnter ? '✓' : '',
schedule: s.sendMode === 'instant' ? '-' : s.scheduleType,
pending: s.pendingCount ?? 0,
active: s.isActive ? '✓' : '',
})));
process.stdout.write(`\n${list.length} subscription(s). Use 'subscription get <id>' for details.\n`);
}
}));
const getCmd = cmd.command('get <subscriptionId>').description('Show full subscription config');
getCmd
.option('--base-id <id>', 'Base id (overrides default)')
.action((0, ctx_1.run)(async (ctx, args, o) => {
const id = args[0];
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
const list = await ctx.client.listSubscriptions(baseId);
const sub = list.find((s) => s.id === id);
if (!sub)
throw new Error(`Subscription ${id} not found in base ${baseId}`);
if (ctx.json)
(0, output_1.printJson)(sub);
else {
(0, output_1.printKv)([
['id', sub.id],
['name', sub.name],
['tableId', sub.tableId],
['watchCreated', String(sub.watchCreated)],
['watchUpdated', String(sub.watchUpdated)],
['fieldIds', sub.fieldIds?.length ? sub.fieldIds.join(', ') : '(all fields)'],
[
'recordIds',
sub.recordIds?.length ? `${sub.recordIds.length} record(s)` : '(all records)',
],
['displayFieldId', sub.displayFieldId ?? '(primary)'],
['attachCsv', String(sub.attachCsv)],
['filter', sub.filter ? JSON.stringify(sub.filter) : '(none)'],
['triggerOnEnter', String(sub.triggerOnEnter)],
['sendMode', sendModeBadge(sub)],
['recipients', (sub.recipients ?? []).join(', ')],
['notifyInApp', String(sub.notifyInApp)],
['scheduleType', sub.scheduleType],
['scheduleTime', sub.scheduleTime ?? ''],
['scheduleWeekday', sub.scheduleWeekday != null ? String(sub.scheduleWeekday) : ''],
['scheduleInterval', sub.scheduleInterval != null ? String(sub.scheduleInterval) : ''],
['isActive', String(sub.isActive)],
['pendingCount', String(sub.pendingCount ?? 0)],
['lastSentTime', sub.lastSentTime ?? ''],
]);
}
}));
const createCmd = cmd
.command('create')
.description('Create a subscription (--name and --table-id required)');
addConfigOptions(createCmd).action((0, ctx_1.run)(async (ctx, _args, o) => {
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
const body = buildRo(opts, true);
const vo = await ctx.client.createSubscription(baseId, body);
if (ctx.json)
(0, output_1.printJson)(vo);
else
(0, output_1.printKv)([['id', vo.id], ['name', vo.name], ['created', '✓']]);
}));
const updateCmd = cmd
.command('update <subscriptionId>')
.description('Update a subscription (only provided options change)');
addConfigOptions(updateCmd).action((0, ctx_1.run)(async (ctx, args, o) => {
const id = args[0];
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
const body = buildRo(opts, false);
if (Object.keys(body).length === 0) {
throw new Error('No options provided — nothing to update');
}
const vo = await ctx.client.updateSubscription(baseId, id, body);
if (ctx.json)
(0, output_1.printJson)(vo);
else
(0, output_1.printKv)([['id', vo.id], ['updated', '✓']]);
}));
cmd
.command('delete <subscriptionId>')
.description('Delete a subscription')
.option('--base-id <id>', 'Base id (overrides default)')
.action((0, ctx_1.run)(async (ctx, args, o) => {
const id = args[0];
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
await ctx.client.deleteSubscription(baseId, id);
if (ctx.json)
(0, output_1.printJson)({ id });
else
process.stdout.write(`Deleted ${id}\n`);
}));
cmd
.command('events <subscriptionId>')
.description('List captured events (pending + sent) of a subscription')
.option('--base-id <id>', 'Base id (overrides default)')
.action((0, ctx_1.run)(async (ctx, args, o) => {
const id = args[0];
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
const events = (await ctx.client.getSubscriptionEvents(baseId, id));
if (ctx.json)
(0, output_1.printJson)(events);
else {
if (!events.length) {
process.stdout.write('No events captured.\n');
return;
}
(0, output_1.printTable)([
{ key: 'recordId', header: 'RECORD' },
{ key: 'changeType', header: 'CHANGE' },
{ key: 'createdTime', header: 'CAPTURED' },
{ key: 'sentTime', header: 'SENT' },
], events.map((e) => ({
recordId: e.recordId,
changeType: e.changeType,
createdTime: e.createdTime,
sentTime: e.sentTime ?? '(pending)',
})));
}
}));
cmd
.command('send <subscriptionId>')
.description('Send pending digest events immediately (flush now)')
.option('--base-id <id>', 'Base id (overrides default)')
.action((0, ctx_1.run)(async (ctx, args, o) => {
const id = args[0];
const opts = o;
const baseId = (0, ctx_1.requireBaseId)(ctx, opts.baseId);
const result = await ctx.client.sendSubscriptionNow(baseId, id);
if (ctx.json)
(0, output_1.printJson)(result ?? { id });
else
(0, output_1.printKv)([['id', id], ['sent', '✓']]);
}));
return cmd;
}
exports.subscriptionCommand = subscriptionCommand;