@teamscala/service-boot 0.23.551 → 0.23.554

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 CHANGED
@@ -27,7 +27,7 @@
27
27
  "repository": "https://github.com/teamgbg/service-boot",
28
28
  "sideEffects": false,
29
29
  "type": "module",
30
- "version": "0.23.551",
30
+ "version": "0.23.554",
31
31
  "dependencies": {
32
32
  "@orpc/openapi": "*",
33
33
  "@orpc/server": "*",
@@ -310,4 +310,4 @@
310
310
  "./ws-boot-self-test": "./src/ws-boot-self-test.ts",
311
311
  "./ws-boot-self-test.ts": "./src/ws-boot-self-test.ts"
312
312
  }
313
- }
313
+ }
@@ -18,16 +18,31 @@ const CACHE_HEADERS = {
18
18
  Vary: "Accept",
19
19
  } as const;
20
20
 
21
+ /**
22
+ * A content-addressed asset URL that answers 404 is a race artifact — a page
23
+ * fetched moments before an asset set rotated, or a rebuild mid-write. The
24
+ * URL carries no meaning a cache may keep: a stale HTML will never become
25
+ * valid again, and Cloudflare's default edge TTL caches a header-less 404 for
26
+ * hours (measured 2026-10-23: chunk 404s served `max-age=14400` from the edge
27
+ * for files that existed at origin), re-breaking every client that held the
28
+ * old page. Never cacheable.
29
+ */
30
+ const NOT_FOUND_HEADERS = { "cache-control": "no-store" } as const;
31
+
32
+ export function assetNotFoundResponse(): Response {
33
+ return new Response("Not Found", { status: 404, headers: NOT_FOUND_HEADERS });
34
+ }
35
+
21
36
  export async function handleAssetProxy(req: Request): Promise<Response> {
22
37
  const url = new URL(req.url);
23
38
  const prefix = "/api/assets/";
24
39
  if (!url.pathname.startsWith(prefix)) {
25
- return new Response("Not Found", { status: 404 });
40
+ return assetNotFoundResponse();
26
41
  }
27
42
 
28
43
  const key = decodeURIComponent(url.pathname.slice(prefix.length));
29
44
  if (!key) {
30
- return new Response("Not Found", { status: 404 });
45
+ return assetNotFoundResponse();
31
46
  }
32
47
 
33
48
  const s3 = await getClient();
@@ -48,7 +63,7 @@ export async function handleAssetProxy(req: Request): Promise<Response> {
48
63
  error: err,
49
64
  key,
50
65
  });
51
- return new Response("Not Found", { status: 404 });
66
+ return assetNotFoundResponse();
52
67
  }
53
68
 
54
69
  const baseEtag = stats.etag || `"${key}"`;
@@ -80,7 +80,7 @@ async function resolveHandler(
80
80
 
81
81
  async function recordFire(jobSlug: string, queueName: string): Promise<string | null> {
82
82
  try {
83
- const row = await getPrisma().scheduledJobRuns.create({
83
+ const row = await getPrisma().scheduled_job_runs.create({
84
84
  data: { job_slug: jobSlug, queue_name: queueName, status: "active", attempt: 1 },
85
85
  });
86
86
  return String(row.id);
@@ -101,7 +101,7 @@ async function recordOutcome(
101
101
  ): Promise<void> {
102
102
  if (runId === null) return;
103
103
  try {
104
- await getPrisma().scheduledJobRuns.update({
104
+ await getPrisma().scheduled_job_runs.update({
105
105
  where: { id: runId },
106
106
  data: { status, completed_at: new Date(), error, result },
107
107
  });
@@ -115,7 +115,7 @@ async function recordOutcome(
115
115
  async function expirePriorProcessRuns(slugs: string[]): Promise<void> {
116
116
  for (const slug of slugs) {
117
117
  try {
118
- await getPrisma().scheduledJobRuns.updateMany({
118
+ await getPrisma().scheduled_job_runs.updateMany({
119
119
  where: { job_slug: slug, status: "active" },
120
120
  data: {
121
121
  status: "expired",
@@ -95,7 +95,11 @@ export async function registerStaticAssets(
95
95
  headers: { "cache-control": "public, max-age=31536000, immutable" },
96
96
  });
97
97
  }
98
- return c.notFound();
98
+ // A hashed asset URL that misses is a race artifact (page from before an
99
+ // asset rotation, or a rebuild mid-write) — never cacheable, else the
100
+ // edge pins the 404 and re-breaks every client holding the old page
101
+ // (assetNotFoundResponse's header note).
102
+ return new Response("Not Found", { status: 404, headers: { "cache-control": "no-store" } });
99
103
  });
100
104
 
101
105
  app.get("/ext/*", async (c) => {
@@ -110,6 +114,6 @@ export async function registerStaticAssets(
110
114
  headers: { "cache-control": "public, max-age=3600" },
111
115
  });
112
116
  }
113
- return c.notFound();
117
+ return new Response("Not Found", { status: 404, headers: { "cache-control": "no-store" } });
114
118
  });
115
119
  }
@@ -87,7 +87,12 @@ export async function buildMinimalApp(
87
87
  const logo = await resolveErrorPageLogo(serviceSlug);
88
88
  return buildResourceNotFoundResponse("page", logo, 404);
89
89
  }
90
- return c.json({ error: "Not Found" }, 404);
90
+ // No-store: a 404 a cache may keep (Cloudflare's default edge TTL caches
91
+ // a header-less 404 on cacheable extensions for hours) re-serves the miss
92
+ // long after the resource exists — same class as the asset-route misses.
93
+ return c.json({ error: "Not Found" }, 404, {
94
+ "cache-control": "no-store",
95
+ });
91
96
  });
92
97
 
93
98
  app.use("*", readinessGate);