@teamscala/service-boot 0.23.285 → 0.23.287

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.285",
9
+ "version": "0.23.287",
10
10
  "sideEffects": false,
11
11
  "files": [
12
12
  "src"
@@ -19,6 +19,7 @@
19
19
  "@teamscala/auth": "*",
20
20
  "@teamscala/aws": "*",
21
21
  "@teamscala/cache": "*",
22
+ "@teamscala/channel": "*",
22
23
  "@teamscala/chat-completion": "*",
23
24
  "@teamscala/db": "*",
24
25
  "@teamscala/db-validation": "*",
@@ -448,6 +449,14 @@
448
449
  "types": "./src/handlers/bun-stable-watch.d.ts",
449
450
  "default": "./src/handlers/bun-stable-watch.ts"
450
451
  },
452
+ "./handlers/pane-outbox-drain": {
453
+ "types": "./src/handlers/pane-outbox-drain.d.ts",
454
+ "default": "./src/handlers/pane-outbox-drain.ts"
455
+ },
456
+ "./handlers/pane-outbox-drain.ts": {
457
+ "types": "./src/handlers/pane-outbox-drain.d.ts",
458
+ "default": "./src/handlers/pane-outbox-drain.ts"
459
+ },
451
460
  "./handlers/projects-digest": {
452
461
  "types": "./src/handlers/projects-digest.d.ts",
453
462
  "default": "./src/handlers/projects-digest.ts"
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @system server-boot
3
+ * @status handwritten
4
+ * @edit edit directly
5
+ *
6
+ * The `pane-outbox-drain` pg-boss handler — delivers messages the operator's
7
+ * WhatsApp PA queues into `pane_messages` to the addressed agent pane.
8
+ *
9
+ * WHY THE REGISTRATION LIVES HERE AND THE WORK LIVES IN @teamscala/channel.
10
+ * The delivery itself belongs to the channel capability, so `paneOutboxDrainTick`
11
+ * is owned by `@teamscala/channel` beside the dispatch handler it calls. But
12
+ * `pgBossHandlerRegistry` is SERVER tier and `@teamscala/channel` is ADAPTER
13
+ * tier, so the channel package cannot register a pg-boss handler without an
14
+ * upward dependency — forbidden by `vertical-dependency-only`, which has no
15
+ * carve-out. server → adapter is DOWNWARD, so the registration sits here and
16
+ * imports the work, exactly as the four sibling handlers in this directory do.
17
+ *
18
+ * The MountContext route (`ctx.registerScheduledJob`) is tier-legal but NOT
19
+ * usable: `start-scheduled-jobs.ts` offers only `long_running` (invoked once,
20
+ * runs forever) and `recurring` (whose handlers "manage their own setInterval").
21
+ * Both are the in-process timer that `pg-boss-for-background-work` forbids and
22
+ * that the timer half of `in-memory-state-is-not-durable` describes — a timer
23
+ * only fires if its host outlives it, so on a restart-prone service it silently
24
+ * never runs while logging that it started. pg-boss is the only durable option,
25
+ * because the job row outlives any worker.
26
+ *
27
+ * Scoped to scala-channel by its `scheduled_job` row's `service_slug`, so it
28
+ * runs on the service that owns the channel rather than on scala-agents, whose
29
+ * UI-asset build failures used to take PA delivery down with them.
30
+ *
31
+ * Registered under slug `@teamscala/service-boot:pane-outbox-drain`.
32
+ */
33
+ import { paneOutboxDrainTick } from "@teamscala/channel/pane-outbox/drain-tick";
34
+ import { pgBossHandlerRegistry } from "../pg-boss-handler-registry.ts";
35
+
36
+ pgBossHandlerRegistry.register(
37
+ "@teamscala/service-boot:pane-outbox-drain",
38
+ paneOutboxDrainTick,
39
+ );
@@ -20,6 +20,7 @@ import "./handlers/upstream-issue-sync.ts";
20
20
  import "./handlers/scheduled-browser-tests.ts";
21
21
  import "./handlers/scala-tools-currency-watch.ts";
22
22
  import "./handlers/registry-stale-inactive-sweep.ts";
23
+ import "./handlers/pane-outbox-drain.ts";
23
24
  export interface PgBossMountContext {
24
25
  databaseUrl: string;
25
26
  serviceSlug: string;
@@ -33,6 +33,7 @@ import "./handlers/upstream-issue-sync.ts";
33
33
  import "./handlers/scheduled-browser-tests.ts";
34
34
  import "./handlers/scala-tools-currency-watch.ts";
35
35
  import "./handlers/registry-stale-inactive-sweep.ts";
36
+ import "./handlers/pane-outbox-drain.ts";
36
37
 
37
38
  export interface PgBossMountContext {
38
39
  databaseUrl: string;
@@ -398,9 +398,11 @@ export async function buildFullApp(
398
398
 
399
399
  // Client asset building (expensive: Vite build)
400
400
  await buildClientAssets(serviceSlug);
401
+ log.info(`[two-phase-boot:${serviceSlug}] phase-2 step: buildClientAssets DONE`);
401
402
 
402
403
  // Scheduled jobs
403
404
  startScheduledJobs(config, mountRegistry, serviceSlug);
405
+ log.info(`[two-phase-boot:${serviceSlug}] phase-2 step: scheduledJobs DONE`);
404
406
 
405
407
  // pg-boss (mount for UI services - wasn't mounted in minimal app)
406
408
  const { jobs: pgBossJobs } = await mountPgBoss({
@@ -408,6 +410,7 @@ export async function buildFullApp(
408
410
  serviceSlug,
409
411
  packages: config.server_packages ?? [],
410
412
  });
413
+ log.info(`[two-phase-boot:${serviceSlug}] phase-2 step: mountPgBoss DONE (${pgBossJobs.length} jobs)`);
411
414
  if (pgBossJobs.length > 0) {
412
415
  log.info(
413
416
  `[two-phase-boot:${serviceSlug}] pg-boss mounted ${pgBossJobs.length} job(s): ${pgBossJobs.map((j) => j.handlerSlug).join(", ")}`,
package/src/ui-service.ts CHANGED
@@ -103,7 +103,16 @@ export async function bootUiService(
103
103
  // Phase 2: Build full app in background (non-blocking)
104
104
  ;(async () => {
105
105
  try {
106
- const fullAppResult = await buildFullApp(minimalAppResult, deps);
106
+ const PHASE2_TIMEOUT_MS = 120_000;
107
+ const fullAppResult = await Promise.race([
108
+ buildFullApp(minimalAppResult, deps),
109
+ new Promise<never>((_, reject) =>
110
+ setTimeout(
111
+ () => reject(new Error(`buildFullApp timed out after ${PHASE2_TIMEOUT_MS / 1000}s — phase 2 hung silently, see step logs`)),
112
+ PHASE2_TIMEOUT_MS,
113
+ ),
114
+ ),
115
+ ]);
107
116
  const fullBuildTime = Date.now();
108
117
  const fullDuration = fullBuildTime - startTime;
109
118