@isomorph.ai/app-sdk 1.4.0 → 1.4.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/dist/runtime.d.ts +5 -2
- package/dist/runtime.js +34 -2
- package/package.json +1 -1
package/dist/runtime.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export declare class Runs {
|
|
|
39
39
|
private readonly client;
|
|
40
40
|
constructor(client: IsomorphClient);
|
|
41
41
|
private table;
|
|
42
|
+
/** The cheapest read the runs table answers; rejects while the gateway's data path is down. */
|
|
43
|
+
ready(): Promise<void>;
|
|
42
44
|
enqueue(job: string, input: unknown, owner: string): Promise<{
|
|
43
45
|
id: string;
|
|
44
46
|
}>;
|
|
@@ -86,10 +88,11 @@ export declare class Runtime {
|
|
|
86
88
|
* `--run-job <name>`: one scheduled run of one job, as a CronJob pod runs it.
|
|
87
89
|
* The run is the scheduler's own (`runScheduled`, the current minute as its
|
|
88
90
|
* tick), so it is loaded, run and recorded exactly as an in-process tick
|
|
89
|
-
* would have been. False when the app has no such job
|
|
90
|
-
* the log names which.
|
|
91
|
+
* would have been. False when the app has no such job, the gateway's data
|
|
92
|
+
* path never answered, or the handler threw; the log names which.
|
|
91
93
|
*/
|
|
92
94
|
runJobOnce(name: string, now?: Date): Promise<boolean>;
|
|
95
|
+
private awaitDataPath;
|
|
93
96
|
stop(): Promise<void>;
|
|
94
97
|
private every;
|
|
95
98
|
private refreshToken;
|
package/dist/runtime.js
CHANGED
|
@@ -54,6 +54,10 @@ const LEASE_MS = 5 * 60_000;
|
|
|
54
54
|
const QUEUE_POLL_MS = 2_000;
|
|
55
55
|
const TOKEN_REFRESH_MS = 60_000;
|
|
56
56
|
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
57
|
+
// A CronJob pod may start while the app's database is still waking: --run-job
|
|
58
|
+
// waits this long for the gateway's data path, probing every retry interval.
|
|
59
|
+
const DATA_PATH_WAIT_MS = 90_000;
|
|
60
|
+
const DATA_PATH_RETRY_MS = 3_000;
|
|
57
61
|
/** Every `actions/<name>.ts` and `jobs/<name>.ts` the app carries, by logical name. */
|
|
58
62
|
export async function discover(root) {
|
|
59
63
|
const list = async (dir) => {
|
|
@@ -150,6 +154,10 @@ export class Runs {
|
|
|
150
154
|
this.client = client;
|
|
151
155
|
}
|
|
152
156
|
table() { return this.client.data.from(RUNS_TABLE); }
|
|
157
|
+
/** The cheapest read the runs table answers; rejects while the gateway's data path is down. */
|
|
158
|
+
async ready() {
|
|
159
|
+
await this.table().select('id').limit(1);
|
|
160
|
+
}
|
|
153
161
|
async enqueue(job, input, owner) {
|
|
154
162
|
const { data } = await this.table().insert({ job, input: input ?? null, status: 'queued', attempt: 0, max_attempts: DEFAULT_MAX_ATTEMPTS, progress: 0, owner_subject: owner, cancel_requested: false }).select('id').single();
|
|
155
163
|
return { id: data.id };
|
|
@@ -232,8 +240,8 @@ export class Runtime {
|
|
|
232
240
|
* `--run-job <name>`: one scheduled run of one job, as a CronJob pod runs it.
|
|
233
241
|
* The run is the scheduler's own (`runScheduled`, the current minute as its
|
|
234
242
|
* tick), so it is loaded, run and recorded exactly as an in-process tick
|
|
235
|
-
* would have been. False when the app has no such job
|
|
236
|
-
* the log names which.
|
|
243
|
+
* would have been. False when the app has no such job, the gateway's data
|
|
244
|
+
* path never answered, or the handler threw; the log names which.
|
|
237
245
|
*/
|
|
238
246
|
async runJobOnce(name, now = new Date()) {
|
|
239
247
|
this.inventory = await discover(this.options.root);
|
|
@@ -242,8 +250,32 @@ export class Runtime {
|
|
|
242
250
|
this.log(`this app has no ${JOBS_DIR}/${name}.ts`);
|
|
243
251
|
return false;
|
|
244
252
|
}
|
|
253
|
+
if (this.options.gatewayUrl && !(await this.awaitDataPath()))
|
|
254
|
+
return false;
|
|
245
255
|
return this.runScheduled(name, new Date(Math.floor(now.getTime() / 60_000) * 60_000));
|
|
246
256
|
}
|
|
257
|
+
// The pod is scheduled by the cluster clock, not by the app's readiness: its
|
|
258
|
+
// database may be paused or waking (plan §5.3) and the gateway answers 5xx
|
|
259
|
+
// until it is up. The handler runs once the cheapest read answers; when it
|
|
260
|
+
// never does within the wait, the run fails with one line naming the last error.
|
|
261
|
+
async awaitDataPath() {
|
|
262
|
+
const deadline = Date.now() + DATA_PATH_WAIT_MS;
|
|
263
|
+
for (;;) {
|
|
264
|
+
try {
|
|
265
|
+
await this.runs.ready();
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
270
|
+
if (Date.now() + DATA_PATH_RETRY_MS > deadline) {
|
|
271
|
+
this.log(`the data path did not answer within ${DATA_PATH_WAIT_MS / 1000} s: ${reason}`);
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
this.log(`waiting for the data path: ${reason}`);
|
|
275
|
+
await new Promise((wake) => setTimeout(wake, DATA_PATH_RETRY_MS));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
247
279
|
async stop() {
|
|
248
280
|
this.stopped = true;
|
|
249
281
|
for (const timer of this.timers)
|