bundle-offers-app/app/lib/scheduler.server.ts
metatroncubeswdev 365e45406a Bundle Offers Shopify app: discount function, admin, storefront, deploy stack
Rust discount function (one automatic discount evaluating all offers in priority
order, parity with the Odoo engine), React Router admin with Polaris, theme app
extension and app proxy pages, webhooks incl. GDPR, managed-pricing limits, and
a Docker Compose stack (Postgres + Caddy HTTPS) for a single VPS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:35:58 -04:00

45 lines
1.7 KiB
TypeScript

// Re-publishes a shop's configuration at every offer start/end boundary (brief §11.5).
// Every 30 s it claims shops whose nextSyncAt has passed (atomic update, safe with several app instances)
// and publishes them with the shop's offline token. The function's guard covers the seconds in between
// for offers that END (see extensions/bundle-discount/src/config.rs), so no discount outlives its offer.
import db from "../db.server";
import { unauthenticated } from "../shopify.server";
import { publishShop } from "./publish.server";
const TICK_MS = 30_000;
export async function runDueSyncs(now = new Date()) {
const due = await db.shop.findMany({
where: { uninstalledAt: null, nextSyncAt: { lte: now } },
select: { id: true, domain: true, nextSyncAt: true },
take: 50,
});
for (const shop of due) {
// claim: only one instance gets each boundary
const claimed = await db.shop.updateMany({
where: { id: shop.id, nextSyncAt: shop.nextSyncAt },
data: { nextSyncAt: new Date(now.getTime() + 5 * 60_000) }, // safety retry if this run dies
});
if (!claimed.count) continue;
try {
const { admin } = await unauthenticated.admin(shop.domain);
await publishShop(admin, shop.domain, new Date());
} catch (e) {
console.error(`[scheduler] publish failed for ${shop.domain}`, e);
}
}
}
declare global {
// eslint-disable-next-line no-var
var __bundleOffersScheduler: NodeJS.Timeout | undefined;
}
export function startScheduler() {
if (globalThis.__bundleOffersScheduler || process.env.DISABLE_SCHEDULER === "1") return;
globalThis.__bundleOffersScheduler = setInterval(() => {
runDueSyncs().catch((e) => console.error("[scheduler]", e));
}, TICK_MS);
globalThis.__bundleOffersScheduler.unref?.();
}