@teamscala/service-boot 0.23.470 → 0.23.472

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
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "name": "@teamscala/service-boot",
8
8
  "type": "module",
9
- "version": "0.23.470",
9
+ "version": "0.23.472",
10
10
  "sideEffects": false,
11
11
  "imports": {
12
12
  "#service-boot/*": "./src/*",
@@ -40,6 +40,36 @@ export async function mountOrpcRouter(
40
40
  );
41
41
  const schemaPath = join(process.cwd(), "prisma/schema.prisma");
42
42
 
43
+ // ORPC forward mode: when SCALA_ORPC_FORWARD_URL is set and there is no
44
+ // local bundle, /api/rpc/* proxies to the central ORPC surface instead of
45
+ // mounting a per-service router. This is the collapse's consolidation path —
46
+ // services shed their local bundle and the central surface serves all
47
+ // row-composed procedures.
48
+ const forwardUrl = process.env.SCALA_ORPC_FORWARD_URL;
49
+ if (!await Bun.file(routerPath).exists() && forwardUrl) {
50
+ const base = forwardUrl.replace(/\/+$/, "");
51
+ app.all("/api/rpc/*", async (c) => {
52
+ const url = new URL(c.req.url);
53
+ const target = `${base}${url.pathname}${url.search}`;
54
+ const res = await fetch(target, {
55
+ method: c.req.method,
56
+ headers: c.req.raw.headers,
57
+ body: c.req.raw.body,
58
+ // @ts-ignore — duplex is required for streaming bodies in Bun
59
+ duplex: "half",
60
+ });
61
+ return new Response(res.body, {
62
+ status: res.status,
63
+ statusText: res.statusText,
64
+ headers: res.headers,
65
+ });
66
+ });
67
+ log.info(
68
+ `[scala-install:${serviceSlug}] ORPC forward mounted at /api/rpc/* → ${base}`,
69
+ );
70
+ return { routeCount: 0 };
71
+ }
72
+
43
73
  // No generated ORPC router is the EXPECTED state for daemon-only / boot-hook
44
74
  // services (session-picker, dep-sync, etc.) that own no DB schema or
45
75
  // api_routes — they serve no /api/rpc surface. Skip silently: importing a
@@ -204,6 +234,17 @@ export async function mountOrpcRouter(
204
234
  context: { prisma, db8, headers: c.req.raw.headers, resolveUser },
205
235
  });
206
236
  if (!result?.matched) return c.notFound();
237
+ // ORPC swallows procedure errors into a 500 response with a
238
+ // generic INTERNAL_SERVER_ERROR body. Without logging here the
239
+ // only signal is a fleet-wide spike in 500s with no traceback —
240
+ // measured 2026-09-04 when the regenerated prisma client shipped
241
+ // an empty orm and every model action returned a silent 500.
242
+ // This makes every 500 LOUD with the request path for routing.
243
+ if (result.response.status >= 500) {
244
+ log.error(
245
+ `[ORPC] ${c.req.method} ${c.req.path} → ${result.response.status}`,
246
+ );
247
+ }
207
248
  return result.response;
208
249
  },
209
250
  );