@tribe-nest/forge 1.13.0 → 1.14.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/package.json +4 -2
- package/src/server/jobs.ts +42 -2
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tribe-nest/forge",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"publishConfig": {
|
|
3
|
+
"version": "1.14.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
5
7
|
"description": "Forge — the headless React SDK for building custom TribeNest creator sites (the Hydrogen of TribeNest). Exposes the backend (memberships, commerce, gated content, ticketing, courses, booking, auth) as data + behavior primitives.",
|
|
6
8
|
"exports": {
|
|
7
9
|
".": "./src/index.ts",
|
package/src/server/jobs.ts
CHANGED
|
@@ -25,7 +25,14 @@ export interface JobSchedule {
|
|
|
25
25
|
intervalMs: number;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
/** Context passed to a job handler — carries the platform token so the handler
|
|
29
|
+
* can write app-owned data back (writeAppCollection). */
|
|
30
|
+
export interface JobContext {
|
|
31
|
+
/** The app-sync token this run was invoked with (forward it to writes). */
|
|
32
|
+
token: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type JobHandler = (payload: unknown, env: unknown, ctx: JobContext) => Promise<void> | void;
|
|
29
36
|
|
|
30
37
|
export interface AppJobsConfig {
|
|
31
38
|
/** The app id (VITE_APP_ID). */
|
|
@@ -95,10 +102,43 @@ export async function handleAppJobRun(
|
|
|
95
102
|
const handler = body.job ? jobs[body.job] : undefined;
|
|
96
103
|
if (!handler) return new Response("unknown job", { status: 404 });
|
|
97
104
|
try {
|
|
98
|
-
|
|
105
|
+
// Pass the verified token so the handler can write app-owned data back
|
|
106
|
+
// (writeAppCollection) without re-minting anything.
|
|
107
|
+
await handler(body.payload ?? null, env, { token });
|
|
99
108
|
return new Response("ok");
|
|
100
109
|
} catch (err) {
|
|
101
110
|
// Non-2xx → the DO retries (one-time) / re-fires next interval (recurring).
|
|
102
111
|
return new Response(`job failed: ${(err as Error).message}`, { status: 500 });
|
|
103
112
|
}
|
|
104
113
|
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Persist APP-OWNED rows into one of the app's collections from a job handler —
|
|
117
|
+
* 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.
|
|
120
|
+
*
|
|
121
|
+
* jobs = {
|
|
122
|
+
* "sync-shopify": async (_p, env, ctx) => {
|
|
123
|
+
* const orders = await fetchShopify(env);
|
|
124
|
+
* await writeAppCollection(ctx, cfg, "shopify-orders",
|
|
125
|
+
* orders.map(o => ({ data: { orderId: o.id, total: o.total, utmSource: o.utm } })),
|
|
126
|
+
* { replace: true });
|
|
127
|
+
* },
|
|
128
|
+
* };
|
|
129
|
+
*/
|
|
130
|
+
export async function writeAppCollection(
|
|
131
|
+
ctx: JobContext,
|
|
132
|
+
cfg: AppJobsConfig,
|
|
133
|
+
slug: string,
|
|
134
|
+
entries: Array<{ data: Record<string, unknown>; status?: "draft" | "published" }>,
|
|
135
|
+
opts?: { replace?: boolean },
|
|
136
|
+
): Promise<{ created: number }> {
|
|
137
|
+
const res = await fetch(`${cfg.apiUrl}/public/apps/${cfg.appId}/collections/${encodeURIComponent(slug)}/entries`, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: { "content-type": "application/json", "x-app-sync-token": ctx.token },
|
|
140
|
+
body: JSON.stringify({ entries, replace: opts?.replace === true }),
|
|
141
|
+
});
|
|
142
|
+
if (!res.ok) throw new Error(`writeAppCollection ${slug} failed: ${res.status} ${await res.text().catch(() => "")}`);
|
|
143
|
+
return res.json();
|
|
144
|
+
}
|