@objectstack/service-job 4.0.4 → 4.0.5

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": "@objectstack/service-job",
3
- "version": "4.0.4",
3
+ "version": "4.0.5",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Job Service for ObjectStack — implements IJobService with setInterval and cron scheduling",
6
6
  "type": "module",
@@ -14,13 +14,38 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@objectstack/core": "4.0.4",
18
- "@objectstack/spec": "4.0.4"
17
+ "@objectstack/core": "4.0.5",
18
+ "@objectstack/spec": "4.0.5"
19
19
  },
20
20
  "devDependencies": {
21
- "@types/node": "^25.6.0",
22
- "typescript": "^6.0.2",
23
- "vitest": "^4.1.4"
21
+ "@types/node": "^25.6.2",
22
+ "typescript": "^6.0.3",
23
+ "vitest": "^4.1.5"
24
+ },
25
+ "keywords": [
26
+ "objectstack",
27
+ "service",
28
+ "job",
29
+ "cron",
30
+ "scheduler"
31
+ ],
32
+ "author": "ObjectStack",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/objectstack-ai/framework.git",
36
+ "directory": "packages/services/service-job"
37
+ },
38
+ "homepage": "https://objectstack.ai/docs",
39
+ "bugs": "https://github.com/objectstack-ai/framework/issues",
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "README.md"
46
+ ],
47
+ "engines": {
48
+ "node": ">=18.0.0"
24
49
  },
