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
This commit is contained in:
@@ -53,8 +53,15 @@ Respond in the user's language. Keep answers concise and action-oriented.
|
||||
| Manual Trigger | Test by firing button-click/webhook | `fire button-click`, `fire webhook` | [automation-guide.md § Testing](guides/automation-guide.md#testing--triggering) |
|
||||
| SQL | Read-only analytics, JOINs, aggregation | `sql <sql>` | [cli-reference.md § sql](guides/cli-reference.md#sql) |
|
||||
| Plugins | Install/manage dynamic plugins, developer whitelist, CSP | `plugin list/install/uninstall`, `plugin dev allow/revoke` | [cli-reference.md § plugin](guides/cli-reference.md#plugin) |
|
||||
| Subscription Center | Record subscriptions (digest/instant email) | `subscription list/get/create/update/delete/events/send` | — (see below) |
|
||||
| Dev/Deploy | Build, restart services, known gotchas | (systemd / pnpm build) | [development.md](guides/development.md) |
|
||||
|
||||
**Subscription quick reference** (full config via `subscription create/update`):
|
||||
`--name --table-id --no-watch-created --no-watch-updated --field-ids '<json>' --record-ids '<json>' --display-field-id --attach-csv/--no-attach-csv --filter '<IFilter json>' --trigger-on-enter --send-mode digest|instant --recipients '<a@x.com,b@y.com>' --no-notify-in-app --schedule-type daily|weekly|hours|minutes --schedule-time HH:mm --schedule-weekday 0-6 --schedule-interval n --inactive`
|
||||
- `--filter` uses the SAME IFilter shape as view filters; date values are `{"mode":"exactDate","exactDate":"2026-08-21","timeZone":"Asia/Shanghai"}`, user values are id arrays (`"Me"` resolves to the subscription creator).
|
||||
- `--trigger-on-enter` = notify only on the transition INTO the filter condition (requires `--filter`); default is state semantics (every matching change).
|
||||
- Filter fields must be visible to the creator (enforced server-side, fail-closed: unknown operator/deleted field never matches).
|
||||
|
||||
### 2.2 Routing Rules
|
||||
|
||||
> **Before executing**: read the documents marked "Required" in the guide for the module.
|
||||
|
||||
Vendored
+2
@@ -13,6 +13,7 @@ const auth_1 = require("./commands/auth");
|
||||
const workflow_1 = require("./commands/workflow");
|
||||
const resources_1 = require("./commands/resources");
|
||||
const extras_1 = require("./commands/extras");
|
||||
const subscription_1 = require("./commands/subscription");
|
||||
const program = new commander_1.Command();
|
||||
program
|
||||
.name('inkreach-teable')
|
||||
@@ -34,6 +35,7 @@ for (const c of (0, resources_1.baseResourceCommands)())
|
||||
program.addCommand((0, workflow_1.workflowCommand)());
|
||||
program.addCommand((0, workflow_1.triggerCommand)());
|
||||
program.addCommand((0, workflow_1.actionCommand)());
|
||||
program.addCommand((0, subscription_1.subscriptionCommand)());
|
||||
program.addCommand((0, extras_1.secretCommand)());
|
||||
program.addCommand((0, extras_1.runsCommand)());
|
||||
program.addCommand((0, extras_1.fireCommand)());
|
||||
|
||||
Vendored
+315
@@ -0,0 +1,315 @@
|
||||
"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;
|
||||
Vendored
+21
@@ -271,5 +271,26 @@ class Client {
|
||||
removeDeveloper(userId) {
|
||||
return this.call('DELETE', `/api/plugin-developers/${encodeURIComponent(userId)}`);
|
||||
}
|
||||
// ---- subscription center (record subscriptions) ----
|
||||
listSubscriptions(baseId, tableId) {
|
||||
const q = tableId ? `?tableId=${encodeURIComponent(tableId)}` : '';
|
||||
return this.call('GET', `/api/base/${baseId}/subscription${q}`);
|
||||
}
|
||||
getSubscriptionEvents(baseId, subscriptionId) {
|
||||
return this.call('GET', `/api/base/${baseId}/subscription/${subscriptionId}/event`);
|
||||
}
|
||||
createSubscription(baseId, body) {
|
||||
return this.call('POST', `/api/base/${baseId}/subscription`, body);
|
||||
}
|
||||
updateSubscription(baseId, subscriptionId, body) {
|
||||
return this.call('PATCH', `/api/base/${baseId}/subscription/${subscriptionId}`, body);
|
||||
}
|
||||
deleteSubscription(baseId, subscriptionId) {
|
||||
return this.call('DELETE', `/api/base/${baseId}/subscription/${subscriptionId}`);
|
||||
}
|
||||
/** 立即发送(flush pending digest events) */
|
||||
sendSubscriptionNow(baseId, subscriptionId) {
|
||||
return this.call('POST', `/api/base/${baseId}/subscription/${subscriptionId}/send`);
|
||||
}
|
||||
}
|
||||
exports.Client = Client;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { authCommand } from './commands/auth';
|
||||
import { workflowCommand, triggerCommand, actionCommand } from './commands/workflow';
|
||||
import { baseResourceCommands } from './commands/resources';
|
||||
import { secretCommand, runsCommand, fireCommand, sqlCommand } from './commands/extras';
|
||||
import { subscriptionCommand } from './commands/subscription';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
@@ -37,6 +38,7 @@ for (const c of baseResourceCommands()) program.addCommand(c);
|
||||
program.addCommand(workflowCommand());
|
||||
program.addCommand(triggerCommand());
|
||||
program.addCommand(actionCommand());
|
||||
program.addCommand(subscriptionCommand());
|
||||
program.addCommand(secretCommand());
|
||||
program.addCommand(runsCommand());
|
||||
program.addCommand(fireCommand());
|
||||
|
||||
+75
-5
@@ -91,11 +91,7 @@ export class Client {
|
||||
/** PUT convert: the ONLY API path that persists field options (choices,
|
||||
* visibilityFilter dependency config, etc.). Requires the field's current type. */
|
||||
convertField(tableId: string, fieldId: string, body: unknown) {
|
||||
return this.call<unknown>(
|
||||
'PUT',
|
||||
`/api/table/${tableId}/field/${fieldId}/convert`,
|
||||
body
|
||||
);
|
||||
return this.call<unknown>('PUT', `/api/table/${tableId}/field/${fieldId}/convert`, body);
|
||||
}
|
||||
deleteField(tableId: string, fieldId: string) {
|
||||
return this.call<{ id: string }>('DELETE', `/api/table/${tableId}/field/${fieldId}`);
|
||||
@@ -342,6 +338,38 @@ export class Client {
|
||||
removeDeveloper(userId: string) {
|
||||
return this.call<unknown>('DELETE', `/api/plugin-developers/${encodeURIComponent(userId)}`);
|
||||
}
|
||||
|
||||
// ---- subscription center (record subscriptions) ----
|
||||
listSubscriptions(baseId: string, tableId?: string) {
|
||||
const q = tableId ? `?tableId=${encodeURIComponent(tableId)}` : '';
|
||||
return this.call<IRecordSubscriptionVo[]>('GET', `/api/base/${baseId}/subscription${q}`);
|
||||
}
|
||||
getSubscriptionEvents(baseId: string, subscriptionId: string) {
|
||||
return this.call<IRecordSubscriptionEventVo[]>(
|
||||
'GET',
|
||||
`/api/base/${baseId}/subscription/${subscriptionId}/event`
|
||||
);
|
||||
}
|
||||
createSubscription(baseId: string, body: IRecordSubscriptionRo) {
|
||||
return this.call<IRecordSubscriptionVo>('POST', `/api/base/${baseId}/subscription`, body);
|
||||
}
|
||||
updateSubscription(baseId: string, subscriptionId: string, body: Partial<IRecordSubscriptionRo>) {
|
||||
return this.call<IRecordSubscriptionVo>(
|
||||
'PATCH',
|
||||
`/api/base/${baseId}/subscription/${subscriptionId}`,
|
||||
body
|
||||
);
|
||||
}
|
||||
deleteSubscription(baseId: string, subscriptionId: string) {
|
||||
return this.call<{ id: string }>(
|
||||
'DELETE',
|
||||
`/api/base/${baseId}/subscription/${subscriptionId}`
|
||||
);
|
||||
}
|
||||
/** 立即发送(flush pending digest events) */
|
||||
sendSubscriptionNow(baseId: string, subscriptionId: string) {
|
||||
return this.call<unknown>('POST', `/api/base/${baseId}/subscription/${subscriptionId}/send`);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- shared types (mirror backend workflow_node / workflow_edge shapes) ----
|
||||
@@ -398,3 +426,45 @@ export interface IWorkflowVo {
|
||||
}
|
||||
|
||||
export { ApiError };
|
||||
|
||||
// ---- subscription types (mirror backend record_subscription contract) ----
|
||||
export interface IRecordSubscriptionRo {
|
||||
name: string;
|
||||
tableId: string;
|
||||
watchCreated?: boolean;
|
||||
watchUpdated?: boolean;
|
||||
fieldIds?: string[];
|
||||
recordIds?: string[];
|
||||
displayFieldId?: string | null;
|
||||
attachCsv?: boolean;
|
||||
filter?: Record<string, unknown> | null;
|
||||
triggerOnEnter?: boolean;
|
||||
sendMode?: 'digest' | 'instant';
|
||||
recipients?: string[];
|
||||
notifyInApp?: boolean;
|
||||
scheduleType?: 'daily' | 'weekly' | 'hours' | 'minutes';
|
||||
scheduleTime?: string;
|
||||
scheduleWeekday?: number;
|
||||
scheduleInterval?: number;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export interface IRecordSubscriptionVo extends IRecordSubscriptionRo {
|
||||
id: string;
|
||||
baseId: string;
|
||||
createdBy: string;
|
||||
createdTime: string;
|
||||
lastModifiedTime?: string | null;
|
||||
lastSentTime?: string | null;
|
||||
pendingCount?: number;
|
||||
}
|
||||
|
||||
export interface IRecordSubscriptionEventVo {
|
||||
id: string;
|
||||
subscriptionId: string;
|
||||
recordId: string;
|
||||
changeType: 'created' | 'updated';
|
||||
changedFieldIds?: string[] | null;
|
||||
createdTime: string;
|
||||
sentTime?: string | null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user