@velajs/vela 0.10.0 → 1.0.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/CHANGELOG.md +33 -0
- package/dist/constants.d.ts +29 -26
- package/dist/constants.js +26 -29
- package/dist/http/middleware-consumer.d.ts +11 -10
- package/dist/http/middleware-consumer.js +10 -11
- package/dist/http/route.manager.js +1 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/pipeline/component.manager.d.ts +1 -0
- package/dist/pipeline/component.manager.js +10 -34
- package/dist/registry/metadata.registry.d.ts +2 -2
- package/dist/registry/metadata.registry.js +13 -6
- package/dist/schedule/cron-matcher.d.ts +2 -0
- package/dist/schedule/cron-matcher.js +62 -0
- package/dist/schedule/index.d.ts +4 -3
- package/dist/schedule/index.js +2 -2
- package/dist/schedule/schedule.module.d.ts +2 -6
- package/dist/schedule/schedule.module.js +4 -42
- package/dist/schedule/schedule.tokens.d.ts +0 -3
- package/dist/schedule/schedule.tokens.js +0 -2
- package/dist/schedule/schedule.types.d.ts +0 -3
- package/dist/schedule-node/index.d.ts +2 -0
- package/dist/schedule-node/index.js +2 -0
- package/dist/schedule-node/schedule-node.module.d.ts +4 -0
- package/dist/schedule-node/schedule-node.module.js +18 -0
- package/dist/{schedule → schedule-node}/schedule.executor.d.ts +2 -5
- package/dist/schedule-node/schedule.executor.js +91 -0
- package/dist/services/logger.d.ts +9 -8
- package/dist/services/logger.js +14 -15
- package/package.json +5 -1
- package/dist/schedule/schedule.executor.js +0 -169
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0 (2026-04-28)
|
|
4
|
+
|
|
5
|
+
Edge-runtime audit and AI-drift cleanup.
|
|
6
|
+
|
|
7
|
+
### Breaking changes
|
|
8
|
+
|
|
9
|
+
- **Schedule module split.** `ScheduleExecutor`, `SCHEDULE_MODULE_OPTIONS`, and `ScheduleModuleOptions` are no longer exported from `@velajs/vela`. The `setInterval`-based timer executor moved to a new opt-in sub-export at `@velajs/vela/schedule-node`. Consumers on Node or Bun should now do:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { ScheduleNodeModule } from '@velajs/vela/schedule-node';
|
|
13
|
+
|
|
14
|
+
@Module({ imports: [ScheduleNodeModule.forRoot()], providers: [JobsService] })
|
|
15
|
+
class AppModule {}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`ScheduleModule.forRoot()` is now metadata-only — `enableTimers` is no longer accepted (drop the option entirely). `ScheduleModule.forRootAsync()` was removed (no options to async-resolve).
|
|
19
|
+
|
|
20
|
+
Edge runtimes without `setInterval` (Cloudflare Workers, etc.) should continue to use platform cron triggers — `@velajs/cloudflare` ≥ 0.2.0 dispatches core `@Cron` jobs via its `scheduled()` handler.
|
|
21
|
+
|
|
22
|
+
- **TypeScript enums replaced with `as const` objects** for `HttpMethod`, `ParamType`, `Scope`, `RequestMethod`, and `LogLevel`. Value access (`HttpMethod.GET`) keeps working; type-position usages (`: HttpMethod`) keep working via same-name type aliases. Code that imported the enum *type* with structural assumptions about enum runtime shape may need adjustment.
|
|
23
|
+
|
|
24
|
+
### Fixes
|
|
25
|
+
|
|
26
|
+
- `@Head()` is now HEAD-only. Previously it registered as a plain GET handler, so `GET` requests would also hit `@Head()`-decorated methods.
|
|
27
|
+
- Handler-level guards / pipes / interceptors / filters now key off the controller constructor instead of `${className}:${method}`, fixing a metadata collision when two controllers shared a class name (across feature modules or after minification).
|
|
28
|
+
- Removed an obsolete narration comment in `route.manager.ts`.
|
|
29
|
+
|
|
30
|
+
### New
|
|
31
|
+
|
|
32
|
+
- `@velajs/vela/schedule-node` — opt-in entry for Node/Bun cron and interval execution.
|
|
33
|
+
- `parseCron(expression)` and `CronMatcher` exported from the core for use by platform cron adapters.
|
|
34
|
+
- Edge-runtime audit test (`src/__tests__/edge-runtime-audit.test.ts`) — fails CI if any file under `src/` (excluding `schedule-node/`) references forbidden APIs (`node:*`, `Buffer`, `process.*`, `__dirname/__filename`, `fs/path/os/child_process`, `setInterval`, `Bun.serve`).
|
|
35
|
+
|
|
3
36
|
## 0.1.0 (2026-02-19)
|
|
4
37
|
|
|
5
38
|
Initial release.
|
package/dist/constants.d.ts
CHANGED
|
@@ -13,29 +13,32 @@ export declare const METADATA_KEYS: {
|
|
|
13
13
|
readonly CATCH: "vela:catch";
|
|
14
14
|
readonly CRUD: "vela:crud";
|
|
15
15
|
};
|
|
16
|
-
export declare
|
|
17
|
-
GET
|
|
18
|
-
POST
|
|
19
|
-
PUT
|
|
20
|
-
PATCH
|
|
21
|
-
DELETE
|
|
22
|
-
OPTIONS
|
|
23
|
-
HEAD
|
|
24
|
-
ALL
|
|
25
|
-
}
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
16
|
+
export declare const HttpMethod: {
|
|
17
|
+
readonly GET: "get";
|
|
18
|
+
readonly POST: "post";
|
|
19
|
+
readonly PUT: "put";
|
|
20
|
+
readonly PATCH: "patch";
|
|
21
|
+
readonly DELETE: "delete";
|
|
22
|
+
readonly OPTIONS: "options";
|
|
23
|
+
readonly HEAD: "head";
|
|
24
|
+
readonly ALL: "all";
|
|
25
|
+
};
|
|
26
|
+
export type HttpMethod = (typeof HttpMethod)[keyof typeof HttpMethod];
|
|
27
|
+
export declare const ParamType: {
|
|
28
|
+
readonly BODY: "body";
|
|
29
|
+
readonly QUERY: "query";
|
|
30
|
+
readonly PARAM: "param";
|
|
31
|
+
readonly HEADERS: "headers";
|
|
32
|
+
readonly REQUEST: "request";
|
|
33
|
+
readonly RESPONSE: "response";
|
|
34
|
+
readonly IP: "ip";
|
|
35
|
+
readonly COOKIE: "cookie";
|
|
36
|
+
readonly RAW_BODY: "raw_body";
|
|
37
|
+
};
|
|
38
|
+
export type ParamType = (typeof ParamType)[keyof typeof ParamType];
|
|
39
|
+
export declare const Scope: {
|
|
40
|
+
readonly SINGLETON: "singleton";
|
|
41
|
+
readonly TRANSIENT: "transient";
|
|
42
|
+
readonly REQUEST: "request";
|
|
43
|
+
};
|
|
44
|
+
export type Scope = (typeof Scope)[keyof typeof Scope];
|
package/dist/constants.js
CHANGED
|
@@ -18,32 +18,29 @@ export const METADATA_KEYS = {
|
|
|
18
18
|
// CRUD
|
|
19
19
|
CRUD: 'vela:crud'
|
|
20
20
|
};
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Scope["REQUEST"] = "request";
|
|
48
|
-
return Scope;
|
|
49
|
-
}({});
|
|
21
|
+
export const HttpMethod = {
|
|
22
|
+
GET: 'get',
|
|
23
|
+
POST: 'post',
|
|
24
|
+
PUT: 'put',
|
|
25
|
+
PATCH: 'patch',
|
|
26
|
+
DELETE: 'delete',
|
|
27
|
+
OPTIONS: 'options',
|
|
28
|
+
HEAD: 'head',
|
|
29
|
+
ALL: 'all'
|
|
30
|
+
};
|
|
31
|
+
export const ParamType = {
|
|
32
|
+
BODY: 'body',
|
|
33
|
+
QUERY: 'query',
|
|
34
|
+
PARAM: 'param',
|
|
35
|
+
HEADERS: 'headers',
|
|
36
|
+
REQUEST: 'request',
|
|
37
|
+
RESPONSE: 'response',
|
|
38
|
+
IP: 'ip',
|
|
39
|
+
COOKIE: 'cookie',
|
|
40
|
+
RAW_BODY: 'raw_body'
|
|
41
|
+
};
|
|
42
|
+
export const Scope = {
|
|
43
|
+
SINGLETON: 'singleton',
|
|
44
|
+
TRANSIENT: 'transient',
|
|
45
|
+
REQUEST: 'request'
|
|
46
|
+
};
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import type { NestMiddleware } from '../pipeline/types';
|
|
2
2
|
import type { Type } from '../registry/types';
|
|
3
|
-
export declare
|
|
4
|
-
GET
|
|
5
|
-
POST
|
|
6
|
-
PUT
|
|
7
|
-
DELETE
|
|
8
|
-
PATCH
|
|
9
|
-
OPTIONS
|
|
10
|
-
HEAD
|
|
11
|
-
ALL
|
|
12
|
-
}
|
|
3
|
+
export declare const RequestMethod: {
|
|
4
|
+
readonly GET: "GET";
|
|
5
|
+
readonly POST: "POST";
|
|
6
|
+
readonly PUT: "PUT";
|
|
7
|
+
readonly DELETE: "DELETE";
|
|
8
|
+
readonly PATCH: "PATCH";
|
|
9
|
+
readonly OPTIONS: "OPTIONS";
|
|
10
|
+
readonly HEAD: "HEAD";
|
|
11
|
+
readonly ALL: "ALL";
|
|
12
|
+
};
|
|
13
|
+
export type RequestMethod = (typeof RequestMethod)[keyof typeof RequestMethod];
|
|
13
14
|
export interface RouteInfo {
|
|
14
15
|
path: string;
|
|
15
16
|
method?: RequestMethod;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}({});
|
|
2
|
+
export const RequestMethod = {
|
|
3
|
+
GET: 'GET',
|
|
4
|
+
POST: 'POST',
|
|
5
|
+
PUT: 'PUT',
|
|
6
|
+
DELETE: 'DELETE',
|
|
7
|
+
PATCH: 'PATCH',
|
|
8
|
+
OPTIONS: 'OPTIONS',
|
|
9
|
+
HEAD: 'HEAD',
|
|
10
|
+
ALL: 'ALL'
|
|
11
|
+
};
|
|
13
12
|
export class MiddlewareBuilder {
|
|
14
13
|
definitions = [];
|
|
15
14
|
apply(...middleware) {
|
|
@@ -50,7 +50,7 @@ export class RouteManager {
|
|
|
50
50
|
],
|
|
51
51
|
[
|
|
52
52
|
HttpMethod.HEAD,
|
|
53
|
-
(app, p, h)=>app.get(p, h)
|
|
53
|
+
(app, p, h)=>app.get(p, (c, next)=>c.req.method === 'HEAD' ? h(c) : next())
|
|
54
54
|
],
|
|
55
55
|
[
|
|
56
56
|
HttpMethod.ALL,
|
|
@@ -175,11 +175,6 @@ export class RouteManager {
|
|
|
175
175
|
instantiateMany(items, container) {
|
|
176
176
|
return items.map((item)=>this.instantiate(item, container));
|
|
177
177
|
}
|
|
178
|
-
// Resolve middleware priority at build time. Supports three sources:
|
|
179
|
-
// 1. static priority on the class constructor
|
|
180
|
-
// 2. priority property on the instance
|
|
181
|
-
// 3. for container tokens, resolve and inspect the instance (+ its ctor)
|
|
182
|
-
// Falls back to 0 on any error or when no priority is set.
|
|
183
178
|
getMiddlewarePriority(entry) {
|
|
184
179
|
if (entry == null) return 0;
|
|
185
180
|
if (typeof entry === 'function') {
|
package/dist/index.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ export { CacheModule, CacheService, CacheInterceptor, MemoryCacheStore, CacheKey
|
|
|
20
20
|
export type { CacheModuleOptions, CacheStore, CacheEntry } from './cache/index';
|
|
21
21
|
export { EventEmitterModule, EventEmitter, EventEmitterSubscriber, OnEvent, ON_EVENT_METADATA, } from './event-emitter/index';
|
|
22
22
|
export type { EventHandler, OnEventMetadata } from './event-emitter/index';
|
|
23
|
-
export { ScheduleModule, ScheduleRegistry,
|
|
24
|
-
export type { RegisteredCronJob, RegisteredIntervalJob, CronMetadata, IntervalMetadata,
|
|
23
|
+
export { ScheduleModule, ScheduleRegistry, Cron, Interval, parseCron, CRON_METADATA, INTERVAL_METADATA, } from './schedule/index';
|
|
24
|
+
export type { RegisteredCronJob, RegisteredIntervalJob, CronMetadata, IntervalMetadata, CronMatcher, } from './schedule/index';
|
|
25
25
|
export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthIndicator, } from './health/index';
|
|
26
26
|
export type { HealthCheckResult, HealthCheckStatus, HealthIndicatorResult, HealthIndicatorFunction, ResponseCheckCallback, HttpPingOptions, } from './health/index';
|
|
27
27
|
export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA, } from './throttler/index';
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ export { CacheModule, CacheService, CacheInterceptor, MemoryCacheStore, CacheKey
|
|
|
24
24
|
// Event Emitter
|
|
25
25
|
export { EventEmitterModule, EventEmitter, EventEmitterSubscriber, OnEvent, ON_EVENT_METADATA } from "./event-emitter/index.js";
|
|
26
26
|
// Schedule
|
|
27
|
-
export { ScheduleModule, ScheduleRegistry,
|
|
27
|
+
export { ScheduleModule, ScheduleRegistry, Cron, Interval, parseCron, CRON_METADATA, INTERVAL_METADATA } from "./schedule/index.js";
|
|
28
28
|
// Health
|
|
29
29
|
export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthIndicator } from "./health/index.js";
|
|
30
30
|
// Throttler
|
|
@@ -8,6 +8,7 @@ export declare class ComponentManager {
|
|
|
8
8
|
static registerController<T extends ComponentType>(type: T, controller: Constructor, ...components: ComponentTypeMap[T][]): void;
|
|
9
9
|
static registerHandler<T extends ComponentType>(type: T, controller: Constructor, handlerName: string | symbol, ...components: ComponentTypeMap[T][]): void;
|
|
10
10
|
static getComponents<T extends ComponentType>(type: T, controller: Constructor, handlerName: string | symbol): ComponentTypeMap[T][];
|
|
11
|
+
private static resolveAll;
|
|
11
12
|
static resolveMiddleware(items: MiddlewareType[]): NestMiddleware[];
|
|
12
13
|
static resolveGuards(items: GuardType[]): CanActivate[];
|
|
13
14
|
static resolvePipes(items: PipeType[]): PipeTransform[];
|
|
@@ -19,16 +19,14 @@ export class ComponentManager {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
static registerHandler(type, controller, handlerName, ...components) {
|
|
22
|
-
const handlerKey = `${controller.name}:${String(handlerName)}`;
|
|
23
22
|
for (const component of components){
|
|
24
|
-
MetadataRegistry.registerHandler(type,
|
|
23
|
+
MetadataRegistry.registerHandler(type, controller, handlerName, component);
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
// 3-level resolution: global → controller → handler
|
|
28
27
|
static getComponents(type, controller, handlerName) {
|
|
29
|
-
const handlerKey = `${controller.name}:${String(handlerName)}`;
|
|
30
28
|
const controllerComponents = MetadataRegistry.getController(type, controller);
|
|
31
|
-
const handlerComponents = MetadataRegistry.getHandler(type,
|
|
29
|
+
const handlerComponents = MetadataRegistry.getHandler(type, controller, handlerName);
|
|
32
30
|
return [
|
|
33
31
|
...MetadataRegistry.getGlobal(type),
|
|
34
32
|
...controllerComponents,
|
|
@@ -36,45 +34,23 @@ export class ComponentManager {
|
|
|
36
34
|
];
|
|
37
35
|
}
|
|
38
36
|
// Type-specific resolvers
|
|
37
|
+
static resolveAll(items, methodKey) {
|
|
38
|
+
return items.map((item)=>isObject(item) && methodKey in item ? item : this.container.resolve(item));
|
|
39
|
+
}
|
|
39
40
|
static resolveMiddleware(items) {
|
|
40
|
-
return
|
|
41
|
-
if (isObject(item) && 'use' in item) {
|
|
42
|
-
return item;
|
|
43
|
-
}
|
|
44
|
-
return this.container.resolve(item);
|
|
45
|
-
});
|
|
41
|
+
return this.resolveAll(items, 'use');
|
|
46
42
|
}
|
|
47
43
|
static resolveGuards(items) {
|
|
48
|
-
return
|
|
49
|
-
if (isObject(item) && 'canActivate' in item) {
|
|
50
|
-
return item;
|
|
51
|
-
}
|
|
52
|
-
return this.container.resolve(item);
|
|
53
|
-
});
|
|
44
|
+
return this.resolveAll(items, 'canActivate');
|
|
54
45
|
}
|
|
55
46
|
static resolvePipes(items) {
|
|
56
|
-
return
|
|
57
|
-
if (isObject(item) && 'transform' in item) {
|
|
58
|
-
return item;
|
|
59
|
-
}
|
|
60
|
-
return this.container.resolve(item);
|
|
61
|
-
});
|
|
47
|
+
return this.resolveAll(items, 'transform');
|
|
62
48
|
}
|
|
63
49
|
static resolveInterceptors(items) {
|
|
64
|
-
return
|
|
65
|
-
if (isObject(item) && 'intercept' in item) {
|
|
66
|
-
return item;
|
|
67
|
-
}
|
|
68
|
-
return this.container.resolve(item);
|
|
69
|
-
});
|
|
50
|
+
return this.resolveAll(items, 'intercept');
|
|
70
51
|
}
|
|
71
52
|
static resolveFilters(items) {
|
|
72
|
-
return
|
|
73
|
-
if (isObject(item) && 'catch' in item) {
|
|
74
|
-
return item;
|
|
75
|
-
}
|
|
76
|
-
return this.container.resolve(item);
|
|
77
|
-
});
|
|
53
|
+
return this.resolveAll(items, 'catch');
|
|
78
54
|
}
|
|
79
55
|
// Pipe execution
|
|
80
56
|
static async executePipes(value, metadata, pipes) {
|
|
@@ -36,8 +36,8 @@ export declare class MetadataRegistry {
|
|
|
36
36
|
static getGlobal<T extends ComponentType>(type: T): Set<ComponentTypeMap[T]>;
|
|
37
37
|
static registerController<T extends ComponentType>(type: T, controller: Constructor, component: ComponentTypeMap[T]): void;
|
|
38
38
|
static getController<T extends ComponentType>(type: T, controller: Constructor): ComponentTypeMap[T][];
|
|
39
|
-
static registerHandler<T extends ComponentType>(type: T,
|
|
40
|
-
static getHandler<T extends ComponentType>(type: T,
|
|
39
|
+
static registerHandler<T extends ComponentType>(type: T, controller: Constructor, methodName: string | symbol, component: ComponentTypeMap[T]): void;
|
|
40
|
+
static getHandler<T extends ComponentType>(type: T, controller: Constructor, methodName: string | symbol): ComponentTypeMap[T][];
|
|
41
41
|
static markInjectable(target: object): void;
|
|
42
42
|
static hasInjectable(target: object): boolean;
|
|
43
43
|
static setScope(target: object, scope: Scope): void;
|
|
@@ -146,16 +146,23 @@ export class MetadataRegistry {
|
|
|
146
146
|
const typeMap = this.controller.get(type);
|
|
147
147
|
return typeMap.get(controller) || [];
|
|
148
148
|
}
|
|
149
|
-
static registerHandler(type,
|
|
149
|
+
static registerHandler(type, controller, methodName, component) {
|
|
150
150
|
const typeMap = this.handler.get(type);
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
let methodMap = typeMap.get(controller);
|
|
152
|
+
if (!methodMap) {
|
|
153
|
+
methodMap = new Map();
|
|
154
|
+
typeMap.set(controller, methodMap);
|
|
153
155
|
}
|
|
154
|
-
|
|
156
|
+
let components = methodMap.get(methodName);
|
|
157
|
+
if (!components) {
|
|
158
|
+
components = [];
|
|
159
|
+
methodMap.set(methodName, components);
|
|
160
|
+
}
|
|
161
|
+
components.push(component);
|
|
155
162
|
}
|
|
156
|
-
static getHandler(type,
|
|
163
|
+
static getHandler(type, controller, methodName) {
|
|
157
164
|
const typeMap = this.handler.get(type);
|
|
158
|
-
return typeMap.get(
|
|
165
|
+
return typeMap.get(controller)?.get(methodName) ?? [];
|
|
159
166
|
}
|
|
160
167
|
// DI metadata
|
|
161
168
|
static markInjectable(target) {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export function parseCron(expression) {
|
|
2
|
+
const fields = expression.trim().split(/\s+/);
|
|
3
|
+
if (fields.length !== 5) return null;
|
|
4
|
+
const [minuteField, hourField, dayField, monthField, weekdayField] = fields;
|
|
5
|
+
const minute = parseField(minuteField, 0, 59);
|
|
6
|
+
const hour = parseField(hourField, 0, 23);
|
|
7
|
+
const day = parseField(dayField, 1, 31);
|
|
8
|
+
const month = parseField(monthField, 1, 12);
|
|
9
|
+
const weekday = parseField(weekdayField, 0, 7);
|
|
10
|
+
if (!minute || !hour || !day || !month || !weekday) return null;
|
|
11
|
+
return (date)=>minute(date.getMinutes()) && hour(date.getHours()) && day(date.getDate()) && month(date.getMonth() + 1) && weekday(date.getDay());
|
|
12
|
+
}
|
|
13
|
+
function parseField(field, min, max) {
|
|
14
|
+
const segments = field.split(',');
|
|
15
|
+
const predicates = [];
|
|
16
|
+
for (const rawSegment of segments){
|
|
17
|
+
const segment = rawSegment.trim();
|
|
18
|
+
if (!segment) return null;
|
|
19
|
+
const stepParts = segment.split('/');
|
|
20
|
+
if (stepParts.length > 2) return null;
|
|
21
|
+
const step = stepParts.length === 2 ? Number(stepParts[1]) : 1;
|
|
22
|
+
if (!Number.isInteger(step) || step <= 0) return null;
|
|
23
|
+
const range = parseRange(stepParts[0], min, max);
|
|
24
|
+
if (!range) return null;
|
|
25
|
+
predicates.push((value)=>{
|
|
26
|
+
if (value < range.start || value > range.end) return false;
|
|
27
|
+
return (value - range.start) % step === 0;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return (value)=>predicates.some((p)=>p(value));
|
|
31
|
+
}
|
|
32
|
+
function parseRange(segment, min, max) {
|
|
33
|
+
if (segment === '*') return {
|
|
34
|
+
start: min,
|
|
35
|
+
end: max
|
|
36
|
+
};
|
|
37
|
+
const bounds = segment.split('-');
|
|
38
|
+
if (bounds.length === 1) {
|
|
39
|
+
const value = parseCronNumber(bounds[0], min, max);
|
|
40
|
+
if (value === null) return null;
|
|
41
|
+
return {
|
|
42
|
+
start: value,
|
|
43
|
+
end: value
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (bounds.length !== 2) return null;
|
|
47
|
+
const start = parseCronNumber(bounds[0], min, max);
|
|
48
|
+
const end = parseCronNumber(bounds[1], min, max);
|
|
49
|
+
if (start === null || end === null || start > end) return null;
|
|
50
|
+
return {
|
|
51
|
+
start,
|
|
52
|
+
end
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function parseCronNumber(raw, min, max) {
|
|
56
|
+
const value = Number(raw);
|
|
57
|
+
if (!Number.isInteger(value)) return null;
|
|
58
|
+
// Cron allows 0 and 7 as Sunday.
|
|
59
|
+
const normalized = max === 7 && value === 7 ? 0 : value;
|
|
60
|
+
if (normalized < min || normalized > max) return null;
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
package/dist/schedule/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { ScheduleModule } from './schedule.module';
|
|
2
2
|
export { ScheduleRegistry } from './schedule.registry';
|
|
3
3
|
export type { RegisteredCronJob, RegisteredIntervalJob } from './schedule.registry';
|
|
4
|
-
export { ScheduleExecutor } from './schedule.executor';
|
|
5
4
|
export { Cron, Interval } from './schedule.decorators';
|
|
6
|
-
export {
|
|
7
|
-
export type { CronMetadata, IntervalMetadata
|
|
5
|
+
export { CRON_METADATA, INTERVAL_METADATA } from './schedule.tokens';
|
|
6
|
+
export type { CronMetadata, IntervalMetadata } from './schedule.types';
|
|
7
|
+
export { parseCron } from './cron-matcher';
|
|
8
|
+
export type { CronMatcher } from './cron-matcher';
|
package/dist/schedule/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ScheduleModule } from "./schedule.module.js";
|
|
2
2
|
export { ScheduleRegistry } from "./schedule.registry.js";
|
|
3
|
-
export { ScheduleExecutor } from "./schedule.executor.js";
|
|
4
3
|
export { Cron, Interval } from "./schedule.decorators.js";
|
|
5
|
-
export {
|
|
4
|
+
export { CRON_METADATA, INTERVAL_METADATA } from "./schedule.tokens.js";
|
|
5
|
+
export { parseCron } from "./cron-matcher.js";
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { ScheduleModuleOptions } from './schedule.types';
|
|
1
|
+
import type { DynamicModule } from '../module/types';
|
|
3
2
|
export declare class ScheduleModule {
|
|
4
|
-
static forRoot(
|
|
5
|
-
static forRootAsync(options: AsyncModuleOptions<ScheduleModuleOptions> & {
|
|
6
|
-
enableTimers?: boolean;
|
|
7
|
-
}): DynamicModule;
|
|
3
|
+
static forRoot(): DynamicModule;
|
|
8
4
|
}
|
|
@@ -1,53 +1,15 @@
|
|
|
1
|
-
import { ScheduleExecutor } from "./schedule.executor.js";
|
|
2
1
|
import { ScheduleRegistry } from "./schedule.registry.js";
|
|
3
|
-
import { SCHEDULE_MODULE_OPTIONS } from "./schedule.tokens.js";
|
|
4
2
|
export class ScheduleModule {
|
|
5
|
-
static forRoot(
|
|
6
|
-
const { enableTimers = false } = options;
|
|
3
|
+
static forRoot() {
|
|
7
4
|
const providers = [
|
|
8
|
-
{
|
|
9
|
-
provide: SCHEDULE_MODULE_OPTIONS,
|
|
10
|
-
useValue: options
|
|
11
|
-
},
|
|
12
5
|
ScheduleRegistry
|
|
13
6
|
];
|
|
14
|
-
const exports = [
|
|
15
|
-
SCHEDULE_MODULE_OPTIONS,
|
|
16
|
-
ScheduleRegistry
|
|
17
|
-
];
|
|
18
|
-
if (enableTimers) {
|
|
19
|
-
providers.push(ScheduleExecutor);
|
|
20
|
-
exports.push(ScheduleExecutor);
|
|
21
|
-
}
|
|
22
|
-
return {
|
|
23
|
-
module: ScheduleModule,
|
|
24
|
-
providers,
|
|
25
|
-
exports
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
static forRootAsync(options) {
|
|
29
|
-
const { enableTimers = false } = options;
|
|
30
|
-
const providers = [
|
|
31
|
-
{
|
|
32
|
-
provide: SCHEDULE_MODULE_OPTIONS,
|
|
33
|
-
useFactory: options.useFactory,
|
|
34
|
-
inject: options.inject ?? []
|
|
35
|
-
},
|
|
36
|
-
ScheduleRegistry
|
|
37
|
-
];
|
|
38
|
-
const exports = [
|
|
39
|
-
SCHEDULE_MODULE_OPTIONS,
|
|
40
|
-
ScheduleRegistry
|
|
41
|
-
];
|
|
42
|
-
if (enableTimers) {
|
|
43
|
-
providers.push(ScheduleExecutor);
|
|
44
|
-
exports.push(ScheduleExecutor);
|
|
45
|
-
}
|
|
46
7
|
return {
|
|
47
8
|
module: ScheduleModule,
|
|
48
|
-
imports: options.imports ?? [],
|
|
49
9
|
providers,
|
|
50
|
-
exports
|
|
10
|
+
exports: [
|
|
11
|
+
ScheduleRegistry
|
|
12
|
+
]
|
|
51
13
|
};
|
|
52
14
|
}
|
|
53
15
|
}
|
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
import { InjectionToken } from '../container/types';
|
|
2
|
-
import type { ScheduleModuleOptions } from './schedule.types';
|
|
3
|
-
export declare const SCHEDULE_MODULE_OPTIONS: InjectionToken<ScheduleModuleOptions>;
|
|
4
1
|
export declare const CRON_METADATA = "vela:cron";
|
|
5
2
|
export declare const INTERVAL_METADATA = "vela:interval";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ScheduleRegistry } from "../schedule/schedule.registry.js";
|
|
2
|
+
import { ScheduleExecutor } from "./schedule.executor.js";
|
|
3
|
+
export class ScheduleNodeModule {
|
|
4
|
+
static forRoot() {
|
|
5
|
+
const providers = [
|
|
6
|
+
ScheduleRegistry,
|
|
7
|
+
ScheduleExecutor
|
|
8
|
+
];
|
|
9
|
+
return {
|
|
10
|
+
module: ScheduleNodeModule,
|
|
11
|
+
providers,
|
|
12
|
+
exports: [
|
|
13
|
+
ScheduleRegistry,
|
|
14
|
+
ScheduleExecutor
|
|
15
|
+
]
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { OnApplicationBootstrap, OnModuleDestroy } from '../lifecycle/index';
|
|
2
|
-
import { ScheduleRegistry } from '
|
|
2
|
+
import { ScheduleRegistry } from '../schedule/schedule.registry';
|
|
3
3
|
export declare class ScheduleExecutor implements OnApplicationBootstrap, OnModuleDestroy {
|
|
4
4
|
private registry;
|
|
5
5
|
private intervalTimers;
|
|
@@ -12,9 +12,6 @@ export declare class ScheduleExecutor implements OnApplicationBootstrap, OnModul
|
|
|
12
12
|
private scheduleInterval;
|
|
13
13
|
private scheduleCron;
|
|
14
14
|
private invoke;
|
|
15
|
-
private
|
|
16
|
-
private parseField;
|
|
17
|
-
private parseRange;
|
|
18
|
-
private parseCronNumber;
|
|
15
|
+
private getMatcher;
|
|
19
16
|
onModuleDestroy(): void;
|
|
20
17
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
function _ts_metadata(k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
}
|
|
10
|
+
import { Injectable } from "../container/index.js";
|
|
11
|
+
import { parseCron } from "../schedule/cron-matcher.js";
|
|
12
|
+
import { ScheduleRegistry } from "../schedule/schedule.registry.js";
|
|
13
|
+
export class ScheduleExecutor {
|
|
14
|
+
registry;
|
|
15
|
+
intervalTimers = [];
|
|
16
|
+
cronTimers = [];
|
|
17
|
+
cronMatcherCache = new Map();
|
|
18
|
+
lastCronMinute = new Map();
|
|
19
|
+
running = true;
|
|
20
|
+
constructor(registry){
|
|
21
|
+
this.registry = registry;
|
|
22
|
+
}
|
|
23
|
+
onApplicationBootstrap() {
|
|
24
|
+
if (typeof setInterval !== 'function') {
|
|
25
|
+
throw new Error('@velajs/vela/schedule-node requires Node or Bun. Use a platform cron adapter on edge runtimes (e.g. CloudflareApplication.scheduled).');
|
|
26
|
+
}
|
|
27
|
+
for (const job of this.registry.getIntervalJobs()){
|
|
28
|
+
this.scheduleInterval(job.instance, job.methodName, job.ms);
|
|
29
|
+
}
|
|
30
|
+
const cronJobs = this.registry.getCronJobs();
|
|
31
|
+
for(let i = 0; i < cronJobs.length; i++){
|
|
32
|
+
const job = cronJobs[i];
|
|
33
|
+
this.scheduleCron(job.instance, job.methodName, job.expression, i);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
scheduleInterval(instance, methodName, ms) {
|
|
37
|
+
if (!this.running) return;
|
|
38
|
+
const timer = setInterval(()=>{
|
|
39
|
+
void this.invoke(instance, methodName);
|
|
40
|
+
}, ms);
|
|
41
|
+
this.intervalTimers.push(timer);
|
|
42
|
+
}
|
|
43
|
+
scheduleCron(instance, methodName, expression, index) {
|
|
44
|
+
if (!this.running) return;
|
|
45
|
+
const matcher = this.getMatcher(expression);
|
|
46
|
+
if (!matcher) return;
|
|
47
|
+
const jobKey = `${index}:${methodName}:${expression}`;
|
|
48
|
+
const timer = setInterval(()=>{
|
|
49
|
+
const now = new Date();
|
|
50
|
+
const minuteKey = Math.floor(now.getTime() / 60_000);
|
|
51
|
+
if (this.lastCronMinute.get(jobKey) === minuteKey) return;
|
|
52
|
+
if (!matcher(now)) return;
|
|
53
|
+
this.lastCronMinute.set(jobKey, minuteKey);
|
|
54
|
+
void this.invoke(instance, methodName);
|
|
55
|
+
}, 1000);
|
|
56
|
+
this.cronTimers.push(timer);
|
|
57
|
+
}
|
|
58
|
+
async invoke(instance, methodName) {
|
|
59
|
+
try {
|
|
60
|
+
const method = instance[methodName];
|
|
61
|
+
if (typeof method === 'function') {
|
|
62
|
+
await method.call(instance);
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// Swallow errors so the scheduler keeps running
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
getMatcher(expression) {
|
|
69
|
+
if (this.cronMatcherCache.has(expression)) {
|
|
70
|
+
return this.cronMatcherCache.get(expression) ?? null;
|
|
71
|
+
}
|
|
72
|
+
const matcher = parseCron(expression);
|
|
73
|
+
this.cronMatcherCache.set(expression, matcher);
|
|
74
|
+
return matcher;
|
|
75
|
+
}
|
|
76
|
+
onModuleDestroy() {
|
|
77
|
+
this.running = false;
|
|
78
|
+
for (const timer of this.intervalTimers)clearInterval(timer);
|
|
79
|
+
for (const timer of this.cronTimers)clearInterval(timer);
|
|
80
|
+
this.intervalTimers = [];
|
|
81
|
+
this.cronTimers = [];
|
|
82
|
+
this.lastCronMinute.clear();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
ScheduleExecutor = _ts_decorate([
|
|
86
|
+
Injectable(),
|
|
87
|
+
_ts_metadata("design:type", Function),
|
|
88
|
+
_ts_metadata("design:paramtypes", [
|
|
89
|
+
typeof ScheduleRegistry === "undefined" ? Object : ScheduleRegistry
|
|
90
|
+
])
|
|
91
|
+
], ScheduleExecutor);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
export declare
|
|
2
|
-
VERBOSE
|
|
3
|
-
DEBUG
|
|
4
|
-
LOG
|
|
5
|
-
WARN
|
|
6
|
-
ERROR
|
|
7
|
-
SILENT
|
|
8
|
-
}
|
|
1
|
+
export declare const LogLevel: {
|
|
2
|
+
readonly VERBOSE: 0;
|
|
3
|
+
readonly DEBUG: 1;
|
|
4
|
+
readonly LOG: 2;
|
|
5
|
+
readonly WARN: 3;
|
|
6
|
+
readonly ERROR: 4;
|
|
7
|
+
readonly SILENT: 5;
|
|
8
|
+
};
|
|
9
|
+
export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];
|
|
9
10
|
export interface LoggerService {
|
|
10
11
|
log(message: unknown, ...optionalParams: unknown[]): void;
|
|
11
12
|
error(message: unknown, ...optionalParams: unknown[]): void;
|
package/dist/services/logger.js
CHANGED
|
@@ -8,15 +8,14 @@ function _ts_metadata(k, v) {
|
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
}
|
|
10
10
|
import { Injectable } from "../container/decorators.js";
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}({});
|
|
11
|
+
export const LogLevel = {
|
|
12
|
+
VERBOSE: 0,
|
|
13
|
+
DEBUG: 1,
|
|
14
|
+
LOG: 2,
|
|
15
|
+
WARN: 3,
|
|
16
|
+
ERROR: 4,
|
|
17
|
+
SILENT: 5
|
|
18
|
+
};
|
|
20
19
|
const defaultWriter = (level, line, ...rest)=>{
|
|
21
20
|
if (level === 'ERROR') return console.error(line, ...rest);
|
|
22
21
|
if (level === 'WARN') return console.warn(line, ...rest);
|
|
@@ -35,7 +34,7 @@ function formatValue(v) {
|
|
|
35
34
|
}
|
|
36
35
|
}
|
|
37
36
|
export class Logger {
|
|
38
|
-
static level =
|
|
37
|
+
static level = LogLevel.LOG;
|
|
39
38
|
static globalLogger;
|
|
40
39
|
static globalContextProviders = [];
|
|
41
40
|
static globalWriter = defaultWriter;
|
|
@@ -90,19 +89,19 @@ export class Logger {
|
|
|
90
89
|
return child;
|
|
91
90
|
}
|
|
92
91
|
log(message, ...optionalParams) {
|
|
93
|
-
this.emit('LOG',
|
|
92
|
+
this.emit('LOG', LogLevel.LOG, message, optionalParams);
|
|
94
93
|
}
|
|
95
94
|
error(message, ...optionalParams) {
|
|
96
|
-
this.emit('ERROR',
|
|
95
|
+
this.emit('ERROR', LogLevel.ERROR, message, optionalParams);
|
|
97
96
|
}
|
|
98
97
|
warn(message, ...optionalParams) {
|
|
99
|
-
this.emit('WARN',
|
|
98
|
+
this.emit('WARN', LogLevel.WARN, message, optionalParams);
|
|
100
99
|
}
|
|
101
100
|
debug(message, ...optionalParams) {
|
|
102
|
-
this.emit('DEBUG',
|
|
101
|
+
this.emit('DEBUG', LogLevel.DEBUG, message, optionalParams);
|
|
103
102
|
}
|
|
104
103
|
verbose(message, ...optionalParams) {
|
|
105
|
-
this.emit('VERBOSE',
|
|
104
|
+
this.emit('VERBOSE', LogLevel.VERBOSE, message, optionalParams);
|
|
106
105
|
}
|
|
107
106
|
emit(levelName, levelValue, message, rest) {
|
|
108
107
|
if (Logger.level > levelValue) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velajs/vela",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "NestJS-compatible framework for edge runtimes, powered by Hono",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"./streaming": {
|
|
14
14
|
"types": "./dist/streaming/index.d.ts",
|
|
15
15
|
"import": "./dist/streaming/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./schedule-node": {
|
|
18
|
+
"types": "./dist/schedule-node/index.d.ts",
|
|
19
|
+
"import": "./dist/schedule-node/index.js"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"files": [
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
}
|
|
7
|
-
function _ts_metadata(k, v) {
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
}
|
|
10
|
-
import { Injectable } from "../container/index.js";
|
|
11
|
-
import { ScheduleRegistry } from "./schedule.registry.js";
|
|
12
|
-
export class ScheduleExecutor {
|
|
13
|
-
registry;
|
|
14
|
-
intervalTimers = [];
|
|
15
|
-
cronTimers = [];
|
|
16
|
-
cronMatcherCache = new Map();
|
|
17
|
-
lastCronMinute = new Map();
|
|
18
|
-
running = true;
|
|
19
|
-
constructor(registry){
|
|
20
|
-
this.registry = registry;
|
|
21
|
-
}
|
|
22
|
-
onApplicationBootstrap() {
|
|
23
|
-
const intervalJobs = this.registry.getIntervalJobs();
|
|
24
|
-
const cronJobs = this.registry.getCronJobs();
|
|
25
|
-
for (const job of intervalJobs){
|
|
26
|
-
this.scheduleInterval(job.instance, job.methodName, job.ms);
|
|
27
|
-
}
|
|
28
|
-
for(let i = 0; i < cronJobs.length; i++){
|
|
29
|
-
const job = cronJobs[i];
|
|
30
|
-
this.scheduleCron(job.instance, job.methodName, job.expression, i);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
scheduleInterval(instance, methodName, ms) {
|
|
34
|
-
if (!this.running) return;
|
|
35
|
-
const timer = setInterval(()=>{
|
|
36
|
-
void this.invoke(instance, methodName);
|
|
37
|
-
}, ms);
|
|
38
|
-
this.intervalTimers.push(timer);
|
|
39
|
-
}
|
|
40
|
-
scheduleCron(instance, methodName, expression, index) {
|
|
41
|
-
if (!this.running) return;
|
|
42
|
-
const matcher = this.getCronMatcher(expression);
|
|
43
|
-
if (!matcher) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const jobKey = `${index}:${methodName}:${expression}`;
|
|
47
|
-
const timer = setInterval(()=>{
|
|
48
|
-
const now = new Date();
|
|
49
|
-
const minuteKey = Math.floor(now.getTime() / 60_000);
|
|
50
|
-
if (this.lastCronMinute.get(jobKey) === minuteKey) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if (!matcher(now)) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
this.lastCronMinute.set(jobKey, minuteKey);
|
|
57
|
-
void this.invoke(instance, methodName);
|
|
58
|
-
}, 1000);
|
|
59
|
-
this.cronTimers.push(timer);
|
|
60
|
-
}
|
|
61
|
-
async invoke(instance, methodName) {
|
|
62
|
-
try {
|
|
63
|
-
const method = instance[methodName];
|
|
64
|
-
if (typeof method === 'function') {
|
|
65
|
-
await method.call(instance);
|
|
66
|
-
}
|
|
67
|
-
} catch {
|
|
68
|
-
// Swallow errors to keep the scheduler alive
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
getCronMatcher(expression) {
|
|
72
|
-
if (this.cronMatcherCache.has(expression)) {
|
|
73
|
-
return this.cronMatcherCache.get(expression) ?? null;
|
|
74
|
-
}
|
|
75
|
-
const fields = expression.trim().split(/\s+/);
|
|
76
|
-
if (fields.length !== 5) {
|
|
77
|
-
this.cronMatcherCache.set(expression, null);
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
const [minuteField, hourField, dayField, monthField, weekdayField] = fields;
|
|
81
|
-
const minuteMatcher = this.parseField(minuteField, 0, 59);
|
|
82
|
-
const hourMatcher = this.parseField(hourField, 0, 23);
|
|
83
|
-
const dayMatcher = this.parseField(dayField, 1, 31);
|
|
84
|
-
const monthMatcher = this.parseField(monthField, 1, 12);
|
|
85
|
-
const weekdayMatcher = this.parseField(weekdayField, 0, 7);
|
|
86
|
-
if (!minuteMatcher || !hourMatcher || !dayMatcher || !monthMatcher || !weekdayMatcher) {
|
|
87
|
-
this.cronMatcherCache.set(expression, null);
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
const matcher = (date)=>{
|
|
91
|
-
const weekday = date.getDay();
|
|
92
|
-
return minuteMatcher(date.getMinutes()) && hourMatcher(date.getHours()) && dayMatcher(date.getDate()) && monthMatcher(date.getMonth() + 1) && weekdayMatcher(weekday);
|
|
93
|
-
};
|
|
94
|
-
this.cronMatcherCache.set(expression, matcher);
|
|
95
|
-
return matcher;
|
|
96
|
-
}
|
|
97
|
-
parseField(field, min, max) {
|
|
98
|
-
const segments = field.split(',');
|
|
99
|
-
const predicates = [];
|
|
100
|
-
for (const rawSegment of segments){
|
|
101
|
-
const segment = rawSegment.trim();
|
|
102
|
-
if (!segment) return null;
|
|
103
|
-
const stepParts = segment.split('/');
|
|
104
|
-
if (stepParts.length > 2) return null;
|
|
105
|
-
const baseSegment = stepParts[0];
|
|
106
|
-
const step = stepParts.length === 2 ? Number(stepParts[1]) : 1;
|
|
107
|
-
if (!Number.isInteger(step) || step <= 0) return null;
|
|
108
|
-
const range = this.parseRange(baseSegment, min, max);
|
|
109
|
-
if (!range) return null;
|
|
110
|
-
predicates.push((value)=>{
|
|
111
|
-
if (value < range.start || value > range.end) return false;
|
|
112
|
-
return (value - range.start) % step === 0;
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
return (value)=>predicates.some((predicate)=>predicate(value));
|
|
116
|
-
}
|
|
117
|
-
parseRange(segment, min, max) {
|
|
118
|
-
if (segment === '*') {
|
|
119
|
-
return {
|
|
120
|
-
start: min,
|
|
121
|
-
end: max
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
const bounds = segment.split('-');
|
|
125
|
-
if (bounds.length === 1) {
|
|
126
|
-
const value = this.parseCronNumber(bounds[0], min, max);
|
|
127
|
-
if (value === null) return null;
|
|
128
|
-
return {
|
|
129
|
-
start: value,
|
|
130
|
-
end: value
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
if (bounds.length !== 2) return null;
|
|
134
|
-
const start = this.parseCronNumber(bounds[0], min, max);
|
|
135
|
-
const end = this.parseCronNumber(bounds[1], min, max);
|
|
136
|
-
if (start === null || end === null || start > end) return null;
|
|
137
|
-
return {
|
|
138
|
-
start,
|
|
139
|
-
end
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
parseCronNumber(raw, min, max) {
|
|
143
|
-
const value = Number(raw);
|
|
144
|
-
if (!Number.isInteger(value)) return null;
|
|
145
|
-
// Cron allows both 0 and 7 for Sunday.
|
|
146
|
-
const normalized = max === 7 && value === 7 ? 0 : value;
|
|
147
|
-
if (normalized < min || normalized > max) return null;
|
|
148
|
-
return normalized;
|
|
149
|
-
}
|
|
150
|
-
onModuleDestroy() {
|
|
151
|
-
this.running = false;
|
|
152
|
-
for (const timer of this.intervalTimers){
|
|
153
|
-
clearInterval(timer);
|
|
154
|
-
}
|
|
155
|
-
for (const timer of this.cronTimers){
|
|
156
|
-
clearInterval(timer);
|
|
157
|
-
}
|
|
158
|
-
this.intervalTimers = [];
|
|
159
|
-
this.cronTimers = [];
|
|
160
|
-
this.lastCronMinute.clear();
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
ScheduleExecutor = _ts_decorate([
|
|
164
|
-
Injectable(),
|
|
165
|
-
_ts_metadata("design:type", Function),
|
|
166
|
-
_ts_metadata("design:paramtypes", [
|
|
167
|
-
typeof ScheduleRegistry === "undefined" ? Object : ScheduleRegistry
|
|
168
|
-
])
|
|
169
|
-
], ScheduleExecutor);
|