@zhin.js/schedule-feature 1.1.15 → 1.1.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/schedule-feature",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "Generation-owned schedule Feature for Zhin Plugin Runtime",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -12,12 +12,11 @@
12
12
  }
13
13
  },
14
14
  "files": [
15
- "lib",
16
- "src"
15
+ "lib"
17
16
  ],
18
17
  "dependencies": {
19
- "@zhin.js/feature-kit": "1.1.15",
20
- "@zhin.js/plugin-runtime": "1.1.23"
18
+ "@zhin.js/feature-kit": "1.1.16",
19
+ "@zhin.js/plugin-runtime": "1.2.0"
21
20
  },
22
21
  "devDependencies": {
23
22
  "@types/node": "^26.1.2",
package/src/definition.ts DELETED
@@ -1,52 +0,0 @@
1
- import type { CapabilityContext } from '@zhin.js/feature-kit';
2
-
3
- /** Operation-scoped owner, configuration and resources; never persist this context. */
4
- export type ScheduleContext<TConfig = unknown> = CapabilityContext<TConfig>;
5
-
6
- export interface ScheduleDefinition<TConfig = unknown> {
7
- readonly cron: string;
8
- readonly description?: string;
9
- execute(context: ScheduleContext<TConfig>): void | Promise<void>;
10
- }
11
-
12
- declare module '@zhin.js/plugin-runtime' {
13
- interface PluginSetupContext<TConfig = unknown> {
14
- addSchedule(localName: string, definition: ScheduleDefinition<TConfig>): void;
15
- }
16
- }
17
-
18
- export type ScheduleDefiner<TConfig> = (
19
- definition: ScheduleDefinition<TConfig>,
20
- ) => Readonly<ScheduleDefinition<TConfig>>;
21
-
22
- export function defineSchedule<TConfig = unknown>(definition: ScheduleDefinition<TConfig>): Readonly<ScheduleDefinition<TConfig>> {
23
- return parseScheduleDefinition<TConfig>(definition);
24
- }
25
-
26
- /**
27
- * Fix the plugin config type first, then define the schedule.
28
- */
29
- defineSchedule.withConfig = function withConfig<TConfig = unknown>(): ScheduleDefiner<TConfig> {
30
- return (definition) => defineSchedule(definition);
31
- };
32
-
33
- export function parseScheduleDefinition<TConfig = unknown>(value: unknown): Readonly<ScheduleDefinition<TConfig>> {
34
- if (!value || typeof value !== 'object') {
35
- throw new TypeError('Schedule definition must be an object');
36
- }
37
- const definition = value as Partial<ScheduleDefinition<TConfig>>;
38
- if (typeof definition.cron !== 'string' || definition.cron.trim() === '') {
39
- throw new TypeError('Schedule definition requires a non-empty cron string');
40
- }
41
- if (typeof definition.execute !== 'function') {
42
- throw new TypeError('Schedule definition requires execute()');
43
- }
44
- if (definition.description !== undefined && typeof definition.description !== 'string') {
45
- throw new TypeError('Schedule definition description must be a string');
46
- }
47
- return Object.freeze({
48
- cron: definition.cron,
49
- ...(definition.description === undefined ? {} : { description: definition.description }),
50
- execute: definition.execute,
51
- });
52
- }
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export { defineSchedule, parseScheduleDefinition } from './definition.js';
2
- export type { ScheduleDefinition, ScheduleContext } from './definition.js';
3
- export { ScheduleIndex } from './schedule-index.js';
4
- export { default, scheduleFeatureId } from './provider.js';
package/src/provider.ts DELETED
@@ -1,34 +0,0 @@
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: value => parseScheduleDefinition(value),
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;
@@ -1,70 +0,0 @@
1
- import {
2
- scheduleHostToken,
3
- stagePluginScheduleRegistration,
4
- createGenerationAdmissionGate,
5
- createPluginScheduleCandidate,
6
- generationAdmissionSource,
7
- scopeSetupFinalizer,
8
- type CapabilitySlot,
9
- type Dispose,
10
- type PluginScheduleHost,
11
- type RuntimeSnapshot,
12
- type PluginId,
13
- } from '@zhin.js/plugin-runtime';
14
- import type { ScheduleDefinition } from './definition.js';
15
- import { createCapabilityContext } from '@zhin.js/feature-kit';
16
-
17
- export class ScheduleIndex {
18
- readonly $projection = 'zhin.schedule-index/1' as const;
19
- readonly #disposers: Dispose[] = [];
20
- readonly #admission = createGenerationAdmissionGate();
21
- readonly #hosts = new Map<PluginId, PluginScheduleHost>();
22
- #started = false;
23
-
24
- constructor(
25
- private readonly slots: readonly Readonly<CapabilitySlot<ScheduleDefinition>>[],
26
- private readonly snapshot: RuntimeSnapshot,
27
- ) {}
28
-
29
- start(): void {
30
- if (this.#started) return;
31
- this.#started = true;
32
- try {
33
- for (const slot of this.slots) {
34
- const resources = this.snapshot.resources.get(slot.owner);
35
- const host = resources?.get(scheduleHostToken.id) as PluginScheduleHost | undefined;
36
- if (!host) throw new Error(`Schedule Host is unavailable for ${slot.owner}`);
37
- let candidate = this.#hosts.get(slot.owner);
38
- if (!candidate) {
39
- candidate = createPluginScheduleCandidate(host, this.#admission);
40
- this.#hosts.set(slot.owner, candidate);
41
- }
42
- this.#disposers.push(stagePluginScheduleRegistration(candidate, {
43
- id: slot.localName,
44
- cron: slot.definition.cron,
45
- description: slot.definition.description,
46
- execute: () => slot.definition.execute(createCapabilityContext({ snapshot: this.snapshot, ownerId: slot.owner, admission: this.#admission })),
47
- }));
48
- }
49
- } catch (error) {
50
- const cleanupErrors: unknown[] = [];
51
- try { this.stop(); } catch (cleanupError) { cleanupErrors.push(cleanupError); }
52
- if (cleanupErrors.length > 0) throw new AggregateError([error, ...cleanupErrors], 'Schedule activation and cleanup both failed', { cause: error });
53
- throw error;
54
- }
55
- }
56
-
57
- stop(): void {
58
- const errors: unknown[] = [];
59
- for (const dispose of this.#disposers.splice(0).reverse()) {
60
- try { dispose(); } catch (error) { errors.push(error); }
61
- }
62
- for (const host of this.#hosts.values()) host[scopeSetupFinalizer]('dispose');
63
- this.#hosts.clear();
64
- this.#started = false;
65
- if (errors.length > 0) throw new AggregateError(errors, 'Schedule cleanup failed');
66
- }
67
-
68
- /** @internal Candidate callbacks only enter after their projection is published. */
69
- get [generationAdmissionSource]() { return [this.#admission]; }
70
- }