@zhin.js/schedule-feature 1.1.0
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 +17 -0
- package/lib/definition.d.ts +9 -0
- package/lib/definition.js +23 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +3 -0
- package/lib/provider.d.ts +4 -0
- package/lib/provider.js +31 -0
- package/lib/schedule-index.d.ts +11 -0
- package/lib/schedule-index.js +40 -0
- package/package.json +45 -0
- package/src/definition.ts +34 -0
- package/src/index.ts +4 -0
- package/src/provider.ts +34 -0
- package/src/schedule-index.ts +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @zhin.js/schedule-feature
|
|
2
|
+
|
|
3
|
+
Plugin Runtime 的 generation-owned Schedule Feature。插件在 `schedules/<name>/index.ts` 默认导出 `defineSchedule(...)`;同目录其他文件是 helper,不会被解析成任务。
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { defineSchedule } from '@zhin.js/schedule-feature';
|
|
7
|
+
|
|
8
|
+
export default defineSchedule({
|
|
9
|
+
cron: '0 0 9 * * *',
|
|
10
|
+
description: 'Daily report',
|
|
11
|
+
async execute() {
|
|
12
|
+
// work
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
也可在 `plugin.ts` 的 `setup({ addSchedule })` 中注入同一定义。Feature 在新 generation 激活时通过 owner-scoped `scheduleHostToken` 注册任务,并在旧 generation lease 释放后撤销旧注册。
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScheduleJobRegistration } from '@zhin.js/plugin-runtime';
|
|
2
|
+
export type ScheduleDefinition = Omit<ScheduleJobRegistration, 'id' | 'owner'>;
|
|
3
|
+
declare module '@zhin.js/plugin-runtime' {
|
|
4
|
+
interface PluginSetupContext {
|
|
5
|
+
addSchedule(localName: string, definition: ScheduleDefinition): void;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export declare function defineSchedule(definition: ScheduleDefinition): Readonly<ScheduleDefinition>;
|
|
9
|
+
export declare function parseScheduleDefinition(value: unknown): Readonly<ScheduleDefinition>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function defineSchedule(definition) {
|
|
2
|
+
return parseScheduleDefinition(definition);
|
|
3
|
+
}
|
|
4
|
+
export function parseScheduleDefinition(value) {
|
|
5
|
+
if (!value || typeof value !== 'object') {
|
|
6
|
+
throw new TypeError('Schedule definition must be an object');
|
|
7
|
+
}
|
|
8
|
+
const definition = value;
|
|
9
|
+
if (typeof definition.cron !== 'string' || definition.cron.trim() === '') {
|
|
10
|
+
throw new TypeError('Schedule definition requires a non-empty cron string');
|
|
11
|
+
}
|
|
12
|
+
if (typeof definition.execute !== 'function') {
|
|
13
|
+
throw new TypeError('Schedule definition requires execute()');
|
|
14
|
+
}
|
|
15
|
+
if (definition.description !== undefined && typeof definition.description !== 'string') {
|
|
16
|
+
throw new TypeError('Schedule definition description must be a string');
|
|
17
|
+
}
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
cron: definition.cron,
|
|
20
|
+
...(definition.description === undefined ? {} : { description: definition.description }),
|
|
21
|
+
execute: definition.execute,
|
|
22
|
+
});
|
|
23
|
+
}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ScheduleIndex } from './schedule-index.js';
|
|
2
|
+
export declare const scheduleFeatureId: import("@zhin.js/plugin-runtime").FeatureId;
|
|
3
|
+
declare const scheduleFeature: Readonly<import("@zhin.js/feature-kit").FeatureProvider<Readonly<import("./definition.js").ScheduleDefinition>, ScheduleIndex>>;
|
|
4
|
+
export default scheduleFeature;
|
package/lib/provider.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { featureId } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { capture, captured, defineFeatureProvider, directoryModules } from '@zhin.js/feature-kit';
|
|
3
|
+
import { parseScheduleDefinition } from './definition.js';
|
|
4
|
+
import { ScheduleIndex } from './schedule-index.js';
|
|
5
|
+
export const scheduleFeatureId = featureId('zhin.schedule');
|
|
6
|
+
const scheduleFeature = defineFeatureProvider({
|
|
7
|
+
protocol: 1,
|
|
8
|
+
id: scheduleFeatureId,
|
|
9
|
+
authoring: {
|
|
10
|
+
setupMethod: 'addSchedule',
|
|
11
|
+
conventions: [directoryModules({
|
|
12
|
+
id: 'schedules-index',
|
|
13
|
+
layouts: [{ segments: ['schedules', capture('name')], localName: (values) => captured(values, 'name') }],
|
|
14
|
+
})],
|
|
15
|
+
validate: parseScheduleDefinition,
|
|
16
|
+
},
|
|
17
|
+
runtime: {
|
|
18
|
+
project(slots, context) {
|
|
19
|
+
const index = new ScheduleIndex(slots, context.snapshot);
|
|
20
|
+
return {
|
|
21
|
+
value: index,
|
|
22
|
+
dispose: () => index.stop(),
|
|
23
|
+
handoff: {
|
|
24
|
+
activateNext: () => index.start(),
|
|
25
|
+
deactivateNext: () => index.stop(),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
export default scheduleFeature;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type CapabilitySlot, type RuntimeSnapshot } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import type { ScheduleDefinition } from './definition.js';
|
|
3
|
+
export declare class ScheduleIndex {
|
|
4
|
+
#private;
|
|
5
|
+
private readonly slots;
|
|
6
|
+
private readonly snapshot;
|
|
7
|
+
readonly $projection: "zhin.schedule-index/1";
|
|
8
|
+
constructor(slots: readonly Readonly<CapabilitySlot<ScheduleDefinition>>[], snapshot: RuntimeSnapshot);
|
|
9
|
+
start(): void;
|
|
10
|
+
stop(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { scheduleHostToken, } from '@zhin.js/plugin-runtime';
|
|
2
|
+
export class ScheduleIndex {
|
|
3
|
+
slots;
|
|
4
|
+
snapshot;
|
|
5
|
+
$projection = 'zhin.schedule-index/1';
|
|
6
|
+
#disposers = [];
|
|
7
|
+
#started = false;
|
|
8
|
+
constructor(slots, snapshot) {
|
|
9
|
+
this.slots = slots;
|
|
10
|
+
this.snapshot = snapshot;
|
|
11
|
+
}
|
|
12
|
+
start() {
|
|
13
|
+
if (this.#started)
|
|
14
|
+
return;
|
|
15
|
+
this.#started = true;
|
|
16
|
+
try {
|
|
17
|
+
for (const slot of this.slots) {
|
|
18
|
+
const resources = this.snapshot.resources.get(slot.owner);
|
|
19
|
+
const host = resources?.get(scheduleHostToken.id);
|
|
20
|
+
if (!host)
|
|
21
|
+
throw new Error(`Schedule Host is unavailable for ${slot.owner}`);
|
|
22
|
+
this.#disposers.push(host.register({
|
|
23
|
+
id: slot.localName,
|
|
24
|
+
cron: slot.definition.cron,
|
|
25
|
+
description: slot.definition.description,
|
|
26
|
+
execute: slot.definition.execute,
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
this.stop();
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
stop() {
|
|
36
|
+
for (const dispose of this.#disposers.splice(0).reverse())
|
|
37
|
+
dispose();
|
|
38
|
+
this.#started = false;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zhin.js/schedule-feature",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Generation-owned schedule Feature for Zhin Plugin Runtime",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"development": "./src/index.ts",
|
|
12
|
+
"import": "./lib/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["lib", "src"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"clean": "rimraf lib",
|
|
19
|
+
"test": "vitest run --root ../../.. packages/im/schedule-feature/tests"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@zhin.js/feature-kit": "workspace:*",
|
|
23
|
+
"@zhin.js/plugin-runtime": "workspace:*"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^26.1.2",
|
|
27
|
+
"typescript": "^6.0.3"
|
|
28
|
+
},
|
|
29
|
+
"zhin": {
|
|
30
|
+
"protocol": 1,
|
|
31
|
+
"type": "feature",
|
|
32
|
+
"entry": "./lib/provider.js",
|
|
33
|
+
"engine": "^1.0.0",
|
|
34
|
+
"featureApi": "1.0.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": { "node": "^20.19.0 || >=22.12.0" },
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"private": false,
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/zhinjs/zhin.git",
|
|
42
|
+
"directory": "packages/im/schedule-feature"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" }
|
|
45
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ScheduleJobRegistration } from '@zhin.js/plugin-runtime';
|
|
2
|
+
|
|
3
|
+
export type ScheduleDefinition = Omit<ScheduleJobRegistration, 'id' | 'owner'>;
|
|
4
|
+
|
|
5
|
+
declare module '@zhin.js/plugin-runtime' {
|
|
6
|
+
interface PluginSetupContext {
|
|
7
|
+
addSchedule(localName: string, definition: ScheduleDefinition): void;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function defineSchedule(definition: ScheduleDefinition): Readonly<ScheduleDefinition> {
|
|
12
|
+
return parseScheduleDefinition(definition);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function parseScheduleDefinition(value: unknown): Readonly<ScheduleDefinition> {
|
|
16
|
+
if (!value || typeof value !== 'object') {
|
|
17
|
+
throw new TypeError('Schedule definition must be an object');
|
|
18
|
+
}
|
|
19
|
+
const definition = value as Partial<ScheduleDefinition>;
|
|
20
|
+
if (typeof definition.cron !== 'string' || definition.cron.trim() === '') {
|
|
21
|
+
throw new TypeError('Schedule definition requires a non-empty cron string');
|
|
22
|
+
}
|
|
23
|
+
if (typeof definition.execute !== 'function') {
|
|
24
|
+
throw new TypeError('Schedule definition requires execute()');
|
|
25
|
+
}
|
|
26
|
+
if (definition.description !== undefined && typeof definition.description !== 'string') {
|
|
27
|
+
throw new TypeError('Schedule definition description must be a string');
|
|
28
|
+
}
|
|
29
|
+
return Object.freeze({
|
|
30
|
+
cron: definition.cron,
|
|
31
|
+
...(definition.description === undefined ? {} : { description: definition.description }),
|
|
32
|
+
execute: definition.execute,
|
|
33
|
+
});
|
|
34
|
+
}
|
package/src/index.ts
ADDED
package/src/provider.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { featureId } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import { capture, captured, defineFeatureProvider, directoryModules } from '@zhin.js/feature-kit';
|
|
3
|
+
import { parseScheduleDefinition } from './definition.js';
|
|
4
|
+
import { ScheduleIndex } from './schedule-index.js';
|
|
5
|
+
|
|
6
|
+
export const scheduleFeatureId = featureId('zhin.schedule');
|
|
7
|
+
|
|
8
|
+
const scheduleFeature = defineFeatureProvider({
|
|
9
|
+
protocol: 1,
|
|
10
|
+
id: scheduleFeatureId,
|
|
11
|
+
authoring: {
|
|
12
|
+
setupMethod: 'addSchedule',
|
|
13
|
+
conventions: [directoryModules({
|
|
14
|
+
id: 'schedules-index',
|
|
15
|
+
layouts: [{ segments: ['schedules', capture('name')], localName: (values) => captured(values, 'name') }],
|
|
16
|
+
})],
|
|
17
|
+
validate: parseScheduleDefinition,
|
|
18
|
+
},
|
|
19
|
+
runtime: {
|
|
20
|
+
project(slots, context) {
|
|
21
|
+
const index = new ScheduleIndex(slots, context.snapshot);
|
|
22
|
+
return {
|
|
23
|
+
value: index,
|
|
24
|
+
dispose: () => index.stop(),
|
|
25
|
+
handoff: {
|
|
26
|
+
activateNext: () => index.start(),
|
|
27
|
+
deactivateNext: () => index.stop(),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export default scheduleFeature;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
scheduleHostToken,
|
|
3
|
+
type CapabilitySlot,
|
|
4
|
+
type Dispose,
|
|
5
|
+
type PluginScheduleHost,
|
|
6
|
+
type RuntimeSnapshot,
|
|
7
|
+
} from '@zhin.js/plugin-runtime';
|
|
8
|
+
import type { ScheduleDefinition } from './definition.js';
|
|
9
|
+
|
|
10
|
+
export class ScheduleIndex {
|
|
11
|
+
readonly $projection = 'zhin.schedule-index/1' as const;
|
|
12
|
+
readonly #disposers: Dispose[] = [];
|
|
13
|
+
#started = false;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly slots: readonly Readonly<CapabilitySlot<ScheduleDefinition>>[],
|
|
17
|
+
private readonly snapshot: RuntimeSnapshot,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
start(): void {
|
|
21
|
+
if (this.#started) return;
|
|
22
|
+
this.#started = true;
|
|
23
|
+
try {
|
|
24
|
+
for (const slot of this.slots) {
|
|
25
|
+
const resources = this.snapshot.resources.get(slot.owner);
|
|
26
|
+
const host = resources?.get(scheduleHostToken.id) as PluginScheduleHost | undefined;
|
|
27
|
+
if (!host) throw new Error(`Schedule Host is unavailable for ${slot.owner}`);
|
|
28
|
+
this.#disposers.push(host.register({
|
|
29
|
+
id: slot.localName,
|
|
30
|
+
cron: slot.definition.cron,
|
|
31
|
+
description: slot.definition.description,
|
|
32
|
+
execute: slot.definition.execute,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
this.stop();
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
stop(): void {
|
|
42
|
+
for (const dispose of this.#disposers.splice(0).reverse()) dispose();
|
|
43
|
+
this.#started = false;
|
|
44
|
+
}
|
|
45
|
+
}
|