Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

13 — State Management (Scheduler Module)

Bloc-based state for the Scheduled Jobs console per 00-shared/06. Server truth: BullMQ owns job state; the API exposes only the registry (E1-E3 implemented, E4-E8 (proposed)). The console is superadmin-only (platform level, scheduler.controller.ts:24,31,38).


1. State model (JobsBloc)

sealed class JobsState { const JobsState(); }

class JobsInitial extends JobsState {}
class JobsLoading extends JobsState {}                       // first load
class JobsLoaded extends JobsState {
  final List<JobRow> jobs;          // {queue,name,pattern,tz} (scheduler.service.ts:152-186)
  final Map<String, RunSummary> enrich; // (proposed) lastRunAt,lastStatus,nextRunAt — key queue:name
  final DateTime fetchedAt;
}
class JobsError extends JobsState {
  final AppError error;             // 401/403/429/5xx
  final List<JobRow>? lastGood;     // keep-last-good
}

JobRow mirrors scheduler.service.ts:152-186 exactly — no client-side derivation (09 B8/B9). Status badges read only enrich when present, else unknown (07 C1).

2. Blocs

BlocOwnsSource
JobsBlocregistry list, enrich map, poll loopE1 + E4 (proposed)
JobDetailBlocone job's definition + run historyE1 + E4
CreateScheduleBlocF1 form state, submit, duplicate warnE2
DlqBlocDLQ records, replay/deleteE7/E8 (proposed)
LogsBlocS4 log stream, level filter, tail-liveE5 (proposed)

Single JobsBloc per /admin/scheduler route, provided in the screen, disposed on pop (poll timer dies with it).

3. Fetch flow (JobsBloc)

flowchart TD
    A[open /admin/scheduler] --> B{lastGood?}
    B -->|no| C[emit Loading]
    C --> D[GET /scheduler]
    D -->|200| E[emit Loaded jobs fetchedAt=now]
    D -->|401| F[refresh → replay → fail = session expiry]
    D -->|403| G[emit permission-empty]
    D -->|429| H[emit Error, skip next poll]
    D -->|5xx| I[emit Error keep lastGood]
    E --> J[Timer 60s]
    J --> K{enrich available? E4}
    K -->|yes| L[GET /scheduler/runs?bulk]
    K -->|no| M[skip — unknown badges]
    L --> E
  • Single-flight + coalescing (10 §2); refresh({force}) for pull-to-refresh.
  • Background: timer paused, resume → immediate staleness check (09 B3).

4. Cron → queue flow (server, for the console's mental model)

flowchart LR
    subgraph Boot
        M[onModuleInit<br/>scheduler.service.ts:43-45] --> R[registerDefaults<br/>10 jobs :47-126]
        R -->|queue.add repeat pattern tz UTC| BQ[(BullMQ repeatables<br/>Redis)]
    end
    BQ -->|fires on schedule| Q[queue.add child job<br/>jobs/*.job.ts]
    Q --> W[Worker<br/>e.g. report.worker.ts]
    W -->|tenantContext.run| T[process<br/>tenant-scoped]
    T -->|fail| RT{attempts 3<br/>exp 5s :60-65}
    RT -->|retry| Q
    RT -->|exhausted| DLQ[DLQ<br/>attemptsMade>=attempts<br/>dlq.setup.ts:8-9]
    T -->|ok| DONE[done]

5. Manual trigger (S5)

TriggerBloc-free: JobDetailBloc.trigger() → confirm dialog (F2) → E6 (proposed) → on success refetch run history; button disabled while in-flight (10 §4). If E6 absent → button disabled with tooltip.

6. Cache & staleness

LayerScopeTTLWrites on
ServerBullMQ stateretention 1 h / 100 (triggers), 14 d failed (bullmq.module.ts:63-64)every run
Client volatileregistry list sl:scheduler:jobs60 ssuccessful E1 only
Client renderlast-good kept; stale banner past 60 s

7. Error mapping (00-shared/06 §5)

ErrorUI
401silent refresh → replay; fail → session expiry
403console hidden (superadmin gate, scheduler.controller.ts:24)
429skip next poll; snackbar once
404job removed concurrently → drop row
5xxkeep lastGood; snackbar ≥ 4 s; no skeleton flash