@tribe-nest/forge 1.19.0 → 1.20.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/server/jobs.ts +30 -32
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tribe-nest/forge",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -14,21 +14,8 @@ export interface AppJobsEnv {
14
14
  }
15
15
 
16
16
  interface AppJobsStub {
17
- reconcile: (
18
- script: string,
19
- appId: string,
20
- desired: JobSchedule[],
21
- hash: string,
22
- host: string,
23
- ) => Promise<{ changed: boolean }>;
24
- enqueue: (
25
- script: string,
26
- appId: string,
27
- name: string,
28
- payload: unknown,
29
- delayMs: number,
30
- host: string,
31
- ) => Promise<void>;
17
+ reconcile: (script: string, appId: string, desired: JobSchedule[], hash: string) => Promise<{ changed: boolean }>;
18
+ enqueue: (script: string, appId: string, name: string, payload: unknown, delayMs: number) => Promise<void>;
32
19
  status: () => Promise<AppJobsStatus>;
33
20
  }
34
21
 
@@ -37,23 +24,34 @@ interface AppJobsStub {
37
24
  * and what happened on the LAST delivery attempt. Because jobs fail silently (no
38
25
  * logs / dead-letter), this is how you SEE why one didn't run:
39
26
  * - `lastDispatchAt` null while `alarmAt` is in the past → the alarm isn't firing;
40
- * - `appHost` null this app predates host registration; redeploy it;
41
- * - `lastError` starting "dispatch threw:" → the callback never reached your app
42
- * (host unroutable / your Worker is down);
27
+ * - `lastError` starting "runner unreachable:" a platform-side fault, not yours;
28
+ * - `lastDispatchStatus` "404" + "no dispatch target" → the platform has no
29
+ * record of where to send your runs; republish the app;
30
+ * - `lastDispatchStatus` "502" → your Worker isn't deployed;
43
31
  * - `lastDispatchStatus` "401" → your app was reached but rejected the token
44
32
  * (the dispatch-worker's APP_SYNC_SECRET ≠ the backend JWT_SECRET);
45
- * - `lastDispatchStatus` "402" → the site is suspended for billing;
46
- * - `lastDispatchStatus` "404" no /api/tn-jobs/run route (or an unknown job name).
33
+ * - `lastDispatchStatus` "404" + "unknown job" → the run landed but no handler
34
+ * in your job map has that name.
35
+ *
36
+ * For "my one-time job never ran", read `deadLetters` first. `queueDepth` on its
37
+ * own tells you almost nothing: `enqueueAppJob(..., delayMs: 0)` causes the run
38
+ * to be dispatched and the row deleted within seconds, so depth reads 0 whether
39
+ * the job succeeded or was never enqueued at all. A job that keeps failing
40
+ * retries for ~15 minutes and then appears in `deadLetters` with its error.
47
41
  */
48
42
  export interface AppJobsStatus {
49
43
  appId: string | null;
50
- /** The public host job runs are posted to. Null on apps deployed before this existed. */
51
- appHost: string | null;
52
44
  now: number;
53
45
  alarmAt: number | null;
54
46
  schedules: Array<{ name: string; intervalMs: number | null; cron: string | null; nextRun: number }>;
55
47
  queueDepth: number;
48
+ /** Names currently queued with their next attempt time. */
49
+ queued: Array<{ name: string; runAt: number; attempts: number }>;
50
+ /** One-time jobs that exhausted their retries and were given up on, newest first. */
51
+ deadLetters: Array<{ name: string; attempts: number; failedAt: number; error: string | null }>;
56
52
  lastDispatchAt: number | null;
53
+ /** Which job the last delivery attempt was for — all jobs share one error slot. */
54
+ lastDispatchJob: string | null;
57
55
  lastDispatchScript: string | null;
58
56
  lastDispatchStatus: string | null;
59
57
  lastError: string | null;
@@ -87,18 +85,18 @@ export type JobHandler = (payload: unknown, env: unknown, ctx: JobContext) => Pr
87
85
  export interface AppJobsConfig {
88
86
  /** The app id (VITE_APP_ID). */
89
87
  appId: string;
90
- /** This Worker's script name in the dispatch namespace (VITE_APP_SCRIPT). */
91
- script: string;
92
88
  /** The TribeNest public API base (VITE_API_URL) — used to verify the callback token. */
93
89
  apiUrl: string;
94
90
  /**
95
- * This app's public host, no scheme (VITE_APP_HOST) e.g. "acme.tribenest.co".
96
- * The job runtime posts each run to `https://<host>/api/tn-jobs/run`; it cannot
97
- * reach the app any other way, so an app that never registers a host will not
98
- * run jobs (its status reports "no app host recorded"). Injected at deploy
99
- * do not hand-set it.
91
+ * @deprecated No longer needed leave it unset in new apps.
92
+ *
93
+ * This Worker's script name in the dispatch namespace (VITE_APP_SCRIPT). Your
94
+ * app's address is a PLATFORM fact: it is recorded at publish and resolved by
95
+ * the job runtime from your `appId`, so an app no longer reports where it
96
+ * lives. Still accepted (and forwarded as a fallback hint) so apps published
97
+ * before that change keep dispatching without a rebuild.
100
98
  */
101
- host: string;
99
+ script?: string;
102
100
  }
103
101
 
104
102
  const stub = (env: AppJobsEnv, appId: string): AppJobsStub => env.JOBS.get(env.JOBS.idFromName(appId));
@@ -120,7 +118,7 @@ function scheduleHash(schedules: JobSchedule[]): string {
120
118
  * one, a removed one is pruned.
121
119
  */
122
120
  export async function reconcileAppJobs(env: AppJobsEnv, cfg: AppJobsConfig, schedules: JobSchedule[]): Promise<void> {
123
- await stub(env, cfg.appId).reconcile(cfg.script, cfg.appId, schedules, scheduleHash(schedules), cfg.host);
121
+ await stub(env, cfg.appId).reconcile(cfg.script ?? "", cfg.appId, schedules, scheduleHash(schedules));
124
122
  }
125
123
 
126
124
  /** Enqueue a durable one-time job (survives redeploys, retried on failure). */
@@ -131,7 +129,7 @@ export async function enqueueAppJob(
131
129
  payload?: unknown,
132
130
  delayMs = 0,
133
131
  ): Promise<void> {
134
- await stub(env, cfg.appId).enqueue(cfg.script, cfg.appId, name, payload ?? null, delayMs, cfg.host);
132
+ await stub(env, cfg.appId).enqueue(cfg.script ?? "", cfg.appId, name, payload ?? null, delayMs);
135
133
  }
136
134
 
137
135
  /**