25
50
  "scripts": {
26
51
  "build": "tsup --config ../../../tsup.config.ts",
@@ -1,22 +0,0 @@
1
-
2
- > @objectstack/service-job@4.0.4 build /home/runner/work/framework/framework/packages/services/service-job
3
- > tsup --config ../../../tsup.config.ts
4
-
5
- CLI Building entry: src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
9
- CLI Target: es2020
10
- CLI Cleaning output folder
11
- ESM Build start
12
- CJS Build start
13
- ESM dist/index.js 3.99 KB
14
- ESM dist/index.js.map 10.88 KB
15
- ESM ⚡️ Build success in 82ms
16
- CJS dist/index.cjs 5.09 KB
17
- CJS dist/index.cjs.map 11.42 KB
18
- CJS ⚡️ Build success in 84ms
19
- DTS Build start
20
- DTS ⚡️ Build success in 15151ms
21
- DTS dist/index.d.ts 3.60 KB
22
- DTS dist/index.d.cts 3.60 KB
package/CHANGELOG.md DELETED
@@ -1,177 +0,0 @@
1
- # @objectstack/service-job
2
-
3
- ## 4.0.4
4
-
5
- ### Patch Changes
6
-
7
- - Updated dependencies [326b66b]
8
- - @objectstack/spec@4.0.4
9
- - @objectstack/core@4.0.4
10
-
11
- ## 4.0.3
12
-
13
- ### Patch Changes
14
-
15
- - @objectstack/spec@4.0.3
16
- - @objectstack/core@4.0.3
17
-
18
- ## 4.0.2
19
-
20
- ### Patch Changes
21
-
22
- - Updated dependencies [5f659e9]
23
- - @objectstack/spec@4.0.2
24
- - @objectstack/core@4.0.2
25
-
26
- ## 4.0.0
27
-
28
- ### Patch Changes
29
-
30
- - Updated dependencies [f08ffc3]
31
- - Updated dependencies [e0b0a78]
32
- - @objectstack/spec@4.0.0
33
- - @objectstack/core@4.0.0
34
-
35
- ## 3.3.1
36
-
37
- ### Patch Changes
38
-
39
- - @objectstack/spec@3.3.1
40
- - @objectstack/core@3.3.1
41
-
42
- ## 3.3.0
43
-
44
- ### Patch Changes
45
-
46
- - @objectstack/spec@3.3.0
47
- - @objectstack/core@3.3.0
48
-
49
- ## 3.2.9
50
-
51
- ### Patch Changes
52
-
53
- - @objectstack/spec@3.2.9
54
- - @objectstack/core@3.2.9
55
-
56
- ## 3.2.8
57
-
58
- ### Patch Changes
59
-
60
- - @objectstack/spec@3.2.8
61
- - @objectstack/core@3.2.8
62
-
63
- ## 3.2.7
64
-
65
- ### Patch Changes
66
-
67
- - @objectstack/spec@3.2.7
68
- - @objectstack/core@3.2.7
69
-
70
- ## 3.2.6
71
-
72
- ### Patch Changes
73
-
74
- - @objectstack/spec@3.2.6
75
- - @objectstack/core@3.2.6
76
-
77
- ## 3.2.5
78
-
79
- ### Patch Changes
80
-
81
- - @objectstack/spec@3.2.5
82
- - @objectstack/core@3.2.5
83
-
84
- ## 3.2.4
85
-
86
- ### Patch Changes
87
-
88
- - @objectstack/spec@3.2.4
89
- - @objectstack/core@3.2.4
90
-
91
- ## 3.2.3
92
-
93
- ### Patch Changes
94
-
95
- - @objectstack/spec@3.2.3
96
- - @objectstack/core@3.2.3
97
-
98
- ## 3.2.2
99
-
100
- ### Patch Changes
101
-
102
- - Updated dependencies [46defbb]
103
- - @objectstack/spec@3.2.2
104
- - @objectstack/core@3.2.2
105
-
106
- ## 3.2.1
107
-
108
- ### Patch Changes
109
-
110
- - Updated dependencies [850b546]
111
- - @objectstack/spec@3.2.1
112
- - @objectstack/core@3.2.1
113
-
114
- ## 3.2.0
115
-
116
- ### Patch Changes
117
-
118
- - Updated dependencies [5901c29]
119
- - @objectstack/spec@3.2.0
120
- - @objectstack/core@3.2.0
121
-
122
- ## 3.1.1
123
-
124
- ### Patch Changes
125
-
126
- - Updated dependencies [953d667]
127
- - @objectstack/spec@3.1.1
128
- - @objectstack/core@3.1.1
129
-
130
- ## 3.1.0
131
-
132
- ### Patch Changes
133
-
134
- - Updated dependencies [0088830]
135
- - @objectstack/spec@3.1.0
136
- - @objectstack/core@3.1.0
137
-
138
- ## 3.0.11
139
-
140
- ### Patch Changes
141
-
142
- - Updated dependencies [92d9d99]
143
- - @objectstack/spec@3.0.11
144
- - @objectstack/core@3.0.11
145
-
146
- ## 3.0.10
147
-
148
- ### Patch Changes
149
-
150
- - Updated dependencies [d1e5d31]
151
- - @objectstack/spec@3.0.10
152
- - @objectstack/core@3.0.10
153
-
154
- ## 3.0.9
155
-
156
- ### Patch Changes
157
-
158
- - Updated dependencies [15e0df6]
159
- - @objectstack/spec@3.0.9
160
- - @objectstack/core@3.0.9
161
-
162
- ## 3.0.8
163
-
164
- ### Patch Changes
165
-
166
- - Updated dependencies [5a968a2]
167
- - @objectstack/spec@3.0.8
168
- - @objectstack/core@3.0.8
169
-
170
- ## 3.0.7
171
-
172
- ### Patch Changes
173
-
174
- - Updated dependencies [0119bd7]
175
- - Updated dependencies [5426bdf]
176
- - @objectstack/spec@3.0.7
177
- - @objectstack/core@3.0.7
@@ -1,51 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import type { IJobService, JobSchedule, JobHandler, JobExecution } from '@objectstack/spec/contracts';
4
-
5
- /**
6
- * Configuration for the cron-based job adapter.
7
- */
8
- export interface CronJobAdapterOptions {
9
- /** Timezone for cron expressions (default: 'UTC') */
10
- timezone?: string;
11
- }
12
-
13
- /**
14
- * Cron-based job adapter skeleton implementing IJobService.
15
- *
16
- * This is a placeholder for future cron integration (e.g., `node-cron` or `croner`).
17
- * Concrete implementation will parse cron expressions and schedule jobs accordingly.
18
- *
19
- * @example
20
- * ```ts
21
- * const scheduler = new CronJobAdapter({ timezone: 'America/New_York' });
22
- * await scheduler.schedule('nightly-cleanup', { type: 'cron', expression: '0 0 * * *' }, handler);
23
- * ```
24
- */
25
- export class CronJobAdapter implements IJobService {
26
- private readonly timezone: string;
27
-
28
- constructor(options: CronJobAdapterOptions = {}) {
29
- this.timezone = options.timezone ?? 'UTC';
30
- }
31
-
32
- async schedule(_name: string, _schedule: JobSchedule, _handler: JobHandler): Promise<void> {
33
- throw new Error(`CronJobAdapter not yet implemented (timezone: ${this.timezone})`);
34
- }
35
-
36
- async cancel(_name: string): Promise<void> {
37
- throw new Error('CronJobAdapter not yet implemented');
38
- }
39
-
40
- async trigger(_name: string, _data?: unknown): Promise<void> {
41
- throw new Error('CronJobAdapter not yet implemented');
42
- }
43
-
44
- async getExecutions(_name: string, _limit?: number): Promise<JobExecution[]> {
45
- throw new Error('CronJobAdapter not yet implemented');
46
- }
47
-
48
- async listJobs(): Promise<string[]> {
49
- throw new Error('CronJobAdapter not yet implemented');
50
- }
51
- }
package/src/index.ts DELETED
@@ -1,8 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- export { JobServicePlugin } from './job-service-plugin.js';
4
- export type { JobServicePluginOptions } from './job-service-plugin.js';
5
- export { IntervalJobAdapter } from './interval-job-adapter.js';
6
- export type { IntervalJobAdapterOptions } from './interval-job-adapter.js';
7
- export { CronJobAdapter } from './cron-job-adapter.js';
8
- export type { CronJobAdapterOptions } from './cron-job-adapter.js';
@@ -1,120 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { describe, it, expect, afterEach } from 'vitest';
4
- import { IntervalJobAdapter } from './interval-job-adapter';
5
- import type { IJobService } from '@objectstack/spec/contracts';
6
-
7
- describe('IntervalJobAdapter', () => {
8
- let adapter: IntervalJobAdapter;
9
-
10
- afterEach(async () => {
11
- await adapter?.destroy();
12
- });
13
-
14
- it('should implement IJobService contract', () => {
15
- adapter = new IntervalJobAdapter();
16
- const job: IJobService = adapter;
17
- expect(typeof job.schedule).toBe('function');
18
- expect(typeof job.cancel).toBe('function');
19
- expect(typeof job.trigger).toBe('function');
20
- expect(typeof job.getExecutions).toBe('function');
21
- expect(typeof job.listJobs).toBe('function');
22
- });
23
-
24
- it('should schedule and list jobs', async () => {
25
- adapter = new IntervalJobAdapter();
26
- await adapter.schedule('daily-report', { type: 'cron', expression: '0 0 * * *' }, async () => {});
27
- expect(await adapter.listJobs()).toEqual(['daily-report']);
28
- });
29
-
30
- it('should cancel a job', async () => {
31
- adapter = new IntervalJobAdapter();
32
- await adapter.schedule('temp-job', { type: 'cron', expression: '* * * * *' }, async () => {});
33
- await adapter.cancel('temp-job');
34
- expect(await adapter.listJobs()).toEqual([]);
35
- });
36
-
37
- it('should trigger a job handler with data', async () => {
38
- adapter = new IntervalJobAdapter();
39
- let triggered = false;
40
- let receivedCtx: any;
41
-
42
- await adapter.schedule('my-job', { type: 'cron', expression: '* * * * *' }, async (ctx) => {
43
- triggered = true;
44
- receivedCtx = ctx;
45
- });
46
-
47
- await adapter.trigger('my-job', { key: 'val' });
48
- expect(triggered).toBe(true);
49
- expect(receivedCtx.jobId).toBe('my-job');
50
- expect(receivedCtx.data).toEqual({ key: 'val' });
51
- });
52
-
53
- it('should throw when triggering non-existent job', async () => {
54
- adapter = new IntervalJobAdapter();
55
- await expect(adapter.trigger('missing')).rejects.toThrow('Job "missing" not found');
56
- });
57
-
58
- it('should record execution history', async () => {
59
- adapter = new IntervalJobAdapter();
60
- await adapter.schedule('tracked-job', { type: 'cron', expression: '* * * * *' }, async () => {});
61
- await adapter.trigger('tracked-job');
62
-
63
- const execs = await adapter.getExecutions('tracked-job');
64
- expect(execs).toHaveLength(1);
65
- expect(execs[0].status).toBe('success');
66
- expect(execs[0].jobId).toBe('tracked-job');
67
- expect(execs[0].startedAt).toBeTruthy();
68
- expect(execs[0].completedAt).toBeTruthy();
69
- expect(typeof execs[0].durationMs).toBe('number');
70
- });
71
-
72
- it('should record failed executions', async () => {
73
- adapter = new IntervalJobAdapter();
74
- await adapter.schedule('fail-job', { type: 'cron', expression: '* * * * *' }, async () => {
75
- throw new Error('Job failed');
76
- });
77
-
78
- await adapter.trigger('fail-job');
79
-
80
- const execs = await adapter.getExecutions('fail-job');
81
- expect(execs).toHaveLength(1);
82
- expect(execs[0].status).toBe('failed');
83
- expect(execs[0].error).toBe('Job failed');
84
- });
85
-
86
- it('should return empty executions for non-existent job', async () => {
87
- adapter = new IntervalJobAdapter();
88
- expect(await adapter.getExecutions('missing')).toEqual([]);
89
- });
90
-
91
- it('should limit execution history with limit param', async () => {
92
- adapter = new IntervalJobAdapter();
93
- await adapter.schedule('multi-job', { type: 'cron', expression: '* * * * *' }, async () => {});
94
- await adapter.trigger('multi-job');
95
- await adapter.trigger('multi-job');
96
- await adapter.trigger('multi-job');
97
-
98
- const execs = await adapter.getExecutions('multi-job', 2);
99
- expect(execs).toHaveLength(2);
100
- });
101
-
102
- it('should replace existing job with same name', async () => {
103
- adapter = new IntervalJobAdapter();
104
- let count = 0;
105
- await adapter.schedule('dup', { type: 'cron', expression: '* * * * *' }, async () => { count = 1; });
106
- await adapter.schedule('dup', { type: 'cron', expression: '* * * * *' }, async () => { count = 2; });
107
-
108
- await adapter.trigger('dup');
109
- expect(count).toBe(2);
110
- expect(await adapter.listJobs()).toEqual(['dup']);
111
- });
112
-
113
- it('should clean up all timers on destroy', async () => {
114
- adapter = new IntervalJobAdapter();
115
- await adapter.schedule('j1', { type: 'interval', intervalMs: 100000 }, async () => {});
116
- await adapter.schedule('j2', { type: 'interval', intervalMs: 100000 }, async () => {});
117
- await adapter.destroy();
118
- expect(await adapter.listJobs()).toEqual([]);
119
- });
120
- });
@@ -1,130 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import type { IJobService, JobSchedule, JobHandler, JobExecution } from '@objectstack/spec/contracts';
4
-
5
- /**
6
- * Internal record for a scheduled job.
7
- */
8
- interface JobRecord {
9
- name: string;
10
- schedule: JobSchedule;
11
- handler: JobHandler;
12
- timerId?: ReturnType<typeof setInterval> | ReturnType<typeof setTimeout>;
13
- executions: JobExecution[];
14
- }
15
-
16
- /**
17
- * Configuration options for IntervalJobAdapter.
18
- */
19
- export interface IntervalJobAdapterOptions {
20
- /** Maximum number of execution records to retain per job (default: 100) */
21
- maxExecutions?: number;
22
- }
23
-
24
- /**
25
- * setInterval-based job adapter implementing IJobService.
26
- *
27
- * Supports `interval` and `once` schedule types using Node.js timers.
28
- * `cron` schedules are stored but not actively executed (requires a cron
29
- * library — see CronJobAdapter skeleton).
30
- *
31
- * Suitable for single-process environments, development, and testing.
32
- */
33
- export class IntervalJobAdapter implements IJobService {
34
- private readonly jobs = new Map<string, JobRecord>();
35
- private readonly maxExecutions: number;
36
-
37
- constructor(options: IntervalJobAdapterOptions = {}) {
38
- this.maxExecutions = options.maxExecutions ?? 100;
39
- }
40
-
41
- async schedule(name: string, schedule: JobSchedule, handler: JobHandler): Promise<void> {
42
- // Cancel any existing job with the same name
43
- await this.cancel(name);
44
-
45
- const record: JobRecord = { name, schedule, handler, executions: [] };
46
-
47
- if (schedule.type === 'interval' && schedule.intervalMs) {
48
- record.timerId = setInterval(async () => {
49
- await this.executeJob(record);
50
- }, schedule.intervalMs);
51
- } else if (schedule.type === 'once' && schedule.at) {
52
- const delay = new Date(schedule.at).getTime() - Date.now();
53
- if (delay > 0) {
54
- record.timerId = setTimeout(async () => {
55
- await this.executeJob(record);
56
- }, delay);
57
- }
58
- }
59
- // 'cron' type: stored but not actively scheduled (needs cron library)
60
-
61
- this.jobs.set(name, record);
62
- }
63
-
64
- async cancel(name: string): Promise<void> {
65
- const record = this.jobs.get(name);
66
- if (record?.timerId) {
67
- clearInterval(record.timerId as ReturnType<typeof setInterval>);
68
- clearTimeout(record.timerId as ReturnType<typeof setTimeout>);
69
- }
70
- this.jobs.delete(name);
71
- }
72
-
73
- async trigger(name: string, data?: unknown): Promise<void> {
74
- const record = this.jobs.get(name);
75
- if (!record) {
76
- throw new Error(`Job "${name}" not found`);
77
- }
78
- await this.executeJob(record, data);
79
- }
80
-
81
- async getExecutions(name: string, limit?: number): Promise<JobExecution[]> {
82
- const record = this.jobs.get(name);
83
- if (!record) return [];
84
- const execs = record.executions;
85
- return limit ? execs.slice(-limit) : execs;
86
- }
87
-
88
- async listJobs(): Promise<string[]> {
89
- return [...this.jobs.keys()];
90
- }
91
-
92
- /**
93
- * Stop all active timers. Call during plugin destroy phase.
94
- */
95
- async destroy(): Promise<void> {
96
- for (const record of this.jobs.values()) {
97
- if (record.timerId) {
98
- clearInterval(record.timerId as ReturnType<typeof setInterval>);
99
- clearTimeout(record.timerId as ReturnType<typeof setTimeout>);
100
- }
101
- }
102
- this.jobs.clear();
103
- }
104
-
105
- private async executeJob(record: JobRecord, data?: unknown): Promise<void> {
106
- const execution: JobExecution = {
107
- jobId: record.name,
108
- status: 'running',
109
- startedAt: new Date().toISOString(),
110
- };
111
-
112
- const startMs = Date.now();
113
- try {
114
- await record.handler({ jobId: record.name, data });
115
- execution.status = 'success';
116
- } catch (err) {
117
- execution.status = 'failed';
118
- execution.error = err instanceof Error ? err.message : String(err);
119
- } finally {
120
- execution.completedAt = new Date().toISOString();
121
- execution.durationMs = Date.now() - startMs;
122
-
123
- record.executions.push(execution);
124
- // Trim old executions
125
- if (record.executions.length > this.maxExecutions) {
126
- record.executions.splice(0, record.executions.length - this.maxExecutions);
127
- }
128
- }
129
- }
130
- }
@@ -1,65 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import type { Plugin, PluginContext } from '@objectstack/core';
4
- import { IntervalJobAdapter } from './interval-job-adapter.js';
5
- import type { IntervalJobAdapterOptions } from './interval-job-adapter.js';
6
-
7
- /**
8
- * Configuration options for the JobServicePlugin.
9
- */
10
- export interface JobServicePluginOptions {
11
- /** Job adapter type (default: 'interval') */
12
- adapter?: 'interval' | 'cron';
13
- /** Options for the interval job adapter */
14
- interval?: IntervalJobAdapterOptions;
15
- }
16
-
17
- /**
18
- * JobServicePlugin — Production IJobService implementation.
19
- *
20
- * Registers a job scheduler with the kernel during the init phase.
21
- * Supports setInterval-based and cron-based adapters.
22
- *
23
- * @example
24
- * ```ts
25
- * import { ObjectKernel } from '@objectstack/core';
26
- * import { JobServicePlugin } from '@objectstack/service-job';
27
- *
28
- * const kernel = new ObjectKernel();
29
- * kernel.use(new JobServicePlugin({ adapter: 'interval' }));
30
- * await kernel.bootstrap();
31
- *
32
- * const job = kernel.getService('job');
33
- * await job.schedule('cleanup', { type: 'interval', intervalMs: 60000 }, handler);
34
- * ```
35
- */
36
- export class JobServicePlugin implements Plugin {
37
- name = 'com.objectstack.service.job';
38
- version = '1.0.0';
39
- type = 'standard';
40
-
41
- private readonly options: JobServicePluginOptions;
42
- private adapter?: IntervalJobAdapter;
43
-
44
- constructor(options: JobServicePluginOptions = {}) {
45
- this.options = { adapter: 'interval', ...options };
46
- }
47
-
48
- async init(ctx: PluginContext): Promise<void> {
49
- const adapterType = this.options.adapter;
50
- if (adapterType === 'cron') {
51
- throw new Error(
52
- 'Cron job adapter is not yet implemented. ' +
53
- 'Use adapter: "interval" or provide a custom IJobService via ctx.registerService("job", impl).'
54
- );
55
- }
56
-
57
- this.adapter = new IntervalJobAdapter(this.options.interval);
58
- ctx.registerService('job', this.adapter);
59
- ctx.logger.info('JobServicePlugin: registered interval job adapter');
60
- }
61
-
62
- async destroy(): Promise<void> {
63
- await this.adapter?.destroy();
64
- }
65
- }
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src",
6
- "types": ["node"]
7
- },
8
- "include": ["src"],
9
- "exclude": ["node_modules", "dist"]
10
- }