@tribe-nest/forge 1.14.0 → 1.15.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 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tribe-nest/forge",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -21,8 +21,17 @@ interface AppJobsStub {
21
21
  export interface JobSchedule {
22
22
  /** Stable job name — also the key in your job map + the `scheduleId` in the token. */
23
23
  name: string;
24
- /** How often to run, in milliseconds (e.g. 60_000 = every minute). */
25
- intervalMs: number;
24
+ /**
25
+ * Fixed interval in milliseconds (e.g. 60_000 = every minute). Provide this OR
26
+ * `cron`, not both. Best for "every N minutes/hours"; the first run lands one
27
+ * interval after the deploy.
28
+ */
29
+ intervalMs?: number;
30
+ // A 5-field UTC cron expression: minute hour day-of-month month day-of-week.
31
+ // e.g. "0 3 * * *" = daily at 03:00 UTC; "0 0,6,12,18 * * *" = every 6 hours;
32
+ // "0 9 * * 1-5" = 09:00 UTC on weekdays. Provide this OR `intervalMs`. Use
33
+ // cron when a run must land at a specific wall-clock time; evaluation is UTC.
34
+ cron?: string;
26
35
  }
27
36
 
28
37
  /** Context passed to a job handler — carries the platform token so the handler
@@ -48,7 +57,11 @@ const stub = (env: AppJobsEnv, appId: string): AppJobsStub => env.JOBS.get(env.J
48
57
  // A stable fingerprint of the desired schedule set, so the DO skips reconcile
49
58
  // when nothing changed (sorted by name → order-independent).
50
59
  function scheduleHash(schedules: JobSchedule[]): string {
51
- return JSON.stringify([...schedules].sort((a, b) => a.name.localeCompare(b.name)).map((s) => [s.name, s.intervalMs]));
60
+ return JSON.stringify(
61
+ [...schedules]
62
+ .sort((a, b) => a.name.localeCompare(b.name))
63
+ .map((s) => [s.name, s.intervalMs ?? null, s.cron ?? null]),
64
+ );
52
65
  }
53
66
 
54
67
  /**
@@ -73,9 +86,9 @@ export async function enqueueAppJob(
73
86
  }
74
87
 
75
88
  /**
76
- * Handle the DO's alarm callback (`POST /_jobs/run`). Verifies the platform
89
+ * Handle the DO's alarm callback (`POST /api/tn-jobs/run`). Verifies the platform
77
90
  * token, looks up the named handler in your job map, and runs it with the app's
78
- * `env` (so `env.SHOPIFY_TOKEN` etc. are available). Wire your `/_jobs/run` route
91
+ * `env` (so `env.SHOPIFY_TOKEN` etc. are available). Wire your `/api/tn-jobs/run` route
79
92
  * to this.
80
93
  */
81
94
  export async function handleAppJobRun(
@@ -115,8 +128,18 @@ export async function handleAppJobRun(
115
128
  /**
116
129
  * Persist APP-OWNED rows into one of the app's collections from a job handler —
117
130
  * the sanctioned server-side write path (do NOT invent an endpoint). Authenticated
118
- * by the run's token (from `JobContext`). `replace: true` = full refresh (archive
119
- * the existing app-owned rows first), so a re-sync doesn't accumulate duplicates.
131
+ * by the run's token (from `JobContext`).
132
+ *
133
+ * IMPORTANT — this ALWAYS INSERTS (there is no upsert-by-key). A stable title does
134
+ * NOT make re-runs idempotent (slugs auto-dedupe to -2/-3…), so re-running WILL
135
+ * duplicate rows. For idempotency use `replace: true` (archives every existing
136
+ * app-owned row in the collection, then inserts) AND re-pull the FULL dataset each
137
+ * run. NEVER combine `replace: true` with an incremental window (e.g.
138
+ * updated_at_min / last-7-days) — it archives the rows outside the window too and
139
+ * you lose history. (True incremental upsert-by-key is not supported yet.)
140
+ *
141
+ * Handlers receive `(payload, env, ctx)` — pass `ctx` here; `cfg` is your own
142
+ * config built from `import.meta.env`.
120
143
  *
121
144
  * jobs = {
122
145
  * "sync-shopify": async (_p, env, ctx) => {