@zintrust/core 2.4.5 → 2.4.7
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 +1 -1
- package/src/cache/Cache.d.ts +3 -0
- package/src/cache/Cache.d.ts.map +1 -1
- package/src/cache/Cache.js +30 -0
- package/src/cache/CacheDriver.d.ts +7 -0
- package/src/cache/CacheDriver.d.ts.map +1 -1
- package/src/cache/drivers/RedisDriver.d.ts.map +1 -1
- package/src/cache/drivers/RedisDriver.js +24 -0
- package/src/index.js +3 -3
- package/src/scheduler/ScheduleRunner.d.ts +17 -3
- package/src/scheduler/ScheduleRunner.d.ts.map +1 -1
- package/src/scheduler/ScheduleRunner.js +54 -4
- package/src/scheduler/SchedulerRuntime.d.ts +16 -1
- package/src/scheduler/SchedulerRuntime.d.ts.map +1 -1
- package/src/scheduler/SchedulerRuntime.js +18 -0
package/package.json
CHANGED
package/src/cache/Cache.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { CacheDriver } from './CacheDriver';
|
|
7
7
|
type CacheStore = Readonly<{
|
|
8
8
|
get: <T>(key: string) => Promise<T | null>;
|
|
9
|
+
many: <T>(keys: string[]) => Promise<(T | null)[]>;
|
|
9
10
|
set: <T>(key: string, value: T, ttl?: number) => Promise<void>;
|
|
10
11
|
delete: (key: string) => Promise<void>;
|
|
11
12
|
clear: () => Promise<void>;
|
|
@@ -16,6 +17,7 @@ type CacheStore = Readonly<{
|
|
|
16
17
|
}>;
|
|
17
18
|
export declare const Cache: Readonly<{
|
|
18
19
|
get: <T>(key: string) => Promise<T | null>;
|
|
20
|
+
many: <T>(keys: string[]) => Promise<(T | null)[]>;
|
|
19
21
|
set: <T>(key: string, value: T, ttl?: number) => Promise<void>;
|
|
20
22
|
delete: (key: string) => Promise<void>;
|
|
21
23
|
clear: () => Promise<void>;
|
|
@@ -32,6 +34,7 @@ export declare const Cache: Readonly<{
|
|
|
32
34
|
*/
|
|
33
35
|
export declare const cache: Readonly<{
|
|
34
36
|
get: <T>(key: string) => Promise<T | null>;
|
|
37
|
+
many: <T>(keys: string[]) => Promise<(T | null)[]>;
|
|
35
38
|
set: <T>(key: string, value: T, ttl?: number) => Promise<void>;
|
|
36
39
|
delete: (key: string) => Promise<void>;
|
|
37
40
|
clear: () => Promise<void>;
|
package/src/cache/Cache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/Cache.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/Cache.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA2QtD,KAAK,UAAU,GAAG,QAAQ,CAAC;IACzB,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3C,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IACnD,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D,SAAS,EAAE,MAAM,WAAW,CAAC;CAC9B,CAAC,CAAC;AA0IH,eAAO,MAAM,KAAK;UAnTC,CAAC,OAAO,MAAM,KAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;WAsDjC,CAAC,QAAQ,MAAM,EAAE,KAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;UAM1C,CAAC,OAAO,MAAM,SAAS,CAAC,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;kBAkBjD,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;iBAiBtB,OAAO,CAAC,IAAI,CAAC;eAgBb,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;qBAenB,MAAM,sBAAe,OAAO,CAAC,MAAM,CAAC;qBAapC,MAAM,sBAAe,OAAO,CAAC,MAAM,CAAC;qBAgB5C,WAAW;iCA6IG,MAAM,KAAG,OAAO;mBA3H9B,MAAM,KAAG,UAAU;iBAuHvB,IAAI;EA4BpB,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,KAAK;UArUC,CAAC,OAAO,MAAM,KAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;WAsDjC,CAAC,QAAQ,MAAM,EAAE,KAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;UAM1C,CAAC,OAAO,MAAM,SAAS,CAAC,QAAQ,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;kBAkBjD,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;iBAiBtB,OAAO,CAAC,IAAI,CAAC;eAgBb,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;qBAenB,MAAM,sBAAe,OAAO,CAAC,MAAM,CAAC;qBAapC,MAAM,sBAAe,OAAO,CAAC,MAAM,CAAC;qBAgB5C,WAAW;iCA6IG,MAAM,KAAG,OAAO;mBA3H9B,MAAM,KAAG,UAAU;iBAuHvB,IAAI;EAiCI,CAAC"}
|
package/src/cache/Cache.js
CHANGED
|
@@ -95,6 +95,33 @@ const get = async (key) => {
|
|
|
95
95
|
SystemTraceBridge.emitCache('get', prefixedKey, Date.now() - startedAt, value !== null, value, DEFAULT_STORE_NAME);
|
|
96
96
|
return value;
|
|
97
97
|
};
|
|
98
|
+
// Batched read: one round-trip via driver.many() (e.g. Redis MGET) when supported,
|
|
99
|
+
// otherwise individual gets. Results stay aligned to the requested key order.
|
|
100
|
+
const runMany = async (driver, keys, storeName) => {
|
|
101
|
+
if (keys.length === 0)
|
|
102
|
+
return [];
|
|
103
|
+
const prefixedKeys = keys.map((key) => autoPrefixKey(key));
|
|
104
|
+
const startedAt = Date.now();
|
|
105
|
+
const many = driver.many;
|
|
106
|
+
let values;
|
|
107
|
+
const batched = typeof many === 'function';
|
|
108
|
+
if (batched) {
|
|
109
|
+
values = await many(prefixedKeys);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
values = await Promise.all(prefixedKeys.map(async (prefixedKey) => driver.get(prefixedKey)));
|
|
113
|
+
}
|
|
114
|
+
const elapsed = Date.now() - startedAt;
|
|
115
|
+
const normalized = prefixedKeys.map((_prefixedKey, index) => values[index] ?? null);
|
|
116
|
+
const hitCount = normalized.filter((value) => value !== null).length;
|
|
117
|
+
// One trace record per round-trip: a driver-level `many` (e.g. Redis MGET) is a single
|
|
118
|
+
// network call, so it must not look like N separate gets. Drivers without `many` fall back
|
|
119
|
+
// to N gets, which is reflected with the `gets` label.
|
|
120
|
+
const label = batched ? 'mget' : 'gets';
|
|
121
|
+
SystemTraceBridge.emitCache('get', `${label}(${prefixedKeys.length}/${hitCount} hit) ${prefixedKeys.join(',')}`, elapsed, hitCount > 0, undefined, storeName);
|
|
122
|
+
return normalized;
|
|
123
|
+
};
|
|
124
|
+
const many = async (keys) => runMany(getDriverInstance(), keys, DEFAULT_STORE_NAME);
|
|
98
125
|
/**
|
|
99
126
|
* Store an item in the cache
|
|
100
127
|
*/
|
|
@@ -169,6 +196,7 @@ const store = (name) => {
|
|
|
169
196
|
SystemTraceBridge.emitCache('get', prefixedKey, Date.now() - startedAt, value !== null, value, String(name ?? DEFAULT_STORE_NAME));
|
|
170
197
|
return value;
|
|
171
198
|
};
|
|
199
|
+
const manyFromStore = async (keys) => runMany(getDriverInstance(name), keys, String(name ?? DEFAULT_STORE_NAME));
|
|
172
200
|
const setInStore = async (key, value, ttl) => {
|
|
173
201
|
const prefixedKey = autoPrefixKey(key);
|
|
174
202
|
const startedAt = Date.now();
|
|
@@ -220,6 +248,7 @@ const store = (name) => {
|
|
|
220
248
|
};
|
|
221
249
|
return Object.freeze({
|
|
222
250
|
get: getFromStore,
|
|
251
|
+
many: manyFromStore,
|
|
223
252
|
set: setInStore,
|
|
224
253
|
delete: delFromStore,
|
|
225
254
|
clear: clearStore,
|
|
@@ -242,6 +271,7 @@ const getRedisClient = (storeName) => {
|
|
|
242
271
|
// Sealed namespace with cache functionality
|
|
243
272
|
export const Cache = Object.freeze({
|
|
244
273
|
get,
|
|
274
|
+
many,
|
|
245
275
|
set,
|
|
246
276
|
delete: del,
|
|
247
277
|
clear,
|
|
@@ -7,6 +7,13 @@ export interface CacheDriver {
|
|
|
7
7
|
* Get an item from the cache
|
|
8
8
|
*/
|
|
9
9
|
get<T>(key: string): Promise<T | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Get many items from the cache in a single round-trip when the driver
|
|
12
|
+
* supports it (e.g. Redis MGET). Results are returned in the same order as
|
|
13
|
+
* the requested keys, with `null` for misses. Optional: callers fall back to
|
|
14
|
+
* issuing individual `get` calls when a driver does not implement it.
|
|
15
|
+
*/
|
|
16
|
+
many?<T>(keys: string[]): Promise<(T | null)[]>;
|
|
10
17
|
/**
|
|
11
18
|
* Store an item in the cache
|
|
12
19
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CacheDriver.d.ts","sourceRoot":"","sources":["../../../src/cache/CacheDriver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEvC;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnC;;OAEG;IACH,SAAS,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE1D;;OAEG;IACH,SAAS,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE1D;;OAEG;IACH,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1B;;;OAGG;IACH,cAAc,CAAC,IAAI,OAAO,CAAC;CAC5B;AAGD,eAAO,MAAM,sBAAsB,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"CacheDriver.d.ts","sourceRoot":"","sources":["../../../src/cache/CacheDriver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAEvC;;;;;OAKG;IACH,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IAEhD;;OAEG;IACH,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEnC;;OAEG;IACH,SAAS,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE1D;;OAEG;IACH,SAAS,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE1D;;OAEG;IACH,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1B;;;OAGG;IACH,cAAc,CAAC,IAAI,OAAO,CAAC;CAC5B;AAGD,eAAO,MAAM,sBAAsB,gBAAgB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../../../src/cache/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../../../src/cache/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAsdtD;;GAEG;AACH,eAAO,MAAM,WAAW;kBAbL,WAAW;EAe5B,CAAC"}
|
|
@@ -69,6 +69,30 @@ const createCacheDriverFromIoredisClient = (client) => ({
|
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
|
+
async many(keys) {
|
|
73
|
+
if (keys.length === 0)
|
|
74
|
+
return [];
|
|
75
|
+
try {
|
|
76
|
+
const prefixedKeys = keys.map((key) => RedisKeys.createCacheKey(key));
|
|
77
|
+
const values = typeof client.mget === 'function'
|
|
78
|
+
? await client.mget(...prefixedKeys)
|
|
79
|
+
: await Promise.all(prefixedKeys.map(async (prefixedKey) => client.get(prefixedKey)));
|
|
80
|
+
return values.map((value) => {
|
|
81
|
+
if (value === null || value === undefined)
|
|
82
|
+
return null;
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(value);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
Logger.error('Redis MGET failed', error);
|
|
93
|
+
return keys.map(() => null);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
72
96
|
async set(key, value, ttl) {
|
|
73
97
|
try {
|
|
74
98
|
const prefixedKey = RedisKeys.createCacheKey(key);
|
package/src/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @zintrust/core v2.4.
|
|
2
|
+
* @zintrust/core v2.4.7
|
|
3
3
|
*
|
|
4
4
|
* ZinTrust Framework - Production-Grade TypeScript Backend
|
|
5
5
|
* Built for performance, type safety, and exceptional developer experience
|
|
6
6
|
*
|
|
7
7
|
* Build Information:
|
|
8
|
-
* Built: 2026-06-
|
|
8
|
+
* Built: 2026-06-01T22:49:09.622Z
|
|
9
9
|
* Node: >=20.0.0
|
|
10
10
|
* License: MIT
|
|
11
11
|
*
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* Available at runtime for debugging and health checks
|
|
22
22
|
*/
|
|
23
23
|
export const ZINTRUST_VERSION = '0.1.41';
|
|
24
|
-
export const ZINTRUST_BUILD_DATE = '2026-06-
|
|
24
|
+
export const ZINTRUST_BUILD_DATE = '2026-06-01T22:49:09.586Z'; // Replaced during build
|
|
25
25
|
export { Application } from './boot/Application.js';
|
|
26
26
|
export { AwsSigV4 } from './common/index.js';
|
|
27
27
|
export { SignedRequest } from './security/SignedRequest.js';
|
|
@@ -2,23 +2,37 @@
|
|
|
2
2
|
* ScheduleRunner
|
|
3
3
|
* Lightweight, zero-dependency schedule runner for long-running runtimes
|
|
4
4
|
*/
|
|
5
|
-
import { type ScheduleRunState } from './state/ScheduleStateStore';
|
|
5
|
+
import { type IScheduleStateStore, type ScheduleRunState } from './state/ScheduleStateStore';
|
|
6
6
|
import type { ISchedule, IScheduleKernel } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* Result of a `runIfDue` invocation.
|
|
9
|
+
* - `ran: true` → the handler was invoked this call.
|
|
10
|
+
* - `ran: false` → skipped; `reason` explains why and `nextRunAt` (when present)
|
|
11
|
+
* is the epoch-ms timestamp at which the schedule next becomes due.
|
|
12
|
+
*/
|
|
13
|
+
export type RunIfDueResult = Readonly<{
|
|
14
|
+
ran: boolean;
|
|
15
|
+
reason?: 'disabled' | 'not-due' | 'no-cadence';
|
|
16
|
+
nextRunAt?: number;
|
|
17
|
+
outcome?: 'success' | 'failure';
|
|
18
|
+
}>;
|
|
7
19
|
type ScheduleRunner = {
|
|
8
20
|
register: (schedule: ISchedule) => void;
|
|
9
21
|
start: (kernel?: IScheduleKernel) => void;
|
|
10
22
|
stop: (timeoutMs?: number) => Promise<void>;
|
|
11
23
|
list: () => ISchedule[];
|
|
12
24
|
runOnce: (name: string, kernel?: IScheduleKernel) => Promise<void>;
|
|
25
|
+
runIfDue: (name: string, kernel?: IScheduleKernel) => Promise<RunIfDueResult>;
|
|
13
26
|
getState: (name: string) => Promise<ScheduleRunState | null>;
|
|
14
27
|
listStates: () => Promise<Array<{
|
|
15
28
|
name: string;
|
|
16
29
|
state: ScheduleRunState;
|
|
17
30
|
}>>;
|
|
31
|
+
setStore: (store: IScheduleStateStore) => void;
|
|
18
32
|
};
|
|
19
|
-
export declare const create: () => Readonly<ScheduleRunner>;
|
|
33
|
+
export declare const create: (store?: IScheduleStateStore) => Readonly<ScheduleRunner>;
|
|
20
34
|
declare const _default: {
|
|
21
|
-
create: () => Readonly<ScheduleRunner>;
|
|
35
|
+
create: (store?: IScheduleStateStore) => Readonly<ScheduleRunner>;
|
|
22
36
|
};
|
|
23
37
|
export default _default;
|
|
24
38
|
//# sourceMappingURL=ScheduleRunner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScheduleRunner.d.ts","sourceRoot":"","sources":["../../../src/scheduler/ScheduleRunner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,
|
|
1
|
+
{"version":3,"file":"ScheduleRunner.d.ts","sourceRoot":"","sources":["../../../src/scheduler/ScheduleRunner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACtB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAkBnE;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,YAAY,CAAC;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CACjC,CAAC,CAAC;AAEH,KAAK,cAAc,GAAG;IACpB,QAAQ,EAAE,CAAC,QAAQ,EAAE,SAAS,KAAK,IAAI,CAAC;IACxC,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;IAC1C,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,EAAE,MAAM,SAAS,EAAE,CAAC;IACxB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAC9E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAC7D,UAAU,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,gBAAgB,CAAA;KAAE,CAAC,CAAC,CAAC;IAC5E,QAAQ,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAChD,CAAC;AAwZF,eAAO,MAAM,MAAM,GAAI,QAAQ,mBAAmB,KAAG,QAAQ,CAAC,cAAc,CAmC3E,CAAC;;qBAnC6B,mBAAmB,KAAG,QAAQ,CAAC,cAAc,CAAC;;AAqC7E,wBAA0B"}
|
|
@@ -146,7 +146,8 @@ const createRegister = (runner, invokeHandler) => (schedule) => {
|
|
|
146
146
|
scheduleNext(state, runner.kernel, invokeHandler, 'success', runner.store);
|
|
147
147
|
}
|
|
148
148
|
};
|
|
149
|
-
const createInvokeHandler = (
|
|
149
|
+
const createInvokeHandler = (runner) => async (state, kernel) => {
|
|
150
|
+
const store = runner.store;
|
|
150
151
|
if (state.isRunning) {
|
|
151
152
|
Logger.info(`Skipping overlapping run for schedule: ${state.schedule.name}`);
|
|
152
153
|
return 'failure';
|
|
@@ -263,30 +264,79 @@ const createRunOnce = (runner, invokeHandler) => async (name, kernel) => {
|
|
|
263
264
|
return;
|
|
264
265
|
await invokeHandler(state, kernel ?? runner.kernel);
|
|
265
266
|
};
|
|
266
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Computes the epoch-ms timestamp at which a schedule next becomes due,
|
|
269
|
+
* measured from its last successful run. Cron takes precedence over interval.
|
|
270
|
+
* Returns `null` when the schedule has neither a cron nor a positive interval
|
|
271
|
+
* (i.e. it has no cadence of its own and is always considered due).
|
|
272
|
+
*/
|
|
273
|
+
const computeDueAtMs = (schedule, lastRunAt) => {
|
|
274
|
+
if (typeof schedule.cron === 'string' && schedule.cron.trim().length > 0) {
|
|
275
|
+
const tz = typeof schedule.timezone === 'string' && schedule.timezone.trim().length > 0
|
|
276
|
+
? schedule.timezone
|
|
277
|
+
: undefined;
|
|
278
|
+
return Cron.nextRunAtMs(lastRunAt, schedule.cron, tz);
|
|
279
|
+
}
|
|
280
|
+
if (typeof schedule.intervalMs === 'number' && schedule.intervalMs > 0) {
|
|
281
|
+
return lastRunAt + Math.floor(schedule.intervalMs);
|
|
282
|
+
}
|
|
283
|
+
return null;
|
|
284
|
+
};
|
|
285
|
+
const createRunIfDue = (runner, invokeHandler) => async (name, kernel) => {
|
|
286
|
+
const state = runner.schedules.get(name);
|
|
287
|
+
if (state === undefined)
|
|
288
|
+
throw ErrorFactory.createNotFoundError(`Schedule not found: ${name}`);
|
|
289
|
+
if (state.schedule.enabled === false)
|
|
290
|
+
return { ran: false, reason: 'disabled' };
|
|
291
|
+
// Prefer the durable, persisted last-run time over the in-memory mirror so
|
|
292
|
+
// this works across stateless invocations (e.g. Cloudflare Worker cron ticks)
|
|
293
|
+
// when a persistent store has been wired via setStore().
|
|
294
|
+
const persisted = await runner.store.get(name);
|
|
295
|
+
const lastRunAt = persisted?.lastSuccessAt ?? persisted?.lastRunAt ?? state.lastRunAt ?? undefined;
|
|
296
|
+
if (typeof lastRunAt === 'number') {
|
|
297
|
+
const dueAt = computeDueAtMs(state.schedule, lastRunAt);
|
|
298
|
+
if (dueAt === null) {
|
|
299
|
+
// No cadence configured: nothing to gate on, run it.
|
|
300
|
+
const outcome = await invokeHandler(state, kernel ?? runner.kernel);
|
|
301
|
+
return { ran: true, outcome, reason: 'no-cadence' };
|
|
302
|
+
}
|
|
303
|
+
if (nowMs() < dueAt) {
|
|
304
|
+
return { ran: false, reason: 'not-due', nextRunAt: dueAt };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const outcome = await invokeHandler(state, kernel ?? runner.kernel);
|
|
308
|
+
return { ran: true, outcome };
|
|
309
|
+
};
|
|
310
|
+
export const create = (store) => {
|
|
267
311
|
const runner = {
|
|
268
312
|
schedules: new Map(),
|
|
269
313
|
started: false,
|
|
270
314
|
kernel: undefined,
|
|
271
|
-
store: InMemoryScheduleStateStore.create(),
|
|
315
|
+
store: store ?? InMemoryScheduleStateStore.create(),
|
|
272
316
|
};
|
|
273
|
-
const invokeHandler = createInvokeHandler(runner
|
|
317
|
+
const invokeHandler = createInvokeHandler(runner);
|
|
274
318
|
const register = createRegister(runner, invokeHandler);
|
|
275
319
|
const start = createStart(runner, invokeHandler);
|
|
276
320
|
const stopRaw = createStop(runner);
|
|
277
321
|
const stop = createStopWithTimeout(stopRaw);
|
|
278
322
|
const list = createList(runner);
|
|
279
323
|
const runOnce = createRunOnce(runner, invokeHandler);
|
|
324
|
+
const runIfDue = createRunIfDue(runner, invokeHandler);
|
|
280
325
|
const getState = async (name) => runner.store.get(name);
|
|
281
326
|
const listStates = async () => runner.store.list();
|
|
327
|
+
const setStore = (next) => {
|
|
328
|
+
runner.store = next;
|
|
329
|
+
};
|
|
282
330
|
return Object.freeze({
|
|
283
331
|
register,
|
|
284
332
|
start,
|
|
285
333
|
stop,
|
|
286
334
|
list,
|
|
287
335
|
runOnce,
|
|
336
|
+
runIfDue,
|
|
288
337
|
getState,
|
|
289
338
|
listStates,
|
|
339
|
+
setStore,
|
|
290
340
|
});
|
|
291
341
|
};
|
|
292
342
|
export default { create };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RunIfDueResult } from './ScheduleRunner';
|
|
2
|
+
import type { IScheduleStateStore, ScheduleRunState } from './state/ScheduleStateStore';
|
|
2
3
|
import type { ISchedule, IScheduleKernel } from './types';
|
|
3
4
|
export declare const SchedulerRuntime: Readonly<{
|
|
4
5
|
registerMany: (schedules: ReadonlyArray<ISchedule>, source?: "core" | "app") => void;
|
|
@@ -10,6 +11,20 @@ export declare const SchedulerRuntime: Readonly<{
|
|
|
10
11
|
state: ScheduleRunState | null;
|
|
11
12
|
}>>;
|
|
12
13
|
runOnce(name: string, kernel?: IScheduleKernel): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Runs a schedule only if it is due, based on its persisted last-run time and
|
|
16
|
+
* its configured cron/interval cadence. Intended for stateless cron triggers
|
|
17
|
+
* (e.g. a Cloudflare Worker `scheduled` handler) where the timer-based
|
|
18
|
+
* `start()` loop cannot run. For the due check to survive cold starts, wire a
|
|
19
|
+
* persistent state store via {@link useStateStore} first.
|
|
20
|
+
*/
|
|
21
|
+
runIfDue(name: string, kernel?: IScheduleKernel): Promise<RunIfDueResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Swaps the schedule state store (default is in-memory). Provide a durable
|
|
24
|
+
* implementation (Redis/KV/D1-backed) so `runIfDue` can read `lastRunAt`
|
|
25
|
+
* across stateless invocations. Call this before `registerMany`/`runIfDue`.
|
|
26
|
+
*/
|
|
27
|
+
useStateStore(store: IScheduleStateStore): void;
|
|
13
28
|
}>;
|
|
14
29
|
export default SchedulerRuntime;
|
|
15
30
|
//# sourceMappingURL=SchedulerRuntime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SchedulerRuntime.d.ts","sourceRoot":"","sources":["../../../src/scheduler/SchedulerRuntime.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"SchedulerRuntime.d.ts","sourceRoot":"","sources":["../../../src/scheduler/SchedulerRuntime.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AACjG,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAgEnE,eAAO,MAAM,gBAAgB;8BAzBhB,aAAa,CAAC,SAAS,CAAC,WAC3B,MAAM,GAAG,KAAK,KACrB,IAAI;mBAyBU,eAAe,GAAG,IAAI;qBAqBd,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAOrC,SAAS,EAAE;qBAGI,OAAO,CAAC,KAAK,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;kBAkB1E,MAAM,WAAW,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAGpE;;;;;;OAMG;mBACkB,MAAM,WAAW,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IAG/E;;;;OAIG;yBACkB,mBAAmB,GAAG,IAAI;EAGjB,CAAC;AAEjC,eAAe,gBAAgB,CAAC"}
|
|
@@ -75,5 +75,23 @@ export const SchedulerRuntime = Object.freeze({
|
|
|
75
75
|
async runOnce(name, kernel) {
|
|
76
76
|
await state.runner.runOnce(name, kernel);
|
|
77
77
|
},
|
|
78
|
+
/**
|
|
79
|
+
* Runs a schedule only if it is due, based on its persisted last-run time and
|
|
80
|
+
* its configured cron/interval cadence. Intended for stateless cron triggers
|
|
81
|
+
* (e.g. a Cloudflare Worker `scheduled` handler) where the timer-based
|
|
82
|
+
* `start()` loop cannot run. For the due check to survive cold starts, wire a
|
|
83
|
+
* persistent state store via {@link useStateStore} first.
|
|
84
|
+
*/
|
|
85
|
+
async runIfDue(name, kernel) {
|
|
86
|
+
return state.runner.runIfDue(name, kernel);
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* Swaps the schedule state store (default is in-memory). Provide a durable
|
|
90
|
+
* implementation (Redis/KV/D1-backed) so `runIfDue` can read `lastRunAt`
|
|
91
|
+
* across stateless invocations. Call this before `registerMany`/`runIfDue`.
|
|
92
|
+
*/
|
|
93
|
+
useStateStore(store) {
|
|
94
|
+
state.runner.setStore(store);
|
|
95
|
+
},
|
|
78
96
|
});
|
|
79
97
|
export default SchedulerRuntime;
|