@jini-ai/daemon 0.3.8 → 0.5.1
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 +124 -0
- package/README.md +50 -8
- package/dist/agent-executor/contracts.d.ts +736 -0
- package/dist/agent-executor/contracts.js +28 -0
- package/dist/agent-executor/event-translation.d.ts +87 -0
- package/dist/agent-executor/event-translation.js +230 -0
- package/dist/agent-executor/events.d.ts +190 -0
- package/dist/agent-executor/events.js +848 -0
- package/dist/agent-executor/handoff.d.ts +18 -0
- package/dist/agent-executor/handoff.js +16 -0
- package/dist/agent-executor/index.d.ts +98 -0
- package/dist/agent-executor/index.js +98 -0
- package/dist/agent-executor/launch.d.ts +375 -0
- package/dist/agent-executor/launch.js +1001 -0
- package/dist/agent-executor/mcp-bridge.d.ts +338 -0
- package/dist/agent-executor/mcp-bridge.js +835 -0
- package/dist/agent-executor/values.d.ts +5 -0
- package/dist/agent-executor/values.js +13 -0
- package/dist/agent-executor.d.ts +375 -176
- package/dist/agent-executor.js +388 -335
- package/dist/close-status.d.ts +11 -8
- package/dist/close-status.js +31 -19
- package/dist/continuation/continuation-transport.d.ts +3 -1
- package/dist/continuation/continuation-transport.js +1 -1
- package/dist/continuation/journal.d.ts +12 -5
- package/dist/continuation/journal.js +4 -4
- package/dist/continuation/run-scoped-context-store.d.ts +13 -4
- package/dist/continuation/run-scoped-context-store.js +6 -6
- package/dist/continuation/run-start-handler.d.ts +1 -1
- package/dist/continuation/run-start-handler.js +3 -16
- package/dist/delegated-tool-bridge.d.ts +7 -3
- package/dist/delegated-tool-bridge.js +32 -31
- package/dist/event-log.d.ts +7 -5
- package/dist/event-log.js +12 -11
- package/dist/frontend-capability-tools.d.ts +1 -1
- package/dist/frontend-capability-tools.js +3 -2
- package/dist/frontend-session-registry.d.ts +32 -9
- package/dist/frontend-session-registry.js +12 -11
- package/dist/http/active-context.d.ts +105 -0
- package/dist/http/active-context.js +67 -0
- package/dist/http/agents.d.ts +68 -0
- package/dist/http/agents.js +12 -0
- package/dist/http/attachments.d.ts +618 -0
- package/dist/http/attachments.js +1034 -0
- package/dist/http/cancel-owned-runs.d.ts +49 -0
- package/dist/http/cancel-owned-runs.js +35 -0
- package/dist/http/component-catalog.d.ts +84 -0
- package/dist/http/component-catalog.js +64 -0
- package/dist/http/connectors.d.ts +209 -0
- package/dist/http/connectors.js +409 -0
- package/dist/http/daemon-status.d.ts +76 -0
- package/dist/http/daemon-status.js +55 -0
- package/dist/http/db-ops.d.ts +123 -0
- package/dist/http/db-ops.js +189 -0
- package/dist/http/delegated-tools.d.ts +113 -0
- package/dist/http/delegated-tools.js +230 -0
- package/dist/http/frontend-control.d.ts +136 -0
- package/dist/http/frontend-control.js +79 -0
- package/dist/http/frontend-sessions.d.ts +110 -0
- package/dist/http/frontend-sessions.js +180 -0
- package/dist/http/health.d.ts +95 -0
- package/dist/http/health.js +40 -0
- package/dist/http/host-tools.d.ts +214 -0
- package/dist/http/host-tools.js +329 -0
- package/dist/http/index.d.ts +52 -0
- package/dist/http/index.js +27 -0
- package/dist/http/media.d.ts +146 -0
- package/dist/http/media.js +346 -0
- package/dist/http/memory.d.ts +265 -0
- package/dist/http/memory.js +328 -0
- package/dist/http/model-proxy.d.ts +49 -0
- package/dist/http/model-proxy.js +642 -0
- package/dist/http/remote-run-events.d.ts +70 -0
- package/dist/http/remote-run-events.js +224 -0
- package/dist/http/research.d.ts +48 -0
- package/dist/http/research.js +315 -0
- package/dist/http/route-manifest.d.ts +35 -0
- package/dist/http/route-manifest.js +103 -0
- package/dist/http/routines.d.ts +120 -0
- package/dist/http/routines.js +298 -0
- package/dist/http/run-credentials.d.ts +73 -0
- package/dist/http/run-credentials.js +71 -0
- package/dist/http/runs.d.ts +91 -0
- package/dist/http/runs.js +221 -0
- package/dist/http/terminals.d.ts +138 -0
- package/dist/http/terminals.js +325 -0
- package/dist/http/tool-catalog.d.ts +78 -0
- package/dist/http/tool-catalog.js +66 -0
- package/dist/http/workspace-root.d.ts +77 -0
- package/dist/http/workspace-root.js +63 -0
- package/dist/http/xai.d.ts +90 -0
- package/dist/http/xai.js +655 -0
- package/dist/image-prompt-delivery.d.ts +13 -3
- package/dist/image-prompt-delivery.js +5 -5
- package/dist/index.d.ts +556 -4
- package/dist/index.js +556 -4
- package/dist/legacy-data-migration.d.ts +59 -8
- package/dist/legacy-data-migration.js +38 -33
- package/dist/read-only-tools.d.ts +130 -0
- package/dist/read-only-tools.js +115 -0
- package/dist/remote-tool-bridge.d.ts +11 -5
- package/dist/remote-tool-bridge.js +15 -15
- package/dist/routines/index.d.ts +1 -1
- package/dist/routines/index.js +1 -1
- package/dist/routines/routine-store.d.ts +34 -12
- package/dist/routines/routine-store.js +21 -20
- package/dist/routines/schedule.d.ts +48 -10
- package/dist/routines/schedule.js +21 -21
- package/dist/routines/scheduler.d.ts +32 -11
- package/dist/routines/scheduler.js +61 -57
- package/dist/routines/types.d.ts +10 -3
- package/dist/run/core/result.d.ts +6 -2
- package/dist/run/core/result.js +3 -3
- package/dist/run/core/retry.d.ts +17 -4
- package/dist/run/core/retry.js +7 -11
- package/dist/run/diagnostics/diagnostics.d.ts +14 -4
- package/dist/run/diagnostics/diagnostics.js +7 -7
- package/dist/run/index.d.ts +1 -1
- package/dist/run/index.js +1 -1
- package/dist/run-credentials.d.ts +138 -0
- package/dist/run-credentials.js +152 -0
- package/dist/run-lifecycle.d.ts +92 -33
- package/dist/run-lifecycle.js +81 -74
- package/dist/scheduler.d.ts +8 -0
- package/dist/scheduler.js +2 -0
- package/dist/session-coordination.d.ts +115 -0
- package/dist/session-coordination.js +111 -0
- package/dist/store/agent-sessions/errors.d.ts +8 -0
- package/dist/store/agent-sessions/errors.js +10 -0
- package/dist/store/agent-sessions/index.d.ts +12 -0
- package/dist/store/agent-sessions/index.js +19 -0
- package/dist/store/agent-sessions/pglite.d.ts +12 -0
- package/dist/store/agent-sessions/pglite.js +7 -0
- package/dist/store/agent-sessions/ports.d.ts +24 -0
- package/dist/store/agent-sessions/ports.js +2 -0
- package/dist/store/agent-sessions/postgres.d.ts +12 -0
- package/dist/store/agent-sessions/postgres.js +7 -0
- package/dist/store/agent-sessions/schema.d.ts +3 -0
- package/dist/store/agent-sessions/schema.js +23 -0
- package/dist/store/agent-sessions/sql.d.ts +35 -0
- package/dist/store/agent-sessions/sql.js +59 -0
- package/dist/store/agent-sessions/sqlite.d.ts +117 -0
- package/dist/store/agent-sessions/sqlite.js +128 -0
- package/dist/store/event-log/sqlite.d.ts +75 -0
- package/dist/store/event-log/sqlite.js +230 -0
- package/dist/surface-exchanges.d.ts +97 -0
- package/dist/surface-exchanges.js +153 -0
- package/dist/terminal-session.d.ts +43 -11
- package/dist/terminal-session.js +22 -20
- package/dist/tokens.js +4 -4
- package/dist/tool-audit.d.ts +114 -0
- package/dist/tool-audit.js +95 -0
- package/dist/tool-executor.d.ts +22 -7
- package/dist/tool-executor.js +16 -16
- package/dist/tool-result-media.d.ts +3 -1
- package/dist/tool-result-media.js +1 -1
- package/dist/tool-result-surfaces.d.ts +3 -1
- package/dist/tool-result-surfaces.js +1 -1
- package/package.json +127 -9
|
@@ -23,22 +23,34 @@ export interface RoutineUpdateInput {
|
|
|
23
23
|
/**
|
|
24
24
|
* Storage-agnostic CRUD + read-only run-history port for routines. `@jini-ai/daemon` ships
|
|
25
25
|
* {@link createInMemoryRoutineStore} as the reference implementation; a durable adapter
|
|
26
|
-
* (`@jini-ai/sqlite`, future work) implements the same interface.
|
|
26
|
+
* (`@jini-ai/sqlite-chat`, future work) implements the same interface.
|
|
27
27
|
*/
|
|
28
28
|
export interface RoutineStore {
|
|
29
29
|
list(): Promise<readonly Routine[]>;
|
|
30
30
|
/** Returns `null` when no routine with `id` exists — never throws for a missing id. */
|
|
31
|
-
get(
|
|
31
|
+
get(args: {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
}): Promise<Routine | null>;
|
|
32
34
|
/** Assigns and returns the new routine's `id` — never supplied by the caller, matching `EventLog.append`'s cursor-assignment precedent. */
|
|
33
|
-
create(
|
|
35
|
+
create(requiredArgs: Pick<RoutineCreateInput, "name" | "prompt" | "schedule" | "target">, optionalArgs?: Pick<RoutineCreateInput, "skillId" | "agentId" | "context" | "enabled">): Promise<Routine>;
|
|
34
36
|
/** Applies a partial patch and returns the updated routine, or `null` if `id` does not exist. */
|
|
35
|
-
update(
|
|
37
|
+
update(args: {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly patch: RoutineUpdateInput;
|
|
40
|
+
}): Promise<Routine | null>;
|
|
36
41
|
/** Returns whether a routine with `id` existed (and was removed). */
|
|
37
|
-
delete(
|
|
42
|
+
delete(args: {
|
|
43
|
+
readonly id: string;
|
|
44
|
+
}): Promise<boolean>;
|
|
38
45
|
/** Returns up to `limit` runs for `routineId`, newest first. Returns `[]` for an unknown routine rather than throwing — mirrors `list()`'s empty-collection convention. */
|
|
39
|
-
listRuns(
|
|
46
|
+
listRuns(args: {
|
|
47
|
+
readonly routineId: string;
|
|
48
|
+
readonly limit: number;
|
|
49
|
+
}): Promise<readonly RoutineRun[]>;
|
|
40
50
|
/** Returns the most recently started run for `routineId`, or `null` if none has ever run. */
|
|
41
|
-
getLatestRun(
|
|
51
|
+
getLatestRun(args: {
|
|
52
|
+
readonly routineId: string;
|
|
53
|
+
}): Promise<RoutineRun | null>;
|
|
42
54
|
}
|
|
43
55
|
/**
|
|
44
56
|
* The display-shaped run summary OD's own `routineDbRowToContract` embedded on `Routine.lastRun`
|
|
@@ -47,7 +59,9 @@ export interface RoutineStore {
|
|
|
47
59
|
* Kept as its own exported pure function (not inlined) so it is directly unit-testable, matching
|
|
48
60
|
* this repo's "extract to a testable pure function" convention for awkward-to-cover branches.
|
|
49
61
|
*/
|
|
50
|
-
export declare function summarizeLastRun(run
|
|
62
|
+
export declare function summarizeLastRun({ run }: {
|
|
63
|
+
readonly run: RoutineRun | null;
|
|
64
|
+
}): Record<string, unknown> | null;
|
|
51
65
|
/**
|
|
52
66
|
* `RoutineStore` plus two extra write hooks for run history that are deliberately NOT part of
|
|
53
67
|
* the public {@link RoutineStore} contract (see this module's doc comment for why run-writing
|
|
@@ -59,19 +73,27 @@ export declare function summarizeLastRun(run: RoutineRun | null): Record<string,
|
|
|
59
73
|
*/
|
|
60
74
|
export interface InMemoryRoutineStore extends RoutineStore {
|
|
61
75
|
/** Records a run — mirrors `RoutinePersistence.insertRun`'s signature minus the scheduled-slot claim (this reference store has no concept of a "slot," only a flat run history), returning `false` if `run.id` was already recorded. */
|
|
62
|
-
recordRun(
|
|
76
|
+
recordRun(args: {
|
|
77
|
+
readonly run: RoutineRun;
|
|
78
|
+
}): boolean;
|
|
63
79
|
/** Patches a previously recorded run in place. A patch for an unknown `id` is a no-op, mirroring `RoutinePersistence.updateRun`'s own silent-no-op-on-unknown-id convention (see `./scheduler.js`'s `updateRun` call sites, none of which check a return value). */
|
|
64
|
-
patchRun(
|
|
80
|
+
patchRun(args: {
|
|
81
|
+
readonly id: string;
|
|
82
|
+
readonly patch: Partial<RoutineRun>;
|
|
83
|
+
}): void;
|
|
65
84
|
}
|
|
66
85
|
/**
|
|
67
86
|
* Reference `RoutineStore` implementation: an in-process `Map` of routines plus a flat array of
|
|
68
87
|
* runs, no durable copy — matching `createInMemoryEventLog`'s own scope (a real persistent
|
|
69
|
-
* adapter is `@jini-ai/sqlite`'s job).
|
|
88
|
+
* adapter is `@jini-ai/sqlite-chat`'s job).
|
|
70
89
|
*
|
|
71
90
|
* @complexity `get`/`create`/`update`/`delete` are O(1). `list` is O(n log n) (stable id-sort for
|
|
72
91
|
* deterministic ordering). `listRuns`/`getLatestRun` are O(m log m) in the number of runs
|
|
73
92
|
* recorded for the routine (m), since each sorts by `startedAt` descending on every call rather
|
|
74
93
|
* than maintaining a secondary index — acceptable at this reference implementation's scale.
|
|
75
94
|
*/
|
|
76
|
-
export declare function createInMemoryRoutineStore(
|
|
95
|
+
export declare function createInMemoryRoutineStore(_requiredArgs: Record<string, never>, { now, newId }?: {
|
|
96
|
+
now?: () => number;
|
|
97
|
+
newId?: () => string;
|
|
98
|
+
}): InMemoryRoutineStore;
|
|
77
99
|
//# sourceMappingURL=routine-store.d.ts.map
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* comment is the explicit template named by the porting proposal,
|
|
5
5
|
* `ADS-memory/reports/proposals/PROP-http-route-packs-automation-routines-2026-07-21.md`): a
|
|
6
6
|
* storage-agnostic interface plus an in-memory reference implementation, async-only from day one
|
|
7
|
-
* (extraction-plan §2.6) so a durable `@jini-ai/sqlite` adapter is a drop-in swap later without an
|
|
7
|
+
* (extraction-plan §2.6) so a durable `@jini-ai/sqlite-chat` adapter is a drop-in swap later without an
|
|
8
8
|
* API break.
|
|
9
9
|
*
|
|
10
10
|
* No such port existed anywhere in this repo before this file — OD's own `routine.ts` route file
|
|
11
11
|
* called eight raw `db.js` SQL functions directly (`getRoutine`/`insertRoutine`/`updateRoutine`/
|
|
12
12
|
* `deleteRoutine`/`listRoutines`/`listRoutineRuns`/`getLatestRoutineRun`/`getRoutineRun`) against
|
|
13
|
-
* `routines`/`routine_runs` tables that `packages/sqlite/
|
|
13
|
+
* `routines`/`routine_runs` tables that `packages/sqlite/archived provenance ledger` already documents as
|
|
14
14
|
* explicitly out of scope for the db-barrel port. This is the missing design.
|
|
15
15
|
*
|
|
16
16
|
* **Deliberately separate from `./scheduler.js`'s `RoutinePersistence`, not a shared interface**,
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* mirrors that same split: `RoutineStore` owns Routine CRUD and *read-only* run history
|
|
22
22
|
* (`listRuns`/`getLatestRun`); it does not write run records — that stays the scheduler's job via
|
|
23
23
|
* its own separately-injected `RoutinePersistence`. A host wiring both together against one real
|
|
24
|
-
* durable table (a future `@jini-ai/sqlite` adapter) is host-level integration wiring, the same way
|
|
24
|
+
* durable table (a future `@jini-ai/sqlite-chat` adapter) is host-level integration wiring, the same way
|
|
25
25
|
* `runs.ts`'s `onStarted` driver is host-supplied rather than built into `RunLifecycle` itself —
|
|
26
26
|
* not attempted here, out of this port's scope.
|
|
27
27
|
*/
|
|
@@ -33,7 +33,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
33
33
|
* Kept as its own exported pure function (not inlined) so it is directly unit-testable, matching
|
|
34
34
|
* this repo's "extract to a testable pure function" convention for awkward-to-cover branches.
|
|
35
35
|
*/
|
|
36
|
-
export function summarizeLastRun(run) {
|
|
36
|
+
export function summarizeLastRun({ run }) {
|
|
37
37
|
if (!run)
|
|
38
38
|
return null;
|
|
39
39
|
return {
|
|
@@ -70,14 +70,14 @@ function cloneRoutine(routine) {
|
|
|
70
70
|
/**
|
|
71
71
|
* Reference `RoutineStore` implementation: an in-process `Map` of routines plus a flat array of
|
|
72
72
|
* runs, no durable copy — matching `createInMemoryEventLog`'s own scope (a real persistent
|
|
73
|
-
* adapter is `@jini-ai/sqlite`'s job).
|
|
73
|
+
* adapter is `@jini-ai/sqlite-chat`'s job).
|
|
74
74
|
*
|
|
75
75
|
* @complexity `get`/`create`/`update`/`delete` are O(1). `list` is O(n log n) (stable id-sort for
|
|
76
76
|
* deterministic ordering). `listRuns`/`getLatestRun` are O(m log m) in the number of runs
|
|
77
77
|
* recorded for the routine (m), since each sorts by `startedAt` descending on every call rather
|
|
78
78
|
* than maintaining a secondary index — acceptable at this reference implementation's scale.
|
|
79
79
|
*/
|
|
80
|
-
export function createInMemoryRoutineStore() {
|
|
80
|
+
export function createInMemoryRoutineStore(_requiredArgs, { now = Date.now, newId = randomUUID } = {}) {
|
|
81
81
|
const routines = new Map();
|
|
82
82
|
const runs = [];
|
|
83
83
|
function runsFor(routineId) {
|
|
@@ -87,21 +87,22 @@ export function createInMemoryRoutineStore() {
|
|
|
87
87
|
}
|
|
88
88
|
async function withLastRun(routine) {
|
|
89
89
|
const latest = runsFor(routine.id)[0] ?? null;
|
|
90
|
-
return { ...cloneRoutine(routine), lastRun: summarizeLastRun(latest) };
|
|
90
|
+
return { ...cloneRoutine(routine), lastRun: summarizeLastRun({ run: latest }) };
|
|
91
91
|
}
|
|
92
92
|
return {
|
|
93
93
|
async list() {
|
|
94
94
|
const ids = Array.from(routines.keys()).sort();
|
|
95
95
|
return Promise.all(ids.map((id) => withLastRun(routines.get(id))));
|
|
96
96
|
},
|
|
97
|
-
async get(id) {
|
|
97
|
+
async get({ id }) {
|
|
98
98
|
const routine = routines.get(id);
|
|
99
99
|
return routine ? withLastRun(routine) : null;
|
|
100
100
|
},
|
|
101
|
-
async create(
|
|
102
|
-
const
|
|
101
|
+
async create(requiredArgs, optionalArgs = {}) {
|
|
102
|
+
const input = { ...requiredArgs, ...optionalArgs };
|
|
103
|
+
const createdAt = now();
|
|
103
104
|
const routine = {
|
|
104
|
-
id: `routine-${
|
|
105
|
+
id: `routine-${newId()}`,
|
|
105
106
|
name: input.name,
|
|
106
107
|
prompt: input.prompt,
|
|
107
108
|
schedule: input.schedule,
|
|
@@ -112,13 +113,13 @@ export function createInMemoryRoutineStore() {
|
|
|
112
113
|
enabled: input.enabled ?? true,
|
|
113
114
|
nextRunAt: null,
|
|
114
115
|
lastRun: null,
|
|
115
|
-
createdAt:
|
|
116
|
-
updatedAt:
|
|
116
|
+
createdAt: createdAt,
|
|
117
|
+
updatedAt: createdAt,
|
|
117
118
|
};
|
|
118
119
|
routines.set(routine.id, routine);
|
|
119
120
|
return withLastRun(routine);
|
|
120
121
|
},
|
|
121
|
-
async update(id, patch) {
|
|
122
|
+
async update({ id, patch }) {
|
|
122
123
|
const existing = routines.get(id);
|
|
123
124
|
if (!existing)
|
|
124
125
|
return null;
|
|
@@ -132,27 +133,27 @@ export function createInMemoryRoutineStore() {
|
|
|
132
133
|
...(patch.agentId !== undefined ? { agentId: patch.agentId } : {}),
|
|
133
134
|
...(patch.context !== undefined ? { context: patch.context } : {}),
|
|
134
135
|
...(patch.enabled !== undefined ? { enabled: patch.enabled } : {}),
|
|
135
|
-
updatedAt:
|
|
136
|
+
updatedAt: now(),
|
|
136
137
|
};
|
|
137
138
|
routines.set(id, updated);
|
|
138
139
|
return withLastRun(updated);
|
|
139
140
|
},
|
|
140
|
-
async delete(id) {
|
|
141
|
+
async delete({ id }) {
|
|
141
142
|
return routines.delete(id);
|
|
142
143
|
},
|
|
143
|
-
async listRuns(routineId, limit) {
|
|
144
|
+
async listRuns({ routineId, limit }) {
|
|
144
145
|
return runsFor(routineId).slice(0, limit);
|
|
145
146
|
},
|
|
146
|
-
async getLatestRun(routineId) {
|
|
147
|
+
async getLatestRun({ routineId }) {
|
|
147
148
|
return runsFor(routineId)[0] ?? null;
|
|
148
149
|
},
|
|
149
|
-
recordRun(run) {
|
|
150
|
+
recordRun({ run }) {
|
|
150
151
|
if (runs.some((existing) => existing.id === run.id))
|
|
151
152
|
return false;
|
|
152
153
|
runs.push({ ...run });
|
|
153
154
|
return true;
|
|
154
155
|
},
|
|
155
|
-
patchRun(id, patch) {
|
|
156
|
+
patchRun({ id, patch }) {
|
|
156
157
|
const run = runs.find((candidate) => candidate.id === id);
|
|
157
158
|
if (run)
|
|
158
159
|
Object.assign(run, patch);
|
|
@@ -18,7 +18,10 @@ import type { RoutineProjectTarget, RoutineSchedule, Weekday } from './types.js'
|
|
|
18
18
|
* edge cases no timezone on this test runner's ICU data naturally triggers — directly testable
|
|
19
19
|
* with a mocked `Intl.DateTimeFormat.prototype.formatToParts` instead of left unreachable.
|
|
20
20
|
*/
|
|
21
|
-
export declare function partsInTimezone(timezone
|
|
21
|
+
export declare function partsInTimezone({ timezone, atUtc }: {
|
|
22
|
+
readonly timezone: string;
|
|
23
|
+
readonly atUtc: Date;
|
|
24
|
+
}): {
|
|
22
25
|
year: number;
|
|
23
26
|
month: number;
|
|
24
27
|
day: number;
|
|
@@ -42,7 +45,14 @@ export declare function partsInTimezone(timezone: string, atUtc: Date): {
|
|
|
42
45
|
* the unguarded `partsInTimezone` earlier in its own loop body, so an invalid timezone throws
|
|
43
46
|
* there first — this function's defensive catch is otherwise unreachable through that call path.
|
|
44
47
|
*/
|
|
45
|
-
export declare function tzWallToUtcCandidates(timezone
|
|
48
|
+
export declare function tzWallToUtcCandidates({ timezone, year, month, day, hour, minute }: {
|
|
49
|
+
readonly timezone: string;
|
|
50
|
+
readonly year: number;
|
|
51
|
+
readonly month: number;
|
|
52
|
+
readonly day: number;
|
|
53
|
+
readonly hour: number;
|
|
54
|
+
readonly minute: number;
|
|
55
|
+
}): Date[];
|
|
46
56
|
/**
|
|
47
57
|
* Spring-forward gap fallback: when the requested wall time doesn't exist in `timezone` on this
|
|
48
58
|
* day (clocks jumped over it), return the later of the two probe candidates. That instant has
|
|
@@ -53,12 +63,40 @@ export declare function tzWallToUtcCandidates(timezone: string, year: number, mo
|
|
|
53
63
|
* Exported for the same reason as {@link tzWallToUtcCandidates} — its own `catch` branch is
|
|
54
64
|
* otherwise unreachable through {@link nextWallTimeMatching}'s call path.
|
|
55
65
|
*/
|
|
56
|
-
export declare function tzWallToUtcGapFallback(timezone
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
export declare function tzWallToUtcGapFallback({ timezone, year, month, day, hour, minute }: {
|
|
67
|
+
readonly timezone: string;
|
|
68
|
+
readonly year: number;
|
|
69
|
+
readonly month: number;
|
|
70
|
+
readonly day: number;
|
|
71
|
+
readonly hour: number;
|
|
72
|
+
readonly minute: number;
|
|
73
|
+
}): Date | null;
|
|
74
|
+
export declare function nextHourlyRunAt({ minute }: {
|
|
75
|
+
readonly minute: number;
|
|
76
|
+
}, { now }?: {
|
|
77
|
+
readonly now?: Date;
|
|
78
|
+
}): Date;
|
|
79
|
+
export declare function nextWallTimeMatching({ timezone, time, predicate, now }: {
|
|
80
|
+
readonly timezone: string;
|
|
81
|
+
readonly time: string;
|
|
82
|
+
readonly predicate: (weekday: Weekday) => boolean;
|
|
83
|
+
readonly now: Date;
|
|
84
|
+
}): Date | null;
|
|
85
|
+
export declare function nextRunAtForSchedule({ schedule }: {
|
|
86
|
+
readonly schedule: RoutineSchedule;
|
|
87
|
+
}, { now }?: {
|
|
88
|
+
readonly now?: Date;
|
|
89
|
+
}): Date | null;
|
|
90
|
+
export declare function isValidWallTime({ time }: {
|
|
91
|
+
readonly time: string;
|
|
92
|
+
}): boolean;
|
|
93
|
+
export declare function isValidTimezone({ tz }: {
|
|
94
|
+
readonly tz: string;
|
|
95
|
+
}): boolean;
|
|
96
|
+
export declare function validateSchedule({ schedule }: {
|
|
97
|
+
readonly schedule: RoutineSchedule;
|
|
98
|
+
}): void;
|
|
99
|
+
export declare function validateTarget({ target }: {
|
|
100
|
+
readonly target: RoutineProjectTarget;
|
|
101
|
+
}): void;
|
|
64
102
|
//# sourceMappingURL=schedule.d.ts.map
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* edge cases no timezone on this test runner's ICU data naturally triggers — directly testable
|
|
10
10
|
* with a mocked `Intl.DateTimeFormat.prototype.formatToParts` instead of left unreachable.
|
|
11
11
|
*/
|
|
12
|
-
export function partsInTimezone(timezone, atUtc) {
|
|
12
|
+
export function partsInTimezone({ timezone, atUtc }) {
|
|
13
13
|
const dtf = new Intl.DateTimeFormat('en-US', {
|
|
14
14
|
timeZone: timezone,
|
|
15
15
|
hour12: false,
|
|
@@ -62,7 +62,7 @@ export function partsInTimezone(timezone, atUtc) {
|
|
|
62
62
|
* the unguarded `partsInTimezone` earlier in its own loop body, so an invalid timezone throws
|
|
63
63
|
* there first — this function's defensive catch is otherwise unreachable through that call path.
|
|
64
64
|
*/
|
|
65
|
-
export function tzWallToUtcCandidates(timezone, year, month, day, hour, minute) {
|
|
65
|
+
export function tzWallToUtcCandidates({ timezone, year, month, day, hour, minute }) {
|
|
66
66
|
try {
|
|
67
67
|
const tentative = Date.UTC(year, month - 1, day, hour, minute, 0);
|
|
68
68
|
const probeOffsetsMs = [-12, 0, 12].map((h) => h * 60 * 60_000);
|
|
@@ -95,7 +95,7 @@ export function tzWallToUtcCandidates(timezone, year, month, day, hour, minute)
|
|
|
95
95
|
* Exported for the same reason as {@link tzWallToUtcCandidates} — its own `catch` branch is
|
|
96
96
|
* otherwise unreachable through {@link nextWallTimeMatching}'s call path.
|
|
97
97
|
*/
|
|
98
|
-
export function tzWallToUtcGapFallback(timezone, year, month, day, hour, minute) {
|
|
98
|
+
export function tzWallToUtcGapFallback({ timezone, year, month, day, hour, minute }) {
|
|
99
99
|
try {
|
|
100
100
|
const tentative = Date.UTC(year, month - 1, day, hour, minute, 0);
|
|
101
101
|
const t1 = tzOffsetMinutes(timezone, new Date(tentative));
|
|
@@ -109,17 +109,17 @@ export function tzWallToUtcGapFallback(timezone, year, month, day, hour, minute)
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
function matchesWallClock(timezone, at, year, month, day, hour, minute) {
|
|
112
|
-
const p = partsInTimezone(timezone, at);
|
|
112
|
+
const p = partsInTimezone({ timezone: timezone, atUtc: at });
|
|
113
113
|
return p.year === year && p.month === month && p.day === day && p.hour === hour && p.minute === minute;
|
|
114
114
|
}
|
|
115
115
|
/** Minutes east of UTC for `timezone` at instant `at`. e.g. Asia/Shanghai returns 480. */
|
|
116
116
|
function tzOffsetMinutes(timezone, at) {
|
|
117
|
-
const p = partsInTimezone(timezone, at);
|
|
117
|
+
const p = partsInTimezone({ timezone: timezone, atUtc: at });
|
|
118
118
|
const asIfUtc = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
|
|
119
119
|
return Math.round((asIfUtc - at.getTime()) / 60_000);
|
|
120
120
|
}
|
|
121
121
|
// ---------- next-fire calculation ----------
|
|
122
|
-
export function nextHourlyRunAt(minute, now = new Date()) {
|
|
122
|
+
export function nextHourlyRunAt({ minute }, { now = new Date() } = {}) {
|
|
123
123
|
const next = new Date(now);
|
|
124
124
|
next.setSeconds(0, 0);
|
|
125
125
|
next.setMinutes(minute);
|
|
@@ -157,11 +157,11 @@ function parseWallTime(time) {
|
|
|
157
157
|
* — see {@link nextWallTimeMatching} for when that actually happens.
|
|
158
158
|
*/
|
|
159
159
|
function resolveDayFireInstant(timezone, date, hour, minute, now) {
|
|
160
|
-
const candidates = tzWallToUtcCandidates(timezone, date.year, date.month, date.day, hour, minute);
|
|
160
|
+
const candidates = tzWallToUtcCandidates({ timezone: timezone, year: date.year, month: date.month, day: date.day, hour: hour, minute: minute });
|
|
161
161
|
if (candidates.length === 0) {
|
|
162
162
|
// Spring-forward gap: no valid wall instant exists today; pick the synthesized post-gap
|
|
163
163
|
// fallback so the routine still fires today.
|
|
164
|
-
const fallback = tzWallToUtcGapFallback(timezone, date.year, date.month, date.day, hour, minute);
|
|
164
|
+
const fallback = tzWallToUtcGapFallback({ timezone: timezone, year: date.year, month: date.month, day: date.day, hour: hour, minute: minute });
|
|
165
165
|
if (!fallback)
|
|
166
166
|
return { kind: 'abandon' };
|
|
167
167
|
return fallback.getTime() > now.getTime() ? { kind: 'fire', at: fallback } : { kind: 'next-day' };
|
|
@@ -175,14 +175,14 @@ function resolveDayFireInstant(timezone, date, hour, minute, now) {
|
|
|
175
175
|
}
|
|
176
176
|
return { kind: 'next-day' };
|
|
177
177
|
}
|
|
178
|
-
export function nextWallTimeMatching(timezone, time, predicate, now) {
|
|
178
|
+
export function nextWallTimeMatching({ timezone, time, predicate, now }) {
|
|
179
179
|
const wall = parseWallTime(time);
|
|
180
180
|
if (!wall)
|
|
181
181
|
return null;
|
|
182
182
|
// Walk day by day in the target timezone.
|
|
183
183
|
for (let offset = 0; offset < 14; offset += 1) {
|
|
184
184
|
const probe = new Date(now.getTime() + offset * 24 * 60 * 60_000);
|
|
185
|
-
const parts = partsInTimezone(timezone, probe);
|
|
185
|
+
const parts = partsInTimezone({ timezone: timezone, atUtc: probe });
|
|
186
186
|
if (!predicate(parts.weekday))
|
|
187
187
|
continue;
|
|
188
188
|
const resolution = resolveDayFireInstant(timezone, parts, wall.hour, wall.minute, now);
|
|
@@ -202,24 +202,24 @@ export function nextWallTimeMatching(timezone, time, predicate, now) {
|
|
|
202
202
|
}
|
|
203
203
|
return null;
|
|
204
204
|
}
|
|
205
|
-
export function nextRunAtForSchedule(schedule, now = new Date()) {
|
|
205
|
+
export function nextRunAtForSchedule({ schedule }, { now = new Date() } = {}) {
|
|
206
206
|
if (schedule.kind === 'hourly') {
|
|
207
|
-
return nextHourlyRunAt(schedule.minute, now);
|
|
207
|
+
return nextHourlyRunAt({ minute: schedule.minute }, { now: now });
|
|
208
208
|
}
|
|
209
209
|
if (schedule.kind === 'daily') {
|
|
210
|
-
return nextWallTimeMatching(schedule.timezone, schedule.time, () => true, now);
|
|
210
|
+
return nextWallTimeMatching({ timezone: schedule.timezone, time: schedule.time, predicate: () => true, now: now });
|
|
211
211
|
}
|
|
212
212
|
if (schedule.kind === 'weekdays') {
|
|
213
213
|
// Mon=1 .. Fri=5
|
|
214
|
-
return nextWallTimeMatching(schedule.timezone, schedule.time, (w) => w >= 1 && w <= 5, now);
|
|
214
|
+
return nextWallTimeMatching({ timezone: schedule.timezone, time: schedule.time, predicate: (w) => w >= 1 && w <= 5, now: now });
|
|
215
215
|
}
|
|
216
216
|
if (schedule.kind === 'weekly') {
|
|
217
|
-
return nextWallTimeMatching(schedule.timezone, schedule.time, (w) => w === schedule.weekday, now);
|
|
217
|
+
return nextWallTimeMatching({ timezone: schedule.timezone, time: schedule.time, predicate: (w) => w === schedule.weekday, now: now });
|
|
218
218
|
}
|
|
219
219
|
return null;
|
|
220
220
|
}
|
|
221
221
|
// ---------- validation ----------
|
|
222
|
-
export function isValidWallTime(time) {
|
|
222
|
+
export function isValidWallTime({ time }) {
|
|
223
223
|
const m = /^(\d{2}):(\d{2})$/.exec(time);
|
|
224
224
|
if (!m)
|
|
225
225
|
return false;
|
|
@@ -227,7 +227,7 @@ export function isValidWallTime(time) {
|
|
|
227
227
|
const mm = Number(m[2]);
|
|
228
228
|
return h >= 0 && h <= 23 && mm >= 0 && mm <= 59;
|
|
229
229
|
}
|
|
230
|
-
export function isValidTimezone(tz) {
|
|
230
|
+
export function isValidTimezone({ tz }) {
|
|
231
231
|
if (!tz || typeof tz !== 'string')
|
|
232
232
|
return false;
|
|
233
233
|
try {
|
|
@@ -249,17 +249,17 @@ function validateWeekday(weekday) {
|
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
function validateWallClockSchedule(schedule) {
|
|
252
|
-
if (!isValidWallTime(schedule.time)) {
|
|
252
|
+
if (!isValidWallTime({ time: schedule.time })) {
|
|
253
253
|
throw new Error(`Invalid time: ${schedule.time}`);
|
|
254
254
|
}
|
|
255
|
-
if (!isValidTimezone(schedule.timezone)) {
|
|
255
|
+
if (!isValidTimezone({ tz: schedule.timezone })) {
|
|
256
256
|
throw new Error(`Invalid timezone: ${schedule.timezone}`);
|
|
257
257
|
}
|
|
258
258
|
if (schedule.kind === 'weekly') {
|
|
259
259
|
validateWeekday(schedule.weekday);
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
|
-
export function validateSchedule(schedule) {
|
|
262
|
+
export function validateSchedule({ schedule }) {
|
|
263
263
|
if (!schedule || typeof schedule !== 'object') {
|
|
264
264
|
throw new Error('schedule is required');
|
|
265
265
|
}
|
|
@@ -273,7 +273,7 @@ export function validateSchedule(schedule) {
|
|
|
273
273
|
}
|
|
274
274
|
throw new Error(`Unsupported schedule kind: ${schedule.kind}`);
|
|
275
275
|
}
|
|
276
|
-
export function validateTarget(target) {
|
|
276
|
+
export function validateTarget({ target }) {
|
|
277
277
|
if (!target || typeof target !== 'object') {
|
|
278
278
|
throw new Error('target is required');
|
|
279
279
|
}
|
|
@@ -9,26 +9,47 @@ export declare class ScheduledRunPersistenceError extends Error {
|
|
|
9
9
|
readonly routineId: string;
|
|
10
10
|
readonly slotAt: number;
|
|
11
11
|
readonly originalError: unknown;
|
|
12
|
-
constructor(routineId
|
|
12
|
+
constructor({ routineId, slotAt, originalError }: {
|
|
13
|
+
readonly routineId: string;
|
|
14
|
+
readonly slotAt: number;
|
|
15
|
+
readonly originalError: unknown;
|
|
16
|
+
});
|
|
13
17
|
}
|
|
14
18
|
export declare class RoutineService {
|
|
15
|
-
private readonly persistence;
|
|
16
19
|
private timers;
|
|
17
20
|
private inflight;
|
|
18
21
|
private runHandler;
|
|
19
22
|
private started;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
private readonly persistence;
|
|
24
|
+
private readonly now;
|
|
25
|
+
private readonly newId;
|
|
26
|
+
constructor({ persistence }: {
|
|
27
|
+
readonly persistence: RoutinePersistence;
|
|
28
|
+
}, { now, newId }?: {
|
|
29
|
+
now?: () => number;
|
|
30
|
+
newId?: () => string;
|
|
31
|
+
});
|
|
32
|
+
setRunHandler({ handler }: {
|
|
33
|
+
readonly handler: RoutineRunHandler;
|
|
34
|
+
}): void;
|
|
35
|
+
start(_args: Record<string, never>): void;
|
|
36
|
+
stop(_args: Record<string, never>): void;
|
|
37
|
+
rescheduleAll(_args: Record<string, never>): void;
|
|
38
|
+
rescheduleOne({ routineId }: {
|
|
39
|
+
readonly routineId: string;
|
|
40
|
+
}): void;
|
|
41
|
+
unschedule({ routineId }: {
|
|
42
|
+
readonly routineId: string;
|
|
43
|
+
}): void;
|
|
27
44
|
private scheduleRoutine;
|
|
28
45
|
private retryScheduledSlot;
|
|
29
46
|
private scheduleRoutineAt;
|
|
30
|
-
nextRunAt(routineId
|
|
31
|
-
|
|
47
|
+
nextRunAt({ routineId }: {
|
|
48
|
+
readonly routineId: string;
|
|
49
|
+
}): Date | null;
|
|
50
|
+
runNow({ routineId }: {
|
|
51
|
+
readonly routineId: string;
|
|
52
|
+
}): Promise<RoutineRunHandlerStart>;
|
|
32
53
|
/**
|
|
33
54
|
* Wraps a persistence failure as a {@link ScheduledRunPersistenceError} when this attempt was a
|
|
34
55
|
* scheduled fire, so the caller retries the same slot instead of advancing the cadence. Manual
|