@happyvertical/smrt-jobs 0.30.0

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.
Files changed (92) hide show
  1. package/AGENTS.md +71 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +151 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/background-policy.d.ts +121 -0
  8. package/dist/background-policy.d.ts.map +1 -0
  9. package/dist/chunks/runner-DV8FBO0y.js +1642 -0
  10. package/dist/chunks/runner-DV8FBO0y.js.map +1 -0
  11. package/dist/chunks/worker-liveness-DOTjoIjr.js +65 -0
  12. package/dist/chunks/worker-liveness-DOTjoIjr.js.map +1 -0
  13. package/dist/error-redaction.d.ts +48 -0
  14. package/dist/error-redaction.d.ts.map +1 -0
  15. package/dist/index.d.ts +13 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +926 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/job-builder.d.ts +94 -0
  20. package/dist/job-builder.d.ts.map +1 -0
  21. package/dist/job-handle.d.ts +71 -0
  22. package/dist/job-handle.d.ts.map +1 -0
  23. package/dist/logger-extension.d.ts +58 -0
  24. package/dist/logger-extension.d.ts.map +1 -0
  25. package/dist/manifest.json +1327 -0
  26. package/dist/object-extension.d.ts +68 -0
  27. package/dist/object-extension.d.ts.map +1 -0
  28. package/dist/playground.d.ts +2 -0
  29. package/dist/playground.d.ts.map +1 -0
  30. package/dist/playground.js +179 -0
  31. package/dist/playground.js.map +1 -0
  32. package/dist/runner.d.ts +189 -0
  33. package/dist/runner.d.ts.map +1 -0
  34. package/dist/runner.js +15 -0
  35. package/dist/runner.js.map +1 -0
  36. package/dist/schedule-runner.d.ts +151 -0
  37. package/dist/schedule-runner.d.ts.map +1 -0
  38. package/dist/smrt-job-event.d.ts +54 -0
  39. package/dist/smrt-job-event.d.ts.map +1 -0
  40. package/dist/smrt-job.d.ts +215 -0
  41. package/dist/smrt-job.d.ts.map +1 -0
  42. package/dist/smrt-knowledge.json +508 -0
  43. package/dist/smrt-worker.d.ts +72 -0
  44. package/dist/smrt-worker.d.ts.map +1 -0
  45. package/dist/stale-recovery.d.ts +34 -0
  46. package/dist/stale-recovery.d.ts.map +1 -0
  47. package/dist/svelte/components/JobActions.svelte +103 -0
  48. package/dist/svelte/components/JobActions.svelte.d.ts +23 -0
  49. package/dist/svelte/components/JobActions.svelte.d.ts.map +1 -0
  50. package/dist/svelte/components/JobDashboard.svelte +199 -0
  51. package/dist/svelte/components/JobDashboard.svelte.d.ts +27 -0
  52. package/dist/svelte/components/JobDashboard.svelte.d.ts.map +1 -0
  53. package/dist/svelte/components/JobDetail.svelte +256 -0
  54. package/dist/svelte/components/JobDetail.svelte.d.ts +17 -0
  55. package/dist/svelte/components/JobDetail.svelte.d.ts.map +1 -0
  56. package/dist/svelte/components/JobList.svelte +360 -0
  57. package/dist/svelte/components/JobList.svelte.d.ts +28 -0
  58. package/dist/svelte/components/JobList.svelte.d.ts.map +1 -0
  59. package/dist/svelte/components/JobStats.svelte +242 -0
  60. package/dist/svelte/components/JobStats.svelte.d.ts +15 -0
  61. package/dist/svelte/components/JobStats.svelte.d.ts.map +1 -0
  62. package/dist/svelte/components/JobStatusBadge.svelte +23 -0
  63. package/dist/svelte/components/JobStatusBadge.svelte.d.ts +9 -0
  64. package/dist/svelte/components/JobStatusBadge.svelte.d.ts.map +1 -0
  65. package/dist/svelte/components/types.d.ts +9 -0
  66. package/dist/svelte/components/types.d.ts.map +1 -0
  67. package/dist/svelte/components/types.js +8 -0
  68. package/dist/svelte/i18n.d.ts +22 -0
  69. package/dist/svelte/i18n.d.ts.map +1 -0
  70. package/dist/svelte/i18n.js +22 -0
  71. package/dist/svelte/index.d.ts +25 -0
  72. package/dist/svelte/index.d.ts.map +1 -0
  73. package/dist/svelte/index.js +28 -0
  74. package/dist/svelte/playground.d.ts +329 -0
  75. package/dist/svelte/playground.d.ts.map +1 -0
  76. package/dist/svelte/playground.js +174 -0
  77. package/dist/svelte/types.d.ts +191 -0
  78. package/dist/svelte/types.d.ts.map +1 -0
  79. package/dist/svelte/types.js +87 -0
  80. package/dist/ui.d.ts +10 -0
  81. package/dist/ui.d.ts.map +1 -0
  82. package/dist/ui.js +69 -0
  83. package/dist/ui.js.map +1 -0
  84. package/dist/worker-liveness-thread.d.ts +2 -0
  85. package/dist/worker-liveness-thread.d.ts.map +1 -0
  86. package/dist/worker-liveness-thread.js +66 -0
  87. package/dist/worker-liveness-thread.js.map +1 -0
  88. package/dist/worker-liveness-ticker.d.ts +30 -0
  89. package/dist/worker-liveness-ticker.d.ts.map +1 -0
  90. package/dist/worker-liveness.d.ts +71 -0
  91. package/dist/worker-liveness.d.ts.map +1 -0
  92. package/package.json +93 -0
