@jini-ai/daemon 0.1.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/LICENSE +201 -0
- package/dist/agent-executor.d.ts +458 -0
- package/dist/agent-executor.d.ts.map +1 -0
- package/dist/agent-executor.js +1277 -0
- package/dist/agent-executor.js.map +1 -0
- package/dist/artifacts/index.d.ts +14 -0
- package/dist/artifacts/index.d.ts.map +1 -0
- package/dist/artifacts/index.js +14 -0
- package/dist/artifacts/index.js.map +1 -0
- package/dist/artifacts/manifest.d.ts +90 -0
- package/dist/artifacts/manifest.d.ts.map +1 -0
- package/dist/artifacts/manifest.js +229 -0
- package/dist/artifacts/manifest.js.map +1 -0
- package/dist/artifacts/publication-guard.d.ts +28 -0
- package/dist/artifacts/publication-guard.d.ts.map +1 -0
- package/dist/artifacts/publication-guard.js +77 -0
- package/dist/artifacts/publication-guard.js.map +1 -0
- package/dist/artifacts/runtime-compat.d.ts +37 -0
- package/dist/artifacts/runtime-compat.d.ts.map +1 -0
- package/dist/artifacts/runtime-compat.js +33 -0
- package/dist/artifacts/runtime-compat.js.map +1 -0
- package/dist/artifacts/store.d.ts +85 -0
- package/dist/artifacts/store.d.ts.map +1 -0
- package/dist/artifacts/store.js +92 -0
- package/dist/artifacts/store.js.map +1 -0
- package/dist/artifacts/stub-guard.d.ts +73 -0
- package/dist/artifacts/stub-guard.d.ts.map +1 -0
- package/dist/artifacts/stub-guard.js +198 -0
- package/dist/artifacts/stub-guard.js.map +1 -0
- package/dist/artifacts/text-suppression.d.ts +68 -0
- package/dist/artifacts/text-suppression.d.ts.map +1 -0
- package/dist/artifacts/text-suppression.js +184 -0
- package/dist/artifacts/text-suppression.js.map +1 -0
- package/dist/close-status.d.ts +86 -0
- package/dist/close-status.d.ts.map +1 -0
- package/dist/close-status.js +91 -0
- package/dist/close-status.js.map +1 -0
- package/dist/continuation/continuation-transport.d.ts +41 -0
- package/dist/continuation/continuation-transport.d.ts.map +1 -0
- package/dist/continuation/continuation-transport.js +18 -0
- package/dist/continuation/continuation-transport.js.map +1 -0
- package/dist/continuation/index.d.ts +4 -0
- package/dist/continuation/index.d.ts.map +1 -0
- package/dist/continuation/index.js +4 -0
- package/dist/continuation/index.js.map +1 -0
- package/dist/continuation/journal.d.ts +37 -0
- package/dist/continuation/journal.d.ts.map +1 -0
- package/dist/continuation/journal.js +23 -0
- package/dist/continuation/journal.js.map +1 -0
- package/dist/continuation/run-start-handler.d.ts +75 -0
- package/dist/continuation/run-start-handler.d.ts.map +1 -0
- package/dist/continuation/run-start-handler.js +32 -0
- package/dist/continuation/run-start-handler.js.map +1 -0
- package/dist/delegated-tool-bridge.d.ts +53 -0
- package/dist/delegated-tool-bridge.d.ts.map +1 -0
- package/dist/delegated-tool-bridge.js +92 -0
- package/dist/delegated-tool-bridge.js.map +1 -0
- package/dist/event-log.d.ts +134 -0
- package/dist/event-log.d.ts.map +1 -0
- package/dist/event-log.js +108 -0
- package/dist/event-log.js.map +1 -0
- package/dist/frontend-capability-tools.d.ts +102 -0
- package/dist/frontend-capability-tools.d.ts.map +1 -0
- package/dist/frontend-capability-tools.js +68 -0
- package/dist/frontend-capability-tools.js.map +1 -0
- package/dist/frontend-session-registry.d.ts +127 -0
- package/dist/frontend-session-registry.d.ts.map +1 -0
- package/dist/frontend-session-registry.js +180 -0
- package/dist/frontend-session-registry.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/legacy-data-migration.d.ts +104 -0
- package/dist/legacy-data-migration.d.ts.map +1 -0
- package/dist/legacy-data-migration.js +239 -0
- package/dist/legacy-data-migration.js.map +1 -0
- package/dist/routines/index.d.ts +12 -0
- package/dist/routines/index.d.ts.map +1 -0
- package/dist/routines/index.js +12 -0
- package/dist/routines/index.js.map +1 -0
- package/dist/routines/routine-store.d.ts +77 -0
- package/dist/routines/routine-store.d.ts.map +1 -0
- package/dist/routines/routine-store.js +162 -0
- package/dist/routines/routine-store.js.map +1 -0
- package/dist/routines/schedule.d.ts +72 -0
- package/dist/routines/schedule.d.ts.map +1 -0
- package/dist/routines/schedule.js +263 -0
- package/dist/routines/schedule.js.map +1 -0
- package/dist/routines/scheduler.d.ts +34 -0
- package/dist/routines/scheduler.d.ts.map +1 -0
- package/dist/routines/scheduler.js +330 -0
- package/dist/routines/scheduler.js.map +1 -0
- package/dist/routines/types.d.ts +131 -0
- package/dist/routines/types.d.ts.map +1 -0
- package/dist/routines/types.js +9 -0
- package/dist/routines/types.js.map +1 -0
- package/dist/run/core/failure-taxonomy.d.ts +27 -0
- package/dist/run/core/failure-taxonomy.d.ts.map +1 -0
- package/dist/run/core/failure-taxonomy.js +16 -0
- package/dist/run/core/failure-taxonomy.js.map +1 -0
- package/dist/run/core/index.d.ts +5 -0
- package/dist/run/core/index.d.ts.map +1 -0
- package/dist/run/core/index.js +5 -0
- package/dist/run/core/index.js.map +1 -0
- package/dist/run/core/result.d.ts +25 -0
- package/dist/run/core/result.d.ts.map +1 -0
- package/dist/run/core/result.js +52 -0
- package/dist/run/core/result.js.map +1 -0
- package/dist/run/core/retry.d.ts +106 -0
- package/dist/run/core/retry.d.ts.map +1 -0
- package/dist/run/core/retry.js +210 -0
- package/dist/run/core/retry.js.map +1 -0
- package/dist/run/diagnostics/diagnostics.d.ts +86 -0
- package/dist/run/diagnostics/diagnostics.d.ts.map +1 -0
- package/dist/run/diagnostics/diagnostics.js +211 -0
- package/dist/run/diagnostics/diagnostics.js.map +1 -0
- package/dist/run/diagnostics/index.d.ts +3 -0
- package/dist/run/diagnostics/index.d.ts.map +1 -0
- package/dist/run/diagnostics/index.js +3 -0
- package/dist/run/diagnostics/index.js.map +1 -0
- package/dist/run/index.d.ts +16 -0
- package/dist/run/index.d.ts.map +1 -0
- package/dist/run/index.js +16 -0
- package/dist/run/index.js.map +1 -0
- package/dist/run-lifecycle.d.ts +118 -0
- package/dist/run-lifecycle.d.ts.map +1 -0
- package/dist/run-lifecycle.js +451 -0
- package/dist/run-lifecycle.js.map +1 -0
- package/dist/terminal-session.d.ts +139 -0
- package/dist/terminal-session.d.ts.map +1 -0
- package/dist/terminal-session.js +316 -0
- package/dist/terminal-session.js.map +1 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +19 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tool-executor.d.ts +97 -0
- package/dist/tool-executor.d.ts.map +1 -0
- package/dist/tool-executor.js +198 -0
- package/dist/tool-executor.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module legacy-data-migration
|
|
3
|
+
*
|
|
4
|
+
* A one-shot, idempotent, host-configured migrator for moving a daemon's
|
|
5
|
+
* data root from one location to another (e.g. a pre-packaging install that
|
|
6
|
+
* ran out of a repo checkout, moved to a per-namespace directory under an
|
|
7
|
+
* OS user-data location). The mechanism — refuse-if-missing-proof,
|
|
8
|
+
* refuse-if-destination-populated, refuse-if-already-migrated, reject
|
|
9
|
+
* symlinks, stage-then-atomically-promote, rollback-on-any-failure — is
|
|
10
|
+
* product-neutral; the *shape* of a data root's payload (which files/dirs
|
|
11
|
+
* constitute "real data", and which single entry proves a directory holds
|
|
12
|
+
* real payload rather than an empty scaffold) is host-supplied via
|
|
13
|
+
* {@link LegacyDataMigrationConfig}, never hardcoded here.
|
|
14
|
+
*
|
|
15
|
+
* Sync by design: intended to run at daemon startup before any database
|
|
16
|
+
* connection opens, so it can't race a concurrent open of the destination.
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from 'node:fs';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
const DEFAULT_MARKER_FILE = '.migrated-from';
|
|
21
|
+
/**
|
|
22
|
+
* Thrown when `legacyDir` is explicitly set but is not a usable legacy data
|
|
23
|
+
* dir, or the new `dataDir` is already populated and would be merged into.
|
|
24
|
+
* Failing loud here is the point: a silent skip trains an operator to
|
|
25
|
+
* assume a migration ran when it didn't.
|
|
26
|
+
*/
|
|
27
|
+
export class LegacyMigrationError extends Error {
|
|
28
|
+
code;
|
|
29
|
+
constructor(code, message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.name = 'LegacyMigrationError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function isExistingDir(p) {
|
|
36
|
+
try {
|
|
37
|
+
return fs.statSync(p).isDirectory();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function isExistingFile(p) {
|
|
44
|
+
try {
|
|
45
|
+
return fs.statSync(p).isFile();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns true when `dataDir` looks like a fresh / never-used data root:
|
|
53
|
+
* either the directory does not exist, or it exists but does not contain
|
|
54
|
+
* the configured `proofEntry` as a file. Deliberately not just "the
|
|
55
|
+
* directory is empty" — a packaged install commonly pre-creates an empty
|
|
56
|
+
* `dataDir` before the daemon ever boots, so emptiness alone is not proof
|
|
57
|
+
* of "no prior data."
|
|
58
|
+
*
|
|
59
|
+
* @param dataDir - The data root to check.
|
|
60
|
+
* @param config - The host's payload/proof-entry configuration.
|
|
61
|
+
*/
|
|
62
|
+
export function dataDirIsEmptyOrFresh(dataDir, config) {
|
|
63
|
+
if (!isExistingDir(dataDir))
|
|
64
|
+
return true;
|
|
65
|
+
return !isExistingFile(path.join(dataDir, config.proofEntry));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Returns true when `legacyDir` contains a payload worth migrating: it
|
|
69
|
+
* exists and its configured `proofEntry` exists as a file.
|
|
70
|
+
*
|
|
71
|
+
* @param legacyDir - The candidate legacy data directory.
|
|
72
|
+
* @param config - The host's payload/proof-entry configuration.
|
|
73
|
+
*/
|
|
74
|
+
export function legacyDirHasPayload(legacyDir, config) {
|
|
75
|
+
if (!isExistingDir(legacyDir))
|
|
76
|
+
return false;
|
|
77
|
+
return isExistingFile(path.join(legacyDir, config.proofEntry));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Returns the list of configured payload entries that already exist under
|
|
81
|
+
* `dataDir`. Migration refuses to merge on top of any of them — a database
|
|
82
|
+
* file/WAL pair or a project tree cannot be safely interleaved with a
|
|
83
|
+
* foreign counterpart, and a partial overlay is worse than no migration.
|
|
84
|
+
*
|
|
85
|
+
* @param dataDir - The data root to check.
|
|
86
|
+
* @param config - The host's payload/proof-entry configuration.
|
|
87
|
+
*/
|
|
88
|
+
export function dataDirHasExistingPayload(dataDir, config) {
|
|
89
|
+
if (!isExistingDir(dataDir))
|
|
90
|
+
return [];
|
|
91
|
+
return config.payloadEntries.filter((entry) => fs.existsSync(path.join(dataDir, entry)));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Walk a payload subtree and refuse to copy if any node is a symlink — a
|
|
95
|
+
* preserved symlink would let a downstream reader escape the data root.
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
function assertNoSymlinks(srcRoot, displayPath = srcRoot) {
|
|
99
|
+
const stat = fs.lstatSync(srcRoot);
|
|
100
|
+
if (stat.isSymbolicLink()) {
|
|
101
|
+
throw new LegacyMigrationError('symlink_in_payload', `legacy payload contains a symlink at "${displayPath}"; refusing to migrate`);
|
|
102
|
+
}
|
|
103
|
+
if (!stat.isDirectory())
|
|
104
|
+
return;
|
|
105
|
+
for (const child of fs.readdirSync(srcRoot)) {
|
|
106
|
+
assertNoSymlinks(path.join(srcRoot, child), path.join(displayPath, child));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Stage every present payload entry into `stagingDir`. Returns the entries actually copied. @internal */
|
|
110
|
+
function stagePayload(legacyDir, stagingDir, payloadEntries) {
|
|
111
|
+
fs.mkdirSync(stagingDir, { recursive: true });
|
|
112
|
+
const copied = [];
|
|
113
|
+
for (const entry of payloadEntries) {
|
|
114
|
+
const src = path.join(legacyDir, entry);
|
|
115
|
+
if (!fs.existsSync(src))
|
|
116
|
+
continue;
|
|
117
|
+
assertNoSymlinks(src);
|
|
118
|
+
const dst = path.join(stagingDir, entry);
|
|
119
|
+
fs.cpSync(src, dst, { recursive: true, force: true, errorOnExist: false });
|
|
120
|
+
copied.push(entry);
|
|
121
|
+
}
|
|
122
|
+
return copied;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Move staged payload entries into the final `dataDir`. Each entry is moved
|
|
126
|
+
* with `renameSync` (atomic on the same filesystem); a cross-device rename
|
|
127
|
+
* falls back to copy + remove. Rollback-safe in two layers: the copy
|
|
128
|
+
* fallback removes a partial destination on its own failure, and the outer
|
|
129
|
+
* loop removes every entry it already promoted before rethrowing.
|
|
130
|
+
*
|
|
131
|
+
* @param stagingDir - The sibling staging directory holding the copied payload.
|
|
132
|
+
* @param dataDir - The destination data root.
|
|
133
|
+
* @param entries - The payload entries to promote.
|
|
134
|
+
* @returns The entries that were placed into `dataDir` before any failure.
|
|
135
|
+
*/
|
|
136
|
+
export function promoteStaged(stagingDir, dataDir, entries) {
|
|
137
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
138
|
+
const promoted = [];
|
|
139
|
+
try {
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
const src = path.join(stagingDir, entry);
|
|
142
|
+
const dst = path.join(dataDir, entry);
|
|
143
|
+
try {
|
|
144
|
+
fs.renameSync(src, dst);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
try {
|
|
148
|
+
fs.cpSync(src, dst, { recursive: true, force: true, errorOnExist: false });
|
|
149
|
+
fs.rmSync(src, { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
catch (cpErr) {
|
|
152
|
+
fs.rmSync(dst, { recursive: true, force: true });
|
|
153
|
+
throw cpErr;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
promoted.push(entry);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
rollbackPromoted(dataDir, promoted);
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
return promoted;
|
|
164
|
+
}
|
|
165
|
+
function rollbackPromoted(dataDir, promoted) {
|
|
166
|
+
for (const entry of promoted) {
|
|
167
|
+
fs.rmSync(path.join(dataDir, entry), { recursive: true, force: true });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function writeMarkerDefault(dataDir, legacyDir, markerFile) {
|
|
171
|
+
const marker = path.join(dataDir, markerFile);
|
|
172
|
+
const temp = `${marker}.tmp-${process.pid}-${Date.now()}`;
|
|
173
|
+
const payload = JSON.stringify({ legacyDir: path.resolve(legacyDir), migratedAt: new Date().toISOString() }, null, 2);
|
|
174
|
+
try {
|
|
175
|
+
fs.writeFileSync(temp, `${payload}\n`, 'utf8');
|
|
176
|
+
fs.renameSync(temp, marker);
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
fs.rmSync(temp, { force: true });
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* One-shot, idempotent legacy data migrator. Synchronous so it can run at
|
|
185
|
+
* module import time before a database connection opens. Throws
|
|
186
|
+
* {@link LegacyMigrationError} on operator-actionable failures (env set but
|
|
187
|
+
* path invalid; `dataDir` already populated; symlink in payload).
|
|
188
|
+
*
|
|
189
|
+
* @param options - Legacy/current dir paths plus the host's payload config.
|
|
190
|
+
* @returns The migration outcome (`noop` when unconfigured, `skipped` when
|
|
191
|
+
* already migrated, `migrated` with the copied entry list otherwise).
|
|
192
|
+
*/
|
|
193
|
+
export function migrateLegacyDataDirSync(options) {
|
|
194
|
+
const log = options.logger ?? { info: (m) => console.log(`[jini-migrate] ${m}`) };
|
|
195
|
+
const markerFile = options.markerFile ?? DEFAULT_MARKER_FILE;
|
|
196
|
+
const raw = options.legacyDir;
|
|
197
|
+
if (raw === undefined || raw.length === 0) {
|
|
198
|
+
return { status: 'noop', reason: 'legacy data dir not configured' };
|
|
199
|
+
}
|
|
200
|
+
const legacyDir = path.resolve(raw);
|
|
201
|
+
const dataDir = path.resolve(options.dataDir);
|
|
202
|
+
if (legacyDir === dataDir) {
|
|
203
|
+
return { status: 'noop', reason: 'legacy data dir equals current data dir' };
|
|
204
|
+
}
|
|
205
|
+
// Marker check runs before legacyDirHasPayload on purpose: once a
|
|
206
|
+
// migration has succeeded, the marker is the canonical "do not touch"
|
|
207
|
+
// signal, even if the legacy source is later moved or deleted.
|
|
208
|
+
const markerPath = path.join(dataDir, markerFile);
|
|
209
|
+
if (fs.existsSync(markerPath)) {
|
|
210
|
+
return { status: 'skipped', reason: 'migration marker already present' };
|
|
211
|
+
}
|
|
212
|
+
if (!legacyDirHasPayload(legacyDir, options)) {
|
|
213
|
+
throw new LegacyMigrationError('legacy_dir_invalid', `legacy data dir "${legacyDir}" is not a usable legacy data dir (expected "${options.proofEntry}" directly inside it).`);
|
|
214
|
+
}
|
|
215
|
+
const existing = dataDirHasExistingPayload(dataDir, options);
|
|
216
|
+
if (existing.length > 0) {
|
|
217
|
+
throw new LegacyMigrationError('data_dir_not_empty', `data dir "${dataDir}" already contains payload entries (${existing.join(', ')}); refusing to merge legacy data on top. Move the existing data aside or pick a fresh data root before retrying.`);
|
|
218
|
+
}
|
|
219
|
+
log.info(`migrating legacy data from "${legacyDir}" to "${dataDir}"`);
|
|
220
|
+
fs.mkdirSync(path.dirname(dataDir), { recursive: true });
|
|
221
|
+
const stagingDir = path.join(path.dirname(dataDir), `${path.basename(dataDir)}.migrate-${process.pid}-${Date.now()}`);
|
|
222
|
+
let copied;
|
|
223
|
+
let promoted = [];
|
|
224
|
+
try {
|
|
225
|
+
copied = stagePayload(legacyDir, stagingDir, options.payloadEntries);
|
|
226
|
+
promoted = promoteStaged(stagingDir, dataDir, copied);
|
|
227
|
+
(options.writeMarker ?? writeMarkerDefault)(dataDir, legacyDir, markerFile);
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
rollbackPromoted(dataDir, promoted);
|
|
231
|
+
fs.rmSync(markerPath, { force: true });
|
|
232
|
+
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
236
|
+
log.info(`migration complete: copied ${copied.length} entr${copied.length === 1 ? 'y' : 'ies'} (${copied.join(', ')})`);
|
|
237
|
+
return { status: 'migrated', reason: 'copied legacy payload', copied };
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=legacy-data-migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-data-migration.js","sourceRoot":"","sources":["../src/legacy-data-migration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module routines
|
|
3
|
+
*
|
|
4
|
+
* The routine scheduler ({@link ./scheduler.js}'s `RoutineService`, `./schedule.js`'s DST-safe
|
|
5
|
+
* wall-clock math) plus its HTTP-facing CRUD + run-history counterpart ({@link ./routine-store.js}'s
|
|
6
|
+
* `RoutineStore`). See `../../source-map.md`'s dated routines section for full provenance.
|
|
7
|
+
*/
|
|
8
|
+
export * from './types.js';
|
|
9
|
+
export * from './schedule.js';
|
|
10
|
+
export * from './scheduler.js';
|
|
11
|
+
export * from './routine-store.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routines/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module routines
|
|
3
|
+
*
|
|
4
|
+
* The routine scheduler ({@link ./scheduler.js}'s `RoutineService`, `./schedule.js`'s DST-safe
|
|
5
|
+
* wall-clock math) plus its HTTP-facing CRUD + run-history counterpart ({@link ./routine-store.js}'s
|
|
6
|
+
* `RoutineStore`). See `../../source-map.md`'s dated routines section for full provenance.
|
|
7
|
+
*/
|
|
8
|
+
export * from './types.js';
|
|
9
|
+
export * from './schedule.js';
|
|
10
|
+
export * from './scheduler.js';
|
|
11
|
+
export * from './routine-store.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/routines/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Routine, RoutineContextSelection, RoutineProjectTarget, RoutineRun, RoutineSchedule } from './types.js';
|
|
2
|
+
export interface RoutineCreateInput {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly prompt: string;
|
|
5
|
+
readonly schedule: RoutineSchedule;
|
|
6
|
+
readonly target: RoutineProjectTarget;
|
|
7
|
+
readonly skillId?: string | null;
|
|
8
|
+
readonly agentId?: string | null;
|
|
9
|
+
readonly context?: RoutineContextSelection;
|
|
10
|
+
/** @default true */
|
|
11
|
+
readonly enabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface RoutineUpdateInput {
|
|
14
|
+
readonly name?: string;
|
|
15
|
+
readonly prompt?: string;
|
|
16
|
+
readonly schedule?: RoutineSchedule;
|
|
17
|
+
readonly target?: RoutineProjectTarget;
|
|
18
|
+
readonly skillId?: string | null;
|
|
19
|
+
readonly agentId?: string | null;
|
|
20
|
+
readonly context?: RoutineContextSelection;
|
|
21
|
+
readonly enabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Storage-agnostic CRUD + read-only run-history port for routines. `@jini-ai/daemon` ships
|
|
25
|
+
* {@link createInMemoryRoutineStore} as the reference implementation; a durable adapter
|
|
26
|
+
* (`@jini-ai/sqlite`, future work) implements the same interface.
|
|
27
|
+
*/
|
|
28
|
+
export interface RoutineStore {
|
|
29
|
+
list(): Promise<readonly Routine[]>;
|
|
30
|
+
/** Returns `null` when no routine with `id` exists — never throws for a missing id. */
|
|
31
|
+
get(id: string): Promise<Routine | null>;
|
|
32
|
+
/** Assigns and returns the new routine's `id` — never supplied by the caller, matching `EventLog.append`'s cursor-assignment precedent. */
|
|
33
|
+
create(input: RoutineCreateInput): Promise<Routine>;
|
|
34
|
+
/** Applies a partial patch and returns the updated routine, or `null` if `id` does not exist. */
|
|
35
|
+
update(id: string, patch: RoutineUpdateInput): Promise<Routine | null>;
|
|
36
|
+
/** Returns whether a routine with `id` existed (and was removed). */
|
|
37
|
+
delete(id: string): Promise<boolean>;
|
|
38
|
+
/** 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(routineId: string, limit: number): Promise<readonly RoutineRun[]>;
|
|
40
|
+
/** Returns the most recently started run for `routineId`, or `null` if none has ever run. */
|
|
41
|
+
getLatestRun(routineId: string): Promise<RoutineRun | null>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The display-shaped run summary OD's own `routineDbRowToContract` embedded on `Routine.lastRun`
|
|
45
|
+
* — a filtered/relabeled projection of a `RoutineRun` (its `id` renamed to `runId`; nullish
|
|
46
|
+
* `completedAt`/`summary`/`error`/`errorCode` omitted entirely rather than included as `null`).
|
|
47
|
+
* Kept as its own exported pure function (not inlined) so it is directly unit-testable, matching
|
|
48
|
+
* this repo's "extract to a testable pure function" convention for awkward-to-cover branches.
|
|
49
|
+
*/
|
|
50
|
+
export declare function summarizeLastRun(run: RoutineRun | null): Record<string, unknown> | null;
|
|
51
|
+
/**
|
|
52
|
+
* `RoutineStore` plus two extra write hooks for run history that are deliberately NOT part of
|
|
53
|
+
* the public {@link RoutineStore} contract (see this module's doc comment for why run-writing
|
|
54
|
+
* stays out of the interface). Exposed as a concrete-type superset so:
|
|
55
|
+
* 1. This module's own tests can seed `listRuns`/`getLatestRun` fixtures without a second store.
|
|
56
|
+
* 2. A host bridging `./scheduler.js`'s `RoutinePersistence.insertRun`/`updateRun` calls into
|
|
57
|
+
* this same in-memory instance (so `RoutineStore`'s read side observes the scheduler's writes)
|
|
58
|
+
* has a documented, shape-matched target to forward them to.
|
|
59
|
+
*/
|
|
60
|
+
export interface InMemoryRoutineStore extends RoutineStore {
|
|
61
|
+
/** 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(run: RoutineRun): boolean;
|
|
63
|
+
/** 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(id: string, patch: Partial<RoutineRun>): void;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Reference `RoutineStore` implementation: an in-process `Map` of routines plus a flat array of
|
|
68
|
+
* runs, no durable copy — matching `createInMemoryEventLog`'s own scope (a real persistent
|
|
69
|
+
* adapter is `@jini-ai/sqlite`'s job).
|
|
70
|
+
*
|
|
71
|
+
* @complexity `get`/`create`/`update`/`delete` are O(1). `list` is O(n log n) (stable id-sort for
|
|
72
|
+
* deterministic ordering). `listRuns`/`getLatestRun` are O(m log m) in the number of runs
|
|
73
|
+
* recorded for the routine (m), since each sorts by `startedAt` descending on every call rather
|
|
74
|
+
* than maintaining a secondary index — acceptable at this reference implementation's scale.
|
|
75
|
+
*/
|
|
76
|
+
export declare function createInMemoryRoutineStore(): InMemoryRoutineStore;
|
|
77
|
+
//# sourceMappingURL=routine-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routine-store.d.ts","sourceRoot":"","sources":["../../src/routines/routine-store.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEtH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAC3C,oBAAoB;IACpB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,uBAAuB,CAAC;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;IACpC,uFAAuF;IACvF,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACzC,2IAA2I;IAC3I,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,iGAAiG;IACjG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACvE,qEAAqE;IACrE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,2KAA2K;IAC3K,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC,CAAC;IAC3E,6FAA6F;IAC7F,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;CAC7D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAevF;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,wOAAwO;IACxO,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC;IACpC,oQAAoQ;IACpQ,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;CACxD;AAqBD;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,IAAI,oBAAoB,CAyFjE"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RoutineStore` — the CRUD + run-history persistence port for `@jini-ai/http`'s routine routes.
|
|
3
|
+
* Designed the same way `../event-log.js`'s `EventLog` is designed (that module's own doc
|
|
4
|
+
* comment is the explicit template named by the porting proposal,
|
|
5
|
+
* `ADS-memory/reports/proposals/PROP-http-route-packs-automation-routines-2026-07-21.md`): a
|
|
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
|
|
8
|
+
* API break.
|
|
9
|
+
*
|
|
10
|
+
* No such port existed anywhere in this repo before this file — OD's own `routine.ts` route file
|
|
11
|
+
* called eight raw `db.js` SQL functions directly (`getRoutine`/`insertRoutine`/`updateRoutine`/
|
|
12
|
+
* `deleteRoutine`/`listRoutines`/`listRoutineRuns`/`getLatestRoutineRun`/`getRoutineRun`) against
|
|
13
|
+
* `routines`/`routine_runs` tables that `packages/sqlite/source-map.md` already documents as
|
|
14
|
+
* explicitly out of scope for the db-barrel port. This is the missing design.
|
|
15
|
+
*
|
|
16
|
+
* **Deliberately separate from `./scheduler.js`'s `RoutinePersistence`, not a shared interface**,
|
|
17
|
+
* matching OD's own architecture: OD itself used two different narrow ports over the same
|
|
18
|
+
* conceptual `routine_runs` table — `RoutinePersistence.insertRun`/`updateRun` (injected into
|
|
19
|
+
* `RoutineService`, the scheduler's write path for a routine actually firing) and `routine.ts`'s
|
|
20
|
+
* own direct DB reads (the HTTP layer's read path for CRUD + run-history display). This port
|
|
21
|
+
* mirrors that same split: `RoutineStore` owns Routine CRUD and *read-only* run history
|
|
22
|
+
* (`listRuns`/`getLatestRun`); it does not write run records — that stays the scheduler's job via
|
|
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
|
|
25
|
+
* `runs.ts`'s `onStarted` driver is host-supplied rather than built into `RunLifecycle` itself —
|
|
26
|
+
* not attempted here, out of this port's scope.
|
|
27
|
+
*/
|
|
28
|
+
import { randomUUID } from 'node:crypto';
|
|
29
|
+
/**
|
|
30
|
+
* The display-shaped run summary OD's own `routineDbRowToContract` embedded on `Routine.lastRun`
|
|
31
|
+
* — a filtered/relabeled projection of a `RoutineRun` (its `id` renamed to `runId`; nullish
|
|
32
|
+
* `completedAt`/`summary`/`error`/`errorCode` omitted entirely rather than included as `null`).
|
|
33
|
+
* Kept as its own exported pure function (not inlined) so it is directly unit-testable, matching
|
|
34
|
+
* this repo's "extract to a testable pure function" convention for awkward-to-cover branches.
|
|
35
|
+
*/
|
|
36
|
+
export function summarizeLastRun(run) {
|
|
37
|
+
if (!run)
|
|
38
|
+
return null;
|
|
39
|
+
return {
|
|
40
|
+
runId: run.id,
|
|
41
|
+
status: run.status,
|
|
42
|
+
trigger: run.trigger,
|
|
43
|
+
startedAt: run.startedAt,
|
|
44
|
+
...(run.completedAt == null ? {} : { completedAt: run.completedAt }),
|
|
45
|
+
projectId: run.projectId,
|
|
46
|
+
conversationId: run.conversationId,
|
|
47
|
+
agentRunId: run.agentRunId,
|
|
48
|
+
...(run.summary ? { summary: run.summary } : {}),
|
|
49
|
+
...(run.error ? { error: run.error } : {}),
|
|
50
|
+
...(run.errorCode ? { errorCode: run.errorCode } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function cloneContext(context) {
|
|
54
|
+
return {
|
|
55
|
+
...(context.skillIds ? { skillIds: [...context.skillIds] } : {}),
|
|
56
|
+
...(context.pluginIds ? { pluginIds: [...context.pluginIds] } : {}),
|
|
57
|
+
...(context.mcpServerIds ? { mcpServerIds: [...context.mcpServerIds] } : {}),
|
|
58
|
+
...(context.connectorIds ? { connectorIds: [...context.connectorIds] } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Defensive deep clone so a caller mutating a returned `Routine` (its `context` array fields, `schedule`, or `target`) cannot corrupt this store's internal state — the store's only copy is never handed out by reference. */
|
|
62
|
+
function cloneRoutine(routine) {
|
|
63
|
+
return {
|
|
64
|
+
...routine,
|
|
65
|
+
context: cloneContext(routine.context),
|
|
66
|
+
schedule: { ...routine.schedule },
|
|
67
|
+
target: { ...routine.target },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Reference `RoutineStore` implementation: an in-process `Map` of routines plus a flat array of
|
|
72
|
+
* runs, no durable copy — matching `createInMemoryEventLog`'s own scope (a real persistent
|
|
73
|
+
* adapter is `@jini-ai/sqlite`'s job).
|
|
74
|
+
*
|
|
75
|
+
* @complexity `get`/`create`/`update`/`delete` are O(1). `list` is O(n log n) (stable id-sort for
|
|
76
|
+
* deterministic ordering). `listRuns`/`getLatestRun` are O(m log m) in the number of runs
|
|
77
|
+
* recorded for the routine (m), since each sorts by `startedAt` descending on every call rather
|
|
78
|
+
* than maintaining a secondary index — acceptable at this reference implementation's scale.
|
|
79
|
+
*/
|
|
80
|
+
export function createInMemoryRoutineStore() {
|
|
81
|
+
const routines = new Map();
|
|
82
|
+
const runs = [];
|
|
83
|
+
function runsFor(routineId) {
|
|
84
|
+
return runs
|
|
85
|
+
.filter((run) => run.routineId === routineId)
|
|
86
|
+
.sort((a, b) => b.startedAt - a.startedAt);
|
|
87
|
+
}
|
|
88
|
+
async function withLastRun(routine) {
|
|
89
|
+
const latest = runsFor(routine.id)[0] ?? null;
|
|
90
|
+
return { ...cloneRoutine(routine), lastRun: summarizeLastRun(latest) };
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
async list() {
|
|
94
|
+
const ids = Array.from(routines.keys()).sort();
|
|
95
|
+
return Promise.all(ids.map((id) => withLastRun(routines.get(id))));
|
|
96
|
+
},
|
|
97
|
+
async get(id) {
|
|
98
|
+
const routine = routines.get(id);
|
|
99
|
+
return routine ? withLastRun(routine) : null;
|
|
100
|
+
},
|
|
101
|
+
async create(input) {
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
const routine = {
|
|
104
|
+
id: `routine-${randomUUID()}`,
|
|
105
|
+
name: input.name,
|
|
106
|
+
prompt: input.prompt,
|
|
107
|
+
schedule: input.schedule,
|
|
108
|
+
target: input.target,
|
|
109
|
+
skillId: input.skillId ?? null,
|
|
110
|
+
agentId: input.agentId ?? null,
|
|
111
|
+
context: input.context ?? {},
|
|
112
|
+
enabled: input.enabled ?? true,
|
|
113
|
+
nextRunAt: null,
|
|
114
|
+
lastRun: null,
|
|
115
|
+
createdAt: now,
|
|
116
|
+
updatedAt: now,
|
|
117
|
+
};
|
|
118
|
+
routines.set(routine.id, routine);
|
|
119
|
+
return withLastRun(routine);
|
|
120
|
+
},
|
|
121
|
+
async update(id, patch) {
|
|
122
|
+
const existing = routines.get(id);
|
|
123
|
+
if (!existing)
|
|
124
|
+
return null;
|
|
125
|
+
const updated = {
|
|
126
|
+
...existing,
|
|
127
|
+
...(patch.name !== undefined ? { name: patch.name } : {}),
|
|
128
|
+
...(patch.prompt !== undefined ? { prompt: patch.prompt } : {}),
|
|
129
|
+
...(patch.schedule !== undefined ? { schedule: patch.schedule } : {}),
|
|
130
|
+
...(patch.target !== undefined ? { target: patch.target } : {}),
|
|
131
|
+
...(patch.skillId !== undefined ? { skillId: patch.skillId } : {}),
|
|
132
|
+
...(patch.agentId !== undefined ? { agentId: patch.agentId } : {}),
|
|
133
|
+
...(patch.context !== undefined ? { context: patch.context } : {}),
|
|
134
|
+
...(patch.enabled !== undefined ? { enabled: patch.enabled } : {}),
|
|
135
|
+
updatedAt: Date.now(),
|
|
136
|
+
};
|
|
137
|
+
routines.set(id, updated);
|
|
138
|
+
return withLastRun(updated);
|
|
139
|
+
},
|
|
140
|
+
async delete(id) {
|
|
141
|
+
return routines.delete(id);
|
|
142
|
+
},
|
|
143
|
+
async listRuns(routineId, limit) {
|
|
144
|
+
return runsFor(routineId).slice(0, limit);
|
|
145
|
+
},
|
|
146
|
+
async getLatestRun(routineId) {
|
|
147
|
+
return runsFor(routineId)[0] ?? null;
|
|
148
|
+
},
|
|
149
|
+
recordRun(run) {
|
|
150
|
+
if (runs.some((existing) => existing.id === run.id))
|
|
151
|
+
return false;
|
|
152
|
+
runs.push({ ...run });
|
|
153
|
+
return true;
|
|
154
|
+
},
|
|
155
|
+
patchRun(id, patch) {
|
|
156
|
+
const run = runs.find((candidate) => candidate.id === id);
|
|
157
|
+
if (run)
|
|
158
|
+
Object.assign(run, patch);
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=routine-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routine-store.js","sourceRoot":"","sources":["../../src/routines/routine-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DST-safe wall-clock schedule math + input validation for {@link ./scheduler.js}'s
|
|
3
|
+
* `RoutineService`. Ported verbatim (logic unchanged) from OD's `apps/daemon/src/routines.ts` —
|
|
4
|
+
* see that module's own header comment, quoted in the porting proposal
|
|
5
|
+
* (`ADS-memory/reports/proposals/PROP-http-route-packs-automation-routines-2026-07-21.md`), for
|
|
6
|
+
* why this is "real, non-trivial, DST-safe timezone math... genuinely hard to get right and not
|
|
7
|
+
* something to casually reinvent." Its only dependency is `Intl.DateTimeFormat`, which Node ships
|
|
8
|
+
* with full-icu by default — no OD coupling.
|
|
9
|
+
*/
|
|
10
|
+
import type { RoutineProjectTarget, RoutineSchedule, Weekday } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Returns the wall-clock parts of `atUtc` rendered in `timezone`. Uses `Intl.DateTimeFormat`. If
|
|
13
|
+
* the timezone is invalid the formatter throws — callers catch upstream.
|
|
14
|
+
*
|
|
15
|
+
* Exported for the same reason as {@link tzWallToUtcCandidates}/{@link tzWallToUtcGapFallback}:
|
|
16
|
+
* its two defensive fallbacks (an Intl-reported hour of `"24"` at local midnight in some
|
|
17
|
+
* locale/zone combinations, and an unrecognized weekday abbreviation) are ICU-implementation
|
|
18
|
+
* edge cases no timezone on this test runner's ICU data naturally triggers — directly testable
|
|
19
|
+
* with a mocked `Intl.DateTimeFormat.prototype.formatToParts` instead of left unreachable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function partsInTimezone(timezone: string, atUtc: Date): {
|
|
22
|
+
year: number;
|
|
23
|
+
month: number;
|
|
24
|
+
day: number;
|
|
25
|
+
hour: number;
|
|
26
|
+
minute: number;
|
|
27
|
+
second: number;
|
|
28
|
+
weekday: Weekday;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Returns every UTC instant at which the wall clock in `timezone` reads the requested Y-M-D h:m,
|
|
32
|
+
* sorted ascending. Most days have exactly one match. On a fall-back transition day the requested
|
|
33
|
+
* time inside the repeated hour has two matches (one before the transition, one after); outside
|
|
34
|
+
* that hour it still has one. On a spring-forward gap the requested time inside the gap has zero
|
|
35
|
+
* matches — callers fall back to {@link tzWallToUtcGapFallback} to land on a post-gap instant the
|
|
36
|
+
* same day. Probes offsets at three reference points across the day so that both pre- and
|
|
37
|
+
* post-transition offsets are sampled regardless of which side of the transition `tentative`
|
|
38
|
+
* happens to land on. Returns `[]` if `timezone` is invalid.
|
|
39
|
+
*
|
|
40
|
+
* Exported (unlike this module's other low-level helpers) so its own `catch` branch is directly
|
|
41
|
+
* unit-testable: {@link nextWallTimeMatching}, this function's only real caller, already calls
|
|
42
|
+
* the unguarded `partsInTimezone` earlier in its own loop body, so an invalid timezone throws
|
|
43
|
+
* there first — this function's defensive catch is otherwise unreachable through that call path.
|
|
44
|
+
*/
|
|
45
|
+
export declare function tzWallToUtcCandidates(timezone: string, year: number, month: number, day: number, hour: number, minute: number): Date[];
|
|
46
|
+
/**
|
|
47
|
+
* Spring-forward gap fallback: when the requested wall time doesn't exist in `timezone` on this
|
|
48
|
+
* day (clocks jumped over it), return the later of the two probe candidates. That instant has
|
|
49
|
+
* crossed the transition and renders as the first valid post-gap wall time, so a routine still
|
|
50
|
+
* fires today instead of firing an hour early before the gap. Returns `null` if `timezone` is
|
|
51
|
+
* invalid.
|
|
52
|
+
*
|
|
53
|
+
* Exported for the same reason as {@link tzWallToUtcCandidates} — its own `catch` branch is
|
|
54
|
+
* otherwise unreachable through {@link nextWallTimeMatching}'s call path.
|
|
55
|
+
*/
|
|
56
|
+
export declare function tzWallToUtcGapFallback(timezone: string, year: number, month: number, day: number, hour: number, minute: number): Date | null;
|
|
57
|
+
export declare function nextHourlyRunAt(minute: number, now?: Date): Date;
|
|
58
|
+
/**
|
|
59
|
+
* Returns the next instant at which the wall-clock in `timezone` reads "HH:MM" on a day where
|
|
60
|
+
* `predicate(weekday)` holds. Walks forward at most 14 calendar days as a safety bound (covers
|
|
61
|
+
* any weekday-based pattern). Exported (unlike this module's other internal helpers) so its
|
|
62
|
+
* 14-day-exhaustion fallback is directly unit-testable: every `predicate` `nextRunAtForSchedule`
|
|
63
|
+
* actually passes (daily/weekdays/weekly) structurally guarantees a match within 7 days, making
|
|
64
|
+
* that branch otherwise unreachable through the public schedule-kind API alone.
|
|
65
|
+
*/
|
|
66
|
+
export declare function nextWallTimeMatching(timezone: string, time: string, predicate: (weekday: Weekday) => boolean, now: Date): Date | null;
|
|
67
|
+
export declare function nextRunAtForSchedule(schedule: RoutineSchedule, now?: Date): Date | null;
|
|
68
|
+
export declare function isValidWallTime(time: string): boolean;
|
|
69
|
+
export declare function isValidTimezone(tz: string): boolean;
|
|
70
|
+
export declare function validateSchedule(schedule: RoutineSchedule): void;
|
|
71
|
+
export declare function validateTarget(target: RoutineProjectTarget): void;
|
|
72
|
+
//# sourceMappingURL=schedule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../../src/routines/schedule.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAIjF;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,IAAI,GACV;IACD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,CAoCA;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,IAAI,EAAE,CAoBR;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,IAAI,GAAG,IAAI,CAWb;AAwBD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,IAAI,CAQtE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,EACxC,GAAG,EAAE,IAAI,GACR,IAAI,GAAG,IAAI,CAuCb;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,OAAa,GAAG,IAAI,GAAG,IAAI,CAe7F;AAID,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAQnD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CA2BhE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAYjE"}
|