package/AGENTS.md ADDED
@@ -0,0 +1,71 @@
1
+ # @happyvertical/smrt-jobs
2
+
3
+ Background job execution with persistent queue, scheduling, and fluent builder API.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ SmrtObject.bg('method') → SmrtJob (in _smrt_jobs) → TaskRunner picks up → executes via ObjectRegistry
9
+ AgentSchedule (cron) → ScheduleRunner creates SmrtJob → TaskRunner executes → ScheduleRunner updates
10
+ TaskRunner.start() → SmrtWorker lease (in _smrt_workers) → recovery keys on worker liveness, not heartbeat
11
+ ```
12
+
13
+ ## SmrtJob
14
+
15
+ Persistent in `_smrt_jobs`. Fields: `queue` (default), `objectType`, `objectId`, `method`, `args`, `runAt`, `priority` (higher=sooner), `status`, `attempts`/`maxAttempts`, `timeout` (default 5min), `retryStrategy`, `workerId` (the owning runner's incarnation key), `workerHeartbeat` (telemetry only — no longer gates recovery).
16
+
17
+ Status: `pending → running → completed/failed/cancelled`.
18
+
19
+ ## TaskRunner
20
+
21
+ Polling-based execution engine. Config: `concurrency` (5), `pollInterval` (1s), `heartbeatInterval` (30s, telemetry only), `leaseTtlMs` (30s), `leaseTickMs` (10s), `shutdownTimeout` (30s).
22
+
23
+ 1. `start()` calls `assertReady()` (fail fast if `_smrt_workers` unmigrated), registers a seeded `SmrtWorker` lease, and adds its worker key to the process-global live set — all **before** polling
24
+ 2. Polls `claimReady()` to atomically claim pending jobs (`runAt <= NOW`, ordered by `priority DESC, runAt ASC, created_at ASC, id ASC`)
25
+ 3. Claim sets `status='running'`, `workerId=<incarnation key>`, heartbeat/start timestamps, and increments `attempts`
26
+ 4. Resolves class via `ObjectRegistry.getClass(objectType)`, creates instance, calls method
27
+ 5. **Internal args**: `_agentConfig` and `_scheduleId` stripped from args before calling method
28
+ 6. Terminal/retry writes are **conditional** (`WHERE worker_id=? AND status='running'`) so a recovered row is never stomped
29
+ 7. Retry: uses strategy from `@happyvertical/jobs`, schedules future `runAt` on failure
30
+ 8. Events: `job:started`, `job:completed`, `job:failed`, `job:retrying`, `runner:started/stopped`
31
+
32
+ ## Worker liveness & recovery (#1474)
33
+
34
+ Recovery keys on **worker-process liveness**, never per-job heartbeat freshness (a CPU-bound synchronous handler used to starve the heartbeat and false-recover its own running jobs).
35
+
36
+ - **`SmrtWorker` / `_smrt_workers`**: one lease row per runner *incarnation* (`workerId` is per-incarnation unique via `createWorkerKey`, so a restart never looks like it still owns the previous crash's jobs). `leaseExpiresAt` is a `datetime` (an integer epoch-ms column overflows `int4`/`INT32` on Postgres/DuckDB). Stage 1 writes/compares it against the host clock — the same approach the old heartbeat recovery used, so it's no more skew-sensitive than the code it replaced.
37
+ - **Process-global live set** (`worker-liveness.ts`, `globalThis.__smrtLiveWorkers`): checked synchronously, so it can't be starved by a blocked loop. Covers all same-process topologies.
38
+ - **Off-loop ticker** (`worker-liveness-ticker.ts` + `worker-liveness-thread.ts`): for engines a second connection can reach (Postgres, file-backed SQLite — `offLoopEligible()`), `start()` spawns a `node:worker_threads` ticker that renews the lease on its own thread, so a CPU-bound synchronous handler on the main loop can't starve it. In-memory SQLite / DuckDB, a thread-spawn failure, a start-handshake timeout, or the thread dying mid-run all fall back to main-loop renewal; the in-process live set keeps same-process correct regardless. The worker entry is a separate build entry resolved via `import.meta.resolve('@happyvertical/smrt-jobs/worker-liveness-thread')`.
39
+ - **Recovery rule** (both runners): a `running` job is orphaned iff its worker is *not alive* = not in the live set **and** no fresh `_smrt_workers` lease. The live set takes precedence over a stale lease. TaskRunner also never recovers a job in its own `activeJobs`. If `_smrt_workers` is absent, recovery skips lease checks (never mass-recovers). Recovery is swept at most once per lease tick, and terminal/recovery writes use `RETURNING id` (not `rowCount`, which DuckDB/JSON adapters always report as ≥1).
40
+ - **Lease clock**: the lease is compared against the host clock (same as the old heartbeat; fine with NTP + a 30s TTL). A dead process stops renewing and the lease expires within its TTL — that is how recovery detects death. (Instant cross-process detection via Postgres session advisory locks was prototyped on `@happyvertical/sql`'s `acquireSession()` but deferred — treating a free lock as proof-of-death false-recovers any worker legitimately in main-loop fallback mode.)
41
+
42
+ ## ScheduleRunner
43
+
44
+ Polls `_smrt_agent_schedules` every 60s for due entries. Creates SmrtJob with `queue='agents'`, `priority=75`. Wires to TaskRunner events for completion/failure tracking. Slot reconciliation keys on worker liveness (it has no in-process active-job set, so the lease/live-set is its whole mechanism).
45
+
46
+ Custom cron parser: 5-field (minute hour dom month dow). `*`, ranges, lists, steps supported. **Not timezone-aware** (UTC).
47
+
48
+ ## JobBuilder — Fluent API
49
+
50
+ ```typescript
51
+ const handle = await doc.background('analyze', { detailed: true })
52
+ .delay('5m').priority('high').retries(5).queue('analysis').timeout(600000).enqueue();
53
+
54
+ await handle.wait({ timeout: 60000, pollInterval: 100 }); // polling-based
55
+ ```
56
+
57
+ `bg()` is shorthand: `await doc.bg('analyze', args)` → enqueues immediately, returns JobHandle.
58
+
59
+ ## withBackgroundJobs(Class)
60
+
61
+ Mixin that adds `bg()` and `background()` to any SmrtObject. Uses WeakMap for collection caching per DB instance.
62
+
63
+ ## Gotchas
64
+
65
+ - **Cron not timezone-aware**: all times treated as UTC
66
+ - **No dead letter queue**: failed jobs stay in DB with `status='failed'` — manual intervention
67
+ - **Result storage**: `resultPointer` is just a string — app must implement result backend
68
+ - **Lazy builder**: `background()` returns builder — nothing happens until `enqueue()`
69
+ - **wait() is polling**: JobHandle.wait() polls DB every 100ms (configurable)
70
+ - **Migrate before start()**: `TaskRunner.start()` throws if `_smrt_workers` is missing — run `smrt db:migrate` after upgrading. Tables are never created at runtime.
71
+ - **Recovery is liveness-based**: don't reintroduce heartbeat-threshold recovery; a blocked event loop must not look dead (see Worker liveness section, #1474)
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright <2025> <Happy Vertical Corporation>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # @happyvertical/smrt-jobs
2
+
3
+ Background job execution for SMRT objects. Provides persistent queue storage, retry strategies, cron-based scheduling, and a fluent `JobBuilder` API via the `withBackgroundJobs()` mixin.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @happyvertical/smrt-jobs
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Add background capabilities to a SmrtObject
14
+
15
+ ```typescript
16
+ import { withBackgroundJobs, TaskRunner } from '@happyvertical/smrt-jobs';
17
+ import { Document } from './document.js';
18
+
19
+ // Mixin adds .bg() and .background() to any SmrtObject class
20
+ const BackgroundDocument = withBackgroundJobs(Document);
21
+ const doc = new BackgroundDocument({ db });
22
+ await doc.initialize();
23
+
24
+ // Quick enqueue — runs immediately when a TaskRunner picks it up
25
+ const handle = await doc.bg('generateSummary', { format: 'md' });
26
+
27
+ // Fluent builder for advanced options
28
+ const handle2 = await doc.background('generateSummary', { format: 'md' })
29
+ .delay('5m')
30
+ .priority('high')
31
+ .retries(5)
32
+ .queue('analysis')
33
+ .timeout(600000)
34
+ .enqueue();
35
+
36
+ // Wait for result (polling-based)
37
+ const result = await handle2.wait({ timeout: 60000, pollInterval: 100 });
38
+ ```
39
+
40
+ ### Run a TaskRunner to process jobs
41
+
42
+ ```typescript
43
+ import { TaskRunner } from '@happyvertical/smrt-jobs';
44
+
45
+ const runner = new TaskRunner({
46
+ concurrency: 5,
47
+ pollInterval: 1000,
48
+ queues: ['default', 'analysis'],
49
+ });
50
+ await runner.initialize(db);
51
+ await runner.start();
52
+
53
+ // Listen for events
54
+ runner.on('job:completed', (job, result) => { /* ... */ });
55
+ runner.on('job:failed', (job, error) => { /* ... */ });
56
+
57
+ // Graceful shutdown
58
+ process.on('SIGTERM', () => runner.stop());
59
+ ```
60
+
61
+ ### Heartbeat-safe job execution
62
+
63
+ `TaskRunner` keeps jobs alive with a heartbeat timer. If your job blocks the
64
+ Node.js event loop for longer than the effective stale-job threshold, the runner
65
+ will recover that work as stale and mark it failed.
66
+
67
+ Common causes:
68
+
69
+ - `execSync`, `spawnSync`, or other synchronous subprocess APIs
70
+ - long CPU-bound loops in the job method itself
71
+ - large synchronous filesystem work
72
+
73
+ Prefer async subprocess APIs (`spawn`, `execFile`, streamed stdio) for long
74
+ exports/builds/uploads, or move CPU-heavy work into a separate process or
75
+ worker thread. If a job is intentionally long-running, tune
76
+ `heartbeatInterval` and `staleJobThresholdMs` together so the stale threshold
77
+ comfortably exceeds the longest gap between heartbeats.
78
+
79
+ `ScheduleRunner` uses the same stale-heartbeat recovery rules when reconciling
80
+ scheduled jobs.
81
+
82
+ ### Schedule recurring jobs with ScheduleRunner
83
+
84
+ The `ScheduleRunner` polls the `_smrt_agent_schedules` table for due cron entries and creates `SmrtJob` records for the `TaskRunner` to execute. Wire them together via events:
85
+
86
+ ```typescript
87
+ import { ScheduleRunner } from '@happyvertical/smrt-jobs';
88
+
89
+ const scheduleRunner = new ScheduleRunner({ pollInterval: 30000 });
90
+ await scheduleRunner.initialize(db);
91
+ await scheduleRunner.start();
92
+
93
+ // Connect TaskRunner events to update schedule state
94
+ taskRunner.on('job:completed', (job) => {
95
+ const scheduleId = job.args?._scheduleId;
96
+ if (scheduleId) scheduleRunner.handleJobCompletion(scheduleId, true);
97
+ });
98
+ taskRunner.on('job:failed', (job, error) => {
99
+ const scheduleId = job.args?._scheduleId;
100
+ if (scheduleId) scheduleRunner.handleJobCompletion(scheduleId, false, error.message);
101
+ });
102
+ ```
103
+
104
+ ### System Tables
105
+
106
+ | Table | Purpose |
107
+ |-------|---------|
108
+ | `_smrt_jobs` | Persistent job queue (SmrtJob records) |
109
+ | `_smrt_agent_schedules` | Cron schedule entries polled by ScheduleRunner |
110
+
111
+ ## API
112
+
113
+ ### Classes
114
+
115
+ | Export | Description |
116
+ |--------|------------|
117
+ | `SmrtJob` | Persistent job record stored in `_smrt_jobs` |
118
+ | `SmrtJobCollection` | Collection with `claimReady()`, `listReady()`, `listByStatus()`, `stats()`, `cleanup()` |
119
+ | `JobBuilder` | Fluent API: `.delay()`, `.priority()`, `.retries()`, `.queue()`, `.timeout()`, `.enqueue()` |
120
+ | `JobHandle` | Track, wait, cancel, or retry an enqueued job |
121
+ | `JobContextLogger` | Logger that auto-injects job context (jobId, attempt, queue) |
122
+ | `TaskRunner` | Polling-based execution engine with concurrency control and heartbeats |
123
+ | `ScheduleRunner` | Polls for due cron schedules and creates SmrtJob entries |
124
+
125
+ `TaskRunner` uses `SmrtJobCollection.claimReady()` so multiple workers can poll
126
+ the same queue without duplicate-claiming a pending row.
127
+
128
+ ### Functions
129
+
130
+ | Export | Description |
131
+ |--------|------------|
132
+ | `createTaskRunner(config?)` | Factory for creating a configured TaskRunner |
133
+ | `createScheduleRunner(config?)` | Factory for creating a configured ScheduleRunner |
134
+ | `withBackgroundJobs(Class)` | Mixin that adds `.bg()` and `.background()` to any SmrtObject class |
135
+ | `parseDelay(delay)` | Parse human-readable delay strings (`'5m'`, `'1h'`, `'30s'`) to milliseconds |
136
+ | `priorityToNumber(priority)` | Convert priority label (`'critical'`/`'high'`/`'normal'`/`'low'`) to number |
137
+
138
+ ### Key Types
139
+
140
+ `Priority`, `JobStatus`, `JobResult`, `WaitOptions`, `BgOptions`, `BackgroundCapable`, `TaskRunnerConfig`, `TaskRunnerEvents`, `ScheduleRunnerConfig`, `ScheduleRunnerEvents`, `ScheduleInfo`, `JobContext`, `TimeoutBehavior`, `SmrtJobData`, `ListReadyOptions`
141
+
142
+ ## Dependencies
143
+
144
+ - `@happyvertical/smrt-core` -- ORM and code generation
145
+ - `@happyvertical/smrt-config` -- configuration loading
146
+ - `@happyvertical/smrt-types` -- shared type definitions
147
+ - `@happyvertical/jobs` -- retry strategies
148
+ - `@happyvertical/sql` -- database interface
149
+ - `@happyvertical/logger` -- structured logging
150
+ - `@happyvertical/utils` -- ID generation utilities
151
+ - Peer (optional): `@happyvertical/smrt-svelte`, `svelte`
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=__smrt-register__.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../src/__smrt-register__.ts"],"names":[],"mappings":""}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Opt-in policy controls for background job creation and dispatch.
3
+ *
4
+ * @remarks
5
+ * These guards harden the background-jobs surface flagged by the S5 audit
6
+ * (#1402):
7
+ *
8
+ * - {@link MAX_JOB_RETRIES} caps the retry count a caller can request so a
9
+ * misconfigured `.retries(n)` cannot pin a worker on a poison job forever.
10
+ * - {@link assertWithinTenantCreationCap} bounds how many jobs a single tenant
11
+ * may hold in the queue at once, so one tenant cannot exhaust the shared
12
+ * worker pool (a cross-tenant denial of service).
13
+ * - {@link isBackgroundEligibleMethod} / {@link backgroundEligible} provide an
14
+ * opt-in allowlist of methods that may be invoked by the runner. The runner's
15
+ * dispatch is already bounded to existing prototype methods (no eval / dynamic
16
+ * import), but a class can further restrict which of its methods are reachable
17
+ * from a persisted job row. This same marker is intended to be consumed by the
18
+ * agents package, which dispatches methods through an equivalent path.
19
+ */
20
+ /**
21
+ * Hard ceiling on retry attempts a caller may request via `.retries(n)` /
22
+ * `bg(..., { retries })`. Requests above this are clamped (not rejected) so
23
+ * existing callers keep working while the worst case stays bounded.
24
+ */
25
+ export declare const MAX_JOB_RETRIES = 25;
26
+ /**
27
+ * Default maximum number of non-terminal (pending/running) jobs a single
28
+ * tenant may hold in the queue at once. Configurable per call; `0` / negative
29
+ * disables the cap.
30
+ */
31
+ export declare const DEFAULT_TENANT_JOB_CAP = 10000;
32
+ /**
33
+ * Clamp a requested retry count to {@link MAX_JOB_RETRIES}.
34
+ *
35
+ * @param requested - The retry count supplied by the caller.
36
+ * @returns A non-negative integer no greater than {@link MAX_JOB_RETRIES}.
37
+ */
38
+ export declare function clampRetries(requested: number): number;
39
+ /**
40
+ * Error thrown when a tenant exceeds its allowed in-flight job count.
41
+ */
42
+ export declare class TenantJobCapExceededError extends Error {
43
+ readonly tenantId: string;
44
+ readonly cap: number;
45
+ readonly current: number;
46
+ constructor(tenantId: string, cap: number, current: number);
47
+ }
48
+ /**
49
+ * Throw {@link TenantJobCapExceededError} when a tenant is at or above its cap.
50
+ *
51
+ * @param tenantId - Tenant the new job would belong to (`null` = global; not
52
+ * subject to the per-tenant cap).
53
+ * @param current - Current count of non-terminal jobs for the tenant.
54
+ * @param cap - Maximum allowed; `<= 0` disables the check.
55
+ */
56
+ export declare function assertWithinTenantCreationCap(tenantId: string | null | undefined, current: number, cap: number): void;
57
+ /**
58
+ * Class shape that opts into a background-method allowlist by declaring a
59
+ * static set/array of method names.
60
+ */
61
+ export interface BackgroundEligibleClass {
62
+ /**
63
+ * Method names that may be invoked by the job/agent runner. When present
64
+ * (even if empty), it is treated as an exhaustive allowlist. When absent,
65
+ * the runner falls back to its default behaviour (any existing method).
66
+ */
67
+ backgroundEligibleMethods?: ReadonlyArray<string> | ReadonlySet<string>;
68
+ }
69
+ /**
70
+ * Add method names to a class's background-eligible allowlist.
71
+ *
72
+ * Installs/extends a static `backgroundEligibleMethods` set on the constructor.
73
+ * Once any method is marked, the runner refuses to dispatch a job whose
74
+ * `method` is not in the set — turning the dispatch surface from "any prototype
75
+ * method" into an explicit contract. Use this when applying the
76
+ * {@link backgroundEligible} decorator is inconvenient (or in non-decorator
77
+ * code, including the agents package).
78
+ *
79
+ * @param ctor - The class constructor to annotate.
80
+ * @param methods - Method names to allow.
81
+ */
82
+ export declare function markBackgroundEligible(ctor: object, ...methods: string[]): void;
83
+ /**
84
+ * Decorator: mark a method as background-eligible.
85
+ *
86
+ * This is a legacy (`experimentalDecorators`) method decorator — the mode the
87
+ * SMRT monorepo compiles with. Applying it (one or more times) builds up the
88
+ * static `backgroundEligibleMethods` allowlist on the owning class. Once any
89
+ * method is marked, the runner will refuse to dispatch a job whose `method` is
90
+ * not in the set.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * class Report extends SmrtObject {
95
+ * \@backgroundEligible()
96
+ * async regenerate() {} // reachable from a job
97
+ *
98
+ * async deleteEverything() {} // NOT reachable from a job
99
+ * }
100
+ * ```
101
+ */
102
+ export declare function backgroundEligible(): (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => PropertyDescriptor | undefined;
103
+ /**
104
+ * Resolve the declared allowlist for a class, if any.
105
+ *
106
+ * @param ctor - The target object's constructor.
107
+ * @returns A `Set` of allowed method names, or `null` when the class did not
108
+ * opt in (runner should fall back to default behaviour).
109
+ */
110
+ export declare function getBackgroundEligibleMethods(ctor: unknown): ReadonlySet<string> | null;
111
+ /**
112
+ * Whether a method may be invoked by the runner for a given target class.
113
+ *
114
+ * @param ctor - Constructor of the resolved target class.
115
+ * @param method - Method name from the persisted job row.
116
+ * @returns `true` when the class declared no allowlist (default) or when the
117
+ * method is on the allowlist; `false` when an allowlist exists and excludes
118
+ * the method.
119
+ */
120
+ export declare function isBackgroundEligibleMethod(ctor: unknown, method: string): boolean;
121
+ //# sourceMappingURL=background-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background-policy.d.ts","sourceRoot":"","sources":["../src/background-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQtD;AAED;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;aAEhC,QAAQ,EAAE,MAAM;aAChB,GAAG,EAAE,MAAM;aACX,OAAO,EAAE,MAAM;gBAFf,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM;CAQlC;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,IAAI,CAKN;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;CACzE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,GAAG,OAAO,EAAE,MAAM,EAAE,GACnB,IAAI,CASN;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,KAE9B,QAAQ,MAAM,EACd,aAAa,MAAM,GAAG,MAAM,EAC5B,aAAa,kBAAkB,KAC9B,kBAAkB,GAAG,SAAS,CAOlC;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,OAAO,GACZ,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,CAK5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAIT"}