@happyvertical/smrt-jobs 0.55.4 → 0.55.7

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.
@@ -1,14 +1,16 @@
1
1
  import { a as resolveEngine, c as unregisterLiveWorker, i as registerLiveWorker, n as isWorkerAlive, o as resolveUrl, r as offLoopEligible, s as tuneSqliteForConcurrency, t as createWorkerKey } from "./worker-liveness-C1Gjnhax.js";
2
2
  import { ObjectRegistry, SmrtCollection, SmrtObject, detectEngine, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, field, foreignKey, getClassConfigResolvers, normalizeCustomActionFailure, registerRetentionTask, resolveLazyConfig, runRetentionSweep, smrt, unregisterRetentionTask } from "@happyvertical/smrt-core";
3
3
  import { createLogger } from "@happyvertical/logger";
4
- import { TenantContext, TenantScoped, getTenantId, tenantId } from "@happyvertical/smrt-tenancy";
4
+ import { TenantContext, TenantIsolationError, TenantScoped, getTenantId, isSuperAdminBypass, isSystemContext, tenantId } from "@happyvertical/smrt-tenancy";
5
+ import { randomUUID } from "node:crypto";
6
+ import { ensureSchema } from "@happyvertical/smrt-core/schema/utils";
5
7
  import { fromConfig } from "@happyvertical/jobs";
6
8
  import { createId } from "@happyvertical/utils";
7
9
  import { AsyncLocalStorage } from "node:async_hooks";
8
10
  import { EventEmitter } from "node:events";
9
11
  import { Worker } from "node:worker_threads";
10
12
  //#region src/__smrt-register__.ts
11
- ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-jobs","packageVersion":"0.55.4","objects":{"@happyvertical/smrt-jobs:ForgeDelivery":{"name":"forgedelivery","className":"ForgeDelivery","qualifiedName":"@happyvertical/smrt-jobs:ForgeDelivery","collection":"forgedeliveries","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/forge-projection.ts","packageName":"@happyvertical/smrt-jobs","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"provider":{"type":"text","required":true,"_meta":{"required":true}},"deliveryId":{"type":"text","required":true,"_meta":{"required":true}},"installationKey":{"type":"text","required":false,"_meta":{"nullable":true}},"repositoryKey":{"type":"text","required":false,"_meta":{"nullable":true}},"eventName":{"type":"text","required":true,"_meta":{"required":true}},"payload":{"type":"json","required":false,"_meta":{}},"status":{"type":"text","required":true,"default":"pending","_meta":{"required":true,"default":"pending"},"enum":["pending","leased","retry","completed","dead_letter"]},"attempts":{"type":"integer","required":true,"default":0,"_meta":{"required":true,"default":0}},"maxAttempts":{"type":"integer","required":true,"default":5,"_meta":{"required":true,"default":5}},"receivedAt":{"type":"datetime","required":true,"_meta":{"required":true}},"nextAttemptAt":{"type":"datetime","required":true,"_meta":{"required":true}},"leaseOwner":{"type":"text","required":false,"_meta":{"nullable":true}},"leaseToken":{"type":"text","required":false,"_meta":{"nullable":true}},"leaseExpiresAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"lastError":{"type":"text","required":false,"_meta":{"nullable":true}},"completedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"replayCount":{"type":"integer","required":true,"default":0,"_meta":{"required":true,"default":0}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_forge_deliveries","conflictColumns":["tenant_id","provider","delivery_id"],"api":false,"cli":false,"mcp":false,"indexes":[{"name":"_smrt_forge_deliveries_status_next_attempt_at_idx","columns":["status","nextAttemptAt"]},{"name":"_smrt_forge_deliveries_status_lease_expires_at_idx","columns":["status","leaseExpiresAt"]}],"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"ForgeDelivery","collectionExportName":"ForgeDeliveryCollection","validationRules":[{"field":"provider","rule":"required","fieldType":"text"},{"field":"deliveryId","rule":"required","fieldType":"text"},{"field":"eventName","rule":"required","fieldType":"text"},{"field":"status","rule":"required","fieldType":"text"},{"field":"attempts","rule":"required","fieldType":"integer"},{"field":"maxAttempts","rule":"required","fieldType":"integer"},{"field":"receivedAt","rule":"required","fieldType":"datetime"},{"field":"nextAttemptAt","rule":"required","fieldType":"datetime"},{"field":"replayCount","rule":"required","fieldType":"integer"}],"schema":{"tableName":"_smrt_forge_deliveries","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_forge_deliveries\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"provider\\" TEXT NOT NULL,\\n \\"delivery_id\\" TEXT NOT NULL,\\n \\"installation_key\\" TEXT,\\n \\"repository_key\\" TEXT,\\n \\"event_name\\" TEXT NOT NULL,\\n \\"payload\\" JSON,\\n \\"status\\" TEXT NOT NULL DEFAULT 'pending',\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"max_attempts\\" INTEGER NOT NULL DEFAULT 5,\\n \\"received_at\\" TIMESTAMP NOT NULL,\\n \\"next_attempt_at\\" TIMESTAMP NOT NULL,\\n \\"lease_owner\\" TEXT,\\n \\"lease_token\\" TEXT,\\n \\"lease_expires_at\\" TIMESTAMP,\\n \\"last_error\\" TEXT,\\n \\"completed_at\\" TIMESTAMP,\\n \\"replay_count\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"provider":{"type":"TEXT","notNull":true,"unique":false},"delivery_id":{"type":"TEXT","notNull":true,"unique":false},"installation_key":{"type":"TEXT","notNull":false,"unique":false},"repository_key":{"type":"TEXT","notNull":false,"unique":false},"event_name":{"type":"TEXT","notNull":true,"unique":false},"payload":{"type":"JSON","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"pending"},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"max_attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":5},"received_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"next_attempt_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"lease_owner":{"type":"TEXT","notNull":false,"unique":false},"lease_token":{"type":"TEXT","notNull":false,"unique":false},"lease_expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_error":{"type":"TEXT","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"replay_count":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"_smrt_forge_deliveries_tenant_id_provider_idx","columns":["tenant_id","provider","delivery_id"],"unique":true},{"name":"_smrt_forge_deliveries_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_forge_deliveries_status_next_attempt_at_idx","columns":["status","next_attempt_at"]},{"name":"_smrt_forge_deliveries_status_lease_expires_at_idx","columns":["status","lease_expires_at"]},{"name":"_smrt_forge_deliveries_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"38d3f476"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:ForgeProjectionCheckpoint":{"name":"forgeprojectioncheckpoint","className":"ForgeProjectionCheckpoint","qualifiedName":"@happyvertical/smrt-jobs:ForgeProjectionCheckpoint","collection":"forgeprojectioncheckpoints","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/forge-projection.ts","packageName":"@happyvertical/smrt-jobs","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"projection":{"type":"text","required":true,"_meta":{"required":true}},"subjectKey":{"type":"text","required":true,"_meta":{"required":true}},"observationVersion":{"type":"integer","required":true,"_meta":{"required":true}},"deliveryId":{"type":"text","required":true,"_meta":{"required":true}},"observedAt":{"type":"datetime","required":true,"_meta":{"required":true}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_forge_projection_checkpoints","conflictColumns":["tenant_id","projection","subject_key"],"api":false,"cli":false,"mcp":false,"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"ForgeProjectionCheckpoint","collectionExportName":"ForgeProjectionCheckpointCollection","validationRules":[{"field":"projection","rule":"required","fieldType":"text"},{"field":"subjectKey","rule":"required","fieldType":"text"},{"field":"observationVersion","rule":"required","fieldType":"integer"},{"field":"deliveryId","rule":"required","fieldType":"text"},{"field":"observedAt","rule":"required","fieldType":"datetime"}],"schema":{"tableName":"_smrt_forge_projection_checkpoints","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_forge_projection_checkpoints\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"projection\\" TEXT NOT NULL,\\n \\"subject_key\\" TEXT NOT NULL,\\n \\"observation_version\\" INTEGER NOT NULL,\\n \\"delivery_id\\" TEXT NOT NULL,\\n \\"observed_at\\" TIMESTAMP NOT NULL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"projection":{"type":"TEXT","notNull":true,"unique":false},"subject_key":{"type":"TEXT","notNull":true,"unique":false},"observation_version":{"type":"INTEGER","notNull":true,"unique":false},"delivery_id":{"type":"TEXT","notNull":true,"unique":false},"observed_at":{"type":"TIMESTAMP","notNull":true,"unique":false}},"indexes":[{"name":"_smrt_forge_projection_checkpoints_tenant_id_projection_idx","columns":["tenant_id","projection","subject_key"],"unique":true},{"name":"_smrt_forge_projection_checkpoints_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_forge_projection_checkpoints_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"39c12493"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:ForgeDeliveryCollection":{"name":"forgedeliverycollection","className":"ForgeDeliveryCollection","qualifiedName":"@happyvertical/smrt-jobs:ForgeDeliveryCollection","collection":"forgedeliveries","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/forge-projection.ts","packageName":"@happyvertical/smrt-jobs","fields":{},"methods":{"accept":{"name":"accept","async":true,"parameters":[{"name":"input","type":"AcceptForgeDeliveryInput","optional":false},{"name":"audit","type":"ForgeProjectionAuditHook","optional":true}],"returnType":"Promise<AcceptedForgeDelivery>","isStatic":false,"isPublic":true},"claimReady":{"name":"claimReady","async":true,"parameters":[{"name":"options","type":"ClaimForgeDeliveryOptions","optional":false},{"name":"audit","type":"ForgeProjectionAuditHook","optional":true}],"returnType":"Promise<ForgeDelivery | null>","isStatic":false,"isPublic":true},"replay":{"name":"replay","async":true,"parameters":[{"name":"id","type":"string","optional":false},{"name":"audit","type":"ForgeProjectionAuditHook","optional":true}],"returnType":"Promise<boolean>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"cli":false,"mcp":false,"tableName":"_smrt_forge_deliveries"},"extends":"SmrtCollection","extendsTypeArg":"ForgeDelivery","exportName":"ForgeDeliveryCollection","collectionExportName":"ForgeDeliveryCollectionCollection","schema":{"tableName":"_smrt_forge_deliveries","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_forge_deliveries\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"provider\\" TEXT NOT NULL,\\n \\"delivery_id\\" TEXT NOT NULL,\\n \\"installation_key\\" TEXT,\\n \\"repository_key\\" TEXT,\\n \\"event_name\\" TEXT NOT NULL,\\n \\"payload\\" JSON,\\n \\"status\\" TEXT NOT NULL DEFAULT 'pending',\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"max_attempts\\" INTEGER NOT NULL DEFAULT 5,\\n \\"received_at\\" TIMESTAMP NOT NULL,\\n \\"next_attempt_at\\" TIMESTAMP NOT NULL,\\n \\"lease_owner\\" TEXT,\\n \\"lease_token\\" TEXT,\\n \\"lease_expires_at\\" TIMESTAMP,\\n \\"last_error\\" TEXT,\\n \\"completed_at\\" TIMESTAMP,\\n \\"replay_count\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"provider":{"type":"TEXT","notNull":true,"unique":false},"delivery_id":{"type":"TEXT","notNull":true,"unique":false},"installation_key":{"type":"TEXT","notNull":false,"unique":false},"repository_key":{"type":"TEXT","notNull":false,"unique":false},"event_name":{"type":"TEXT","notNull":true,"unique":false},"payload":{"type":"JSON","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"pending"},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"max_attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":5},"received_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"next_attempt_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"lease_owner":{"type":"TEXT","notNull":false,"unique":false},"lease_token":{"type":"TEXT","notNull":false,"unique":false},"lease_expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_error":{"type":"TEXT","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"replay_count":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"_smrt_forge_deliveries_tenant_id_provider_idx","columns":["tenant_id","provider","delivery_id"],"unique":true},{"name":"_smrt_forge_deliveries_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_forge_deliveries_status_next_attempt_at_idx","columns":["status","next_attempt_at"]},{"name":"_smrt_forge_deliveries_status_lease_expires_at_idx","columns":["status","lease_expires_at"]},{"name":"_smrt_forge_deliveries_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"38d3f476"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtJobEvent":{"name":"smrtjobevent","className":"SmrtJobEvent","qualifiedName":"@happyvertical/smrt-jobs:SmrtJobEvent","collection":"smrtjobevents","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-job-event.ts","packageName":"@happyvertical/smrt-jobs","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"jobId":{"type":"foreignKey","required":true,"related":"SmrtJob","_meta":{"required":true,"constraint":false}},"type":{"type":"text","required":true,"default":"log","_meta":{"required":true,"default":"log"}},"level":{"type":"text","required":true,"default":"info","_meta":{"required":true,"default":"info"},"enum":["debug","info","warn","error"]},"stage":{"type":"text","required":false,"_meta":{"nullable":true}},"progress":{"type":"integer","required":false,"_meta":{"nullable":true}},"message":{"type":"text","required":true,"default":"","_meta":{"required":true,"default":""}},"data":{"type":"json","required":false,"_meta":{}},"createdAt":{"type":"datetime","required":true,"_meta":{"required":true}}},"methods":{"toCursor":{"name":"toCursor","async":false,"parameters":[],"returnType":"string","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_job_events","api":false,"cli":{"include":["list","get"],"http":false,"skipApiCheck":true},"mcp":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"SmrtJobEvent","collectionExportName":"SmrtJobEventCollection","validationRules":[{"field":"jobId","rule":"required","fieldType":"foreignKey"},{"field":"type","rule":"required","fieldType":"text"},{"field":"level","rule":"required","fieldType":"text"},{"field":"message","rule":"required","fieldType":"text"},{"field":"createdAt","rule":"required","fieldType":"datetime"}],"schema":{"tableName":"_smrt_job_events","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_job_events\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"job_id\\" UUID NOT NULL,\\n \\"type\\" TEXT NOT NULL DEFAULT 'log',\\n \\"level\\" TEXT NOT NULL DEFAULT 'info',\\n \\"stage\\" TEXT,\\n \\"progress\\" INTEGER,\\n \\"message\\" TEXT NOT NULL DEFAULT '',\\n \\"data\\" JSON\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"job_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false},"type":{"type":"TEXT","notNull":true,"unique":false,"default":"log"},"level":{"type":"TEXT","notNull":true,"unique":false,"default":"info"},"stage":{"type":"TEXT","notNull":false,"unique":false},"progress":{"type":"INTEGER","notNull":false,"unique":false},"message":{"type":"TEXT","notNull":true,"unique":false,"default":""},"data":{"type":"JSON","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_job_events_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_job_events_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"_smrt_job_events_job_id_idx","columns":["job_id"]}],"version":"8a9faeec"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtJobEventCollection":{"name":"smrtjobeventcollection","className":"SmrtJobEventCollection","qualifiedName":"@happyvertical/smrt-jobs:SmrtJobEventCollection","collection":"smrtjobevents","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-job-event.ts","packageName":"@happyvertical/smrt-jobs","fields":{},"methods":{"initialize":{"name":"initialize","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"append":{"name":"append","async":true,"parameters":[{"name":"input","type":"SmrtJobEventData","optional":false}],"returnType":"Promise<SmrtJobEvent>","isStatic":false,"isPublic":true},"listByJob":{"name":"listByJob","async":true,"parameters":[{"name":"jobId","type":"string","optional":false},{"name":"options","type":"ListJobEventsOptions","optional":true}],"returnType":"Promise<SmrtJobEvent[]>","isStatic":false,"isPublic":true},"listSinceCursor":{"name":"listSinceCursor","async":true,"parameters":[{"name":"options","type":"any","optional":true,"typeUnresolved":true}],"returnType":"Promise<SmrtJobEvent[]>","isStatic":false,"isPublic":true},"latestProgressByJobIds":{"name":"latestProgressByJobIds","async":true,"parameters":[{"name":"jobIds","type":"string[]","optional":false},{"name":"options","type":"object","optional":true,"memberTypes":["string | null"]}],"returnType":"Promise<Map<string, SmrtJobEvent>>","isStatic":false,"isPublic":true},"cleanup":{"name":"cleanup","async":true,"parameters":[{"name":"options","type":"object","optional":false,"memberTypes":["Date","boolean"]}],"returnType":"Promise<number>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_job_events","api":false,"mcp":false},"extends":"SmrtCollection","extendsTypeArg":"SmrtJobEvent","exportName":"SmrtJobEventCollection","collectionExportName":"SmrtJobEventCollectionCollection","schema":{"tableName":"_smrt_job_events","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_job_events\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"job_id\\" UUID NOT NULL,\\n \\"type\\" TEXT NOT NULL DEFAULT 'log',\\n \\"level\\" TEXT NOT NULL DEFAULT 'info',\\n \\"stage\\" TEXT,\\n \\"progress\\" INTEGER,\\n \\"message\\" TEXT NOT NULL DEFAULT '',\\n \\"data\\" JSON\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"job_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false},"type":{"type":"TEXT","notNull":true,"unique":false,"default":"log"},"level":{"type":"TEXT","notNull":true,"unique":false,"default":"info"},"stage":{"type":"TEXT","notNull":false,"unique":false},"progress":{"type":"INTEGER","notNull":false,"unique":false},"message":{"type":"TEXT","notNull":true,"unique":false,"default":""},"data":{"type":"JSON","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_job_events_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_job_events_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"_smrt_job_events_job_id_idx","columns":["job_id"]}],"version":"8a9faeec"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtJob":{"name":"smrtjob","className":"SmrtJob","qualifiedName":"@happyvertical/smrt-jobs:SmrtJob","collection":"smrtjobs","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-job.ts","packageName":"@happyvertical/smrt-jobs","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"queue":{"type":"text","required":true,"default":"default","_meta":{"required":true,"default":"default"}},"objectType":{"type":"text","required":true,"_meta":{"required":true}},"objectId":{"type":"text","required":false,"_meta":{"nullable":true}},"method":{"type":"text","required":true,"_meta":{"required":true}},"args":{"type":"json","required":false,"_meta":{}},"runAt":{"type":"datetime","required":true,"_meta":{"required":true,"indexed":true}},"priority":{"type":"integer","required":true,"default":50,"_meta":{"required":true,"default":50}},"status":{"type":"text","required":true,"default":"pending","_meta":{"required":true,"default":"pending","indexed":true},"enum":["pending","ready","running","completed","failed","cancelled"]},"attempts":{"type":"integer","required":true,"default":0,"_meta":{"required":true,"default":0}},"maxAttempts":{"type":"integer","required":true,"default":3,"_meta":{"required":true,"default":3}},"timeout":{"type":"integer","required":true,"default":300000,"_meta":{"required":true,"default":300000}},"timeoutBehavior":{"type":"text","required":true,"default":"fail","_meta":{"required":true,"default":"fail"},"enum":["fail","kill","warn"]},"startedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"completedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"lastError":{"type":"text","required":false,"_meta":{"nullable":true}},"resultPointer":{"type":"text","required":false,"_meta":{"nullable":true}},"taskId":{"type":"text","required":false,"_meta":{"nullable":true,"unique":true}},"taskOwnerId":{"type":"text","required":false,"_meta":{"nullable":true}},"taskResult":{"type":"json","required":false,"_meta":{"nullable":true}},"taskInputRequests":{"type":"json","required":false,"_meta":{"nullable":true}},"taskInputResponses":{"type":"json","required":false,"_meta":{"nullable":true}},"retryStrategy":{"type":"json","required":false,"_meta":{}},"workerId":{"type":"text","required":false,"_meta":{"nullable":true}},"workerHeartbeat":{"type":"datetime","required":false,"_meta":{"nullable":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"retry":{"name":"retry","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"cancel":{"name":"cancel","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"getDescription":{"name":"getDescription","async":false,"parameters":[],"returnType":"string","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_jobs","api":false,"cli":{"include":["list","get","retry","cancel"],"skipApiCheck":true,"http":false},"mcp":false,"indexes":[{"name":"_smrt_jobs_status_run_at_idx","columns":["status","runAt"]},{"name":"_smrt_jobs_ready_idx","columns":["status","runAt"],"where":"status = 'pending' AND (task_input_requests IS NULL OR task_input_responses IS NOT NULL)"}],"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"SmrtJob","collectionExportName":"SmrtJobCollection","validationRules":[{"field":"queue","rule":"required","fieldType":"text"},{"field":"objectType","rule":"required","fieldType":"text"},{"field":"method","rule":"required","fieldType":"text"},{"field":"runAt","rule":"required","fieldType":"datetime"},{"field":"priority","rule":"required","fieldType":"integer"},{"field":"status","rule":"required","fieldType":"text"},{"field":"attempts","rule":"required","fieldType":"integer"},{"field":"maxAttempts","rule":"required","fieldType":"integer"},{"field":"timeout","rule":"required","fieldType":"integer"},{"field":"timeoutBehavior","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_jobs","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_jobs\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"queue\\" TEXT NOT NULL DEFAULT 'default',\\n \\"object_type\\" TEXT NOT NULL,\\n \\"object_id\\" TEXT,\\n \\"method\\" TEXT NOT NULL,\\n \\"args\\" JSON,\\n \\"run_at\\" TIMESTAMP NOT NULL,\\n \\"priority\\" INTEGER NOT NULL DEFAULT 50,\\n \\"status\\" TEXT NOT NULL DEFAULT 'pending',\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"max_attempts\\" INTEGER NOT NULL DEFAULT 3,\\n \\"timeout\\" INTEGER NOT NULL DEFAULT 300000,\\n \\"timeout_behavior\\" TEXT NOT NULL DEFAULT 'fail',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"last_error\\" TEXT,\\n \\"result_pointer\\" TEXT,\\n \\"task_id\\" TEXT UNIQUE,\\n \\"task_owner_id\\" TEXT,\\n \\"task_result\\" JSON,\\n \\"task_input_requests\\" JSON,\\n \\"task_input_responses\\" JSON,\\n \\"retry_strategy\\" JSON,\\n \\"worker_id\\" TEXT,\\n \\"worker_heartbeat\\" TIMESTAMP\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"queue":{"type":"TEXT","notNull":true,"unique":false,"default":"default"},"object_type":{"type":"TEXT","notNull":true,"unique":false},"object_id":{"type":"TEXT","notNull":false,"unique":false},"method":{"type":"TEXT","notNull":true,"unique":false},"args":{"type":"JSON","notNull":false,"unique":false},"run_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"priority":{"type":"INTEGER","notNull":true,"unique":false,"default":50},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"pending"},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"max_attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":3},"timeout":{"type":"INTEGER","notNull":true,"unique":false,"default":300000},"timeout_behavior":{"type":"TEXT","notNull":true,"unique":false,"default":"fail"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_error":{"type":"TEXT","notNull":false,"unique":false},"result_pointer":{"type":"TEXT","notNull":false,"unique":false},"task_id":{"type":"TEXT","notNull":false,"unique":true},"task_owner_id":{"type":"TEXT","notNull":false,"unique":false},"task_result":{"type":"JSON","notNull":false,"unique":false},"task_input_requests":{"type":"JSON","notNull":false,"unique":false},"task_input_responses":{"type":"JSON","notNull":false,"unique":false},"retry_strategy":{"type":"JSON","notNull":false,"unique":false},"worker_id":{"type":"TEXT","notNull":false,"unique":false},"worker_heartbeat":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_jobs_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_jobs_run_at_idx","columns":["run_at"]},{"name":"_smrt_jobs_status_idx","columns":["status"]},{"name":"_smrt_jobs_status_run_at_idx","columns":["status","run_at"]},{"name":"_smrt_jobs_ready_idx","columns":["status","run_at"],"where":"status = 'pending' AND (task_input_requests IS NULL OR task_input_responses IS NOT NULL)"},{"name":"_smrt_jobs_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"43e1324f"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtJobCollection":{"name":"smrtjobcollection","className":"SmrtJobCollection","qualifiedName":"@happyvertical/smrt-jobs:SmrtJobCollection","collection":"smrtjobs","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-job.ts","packageName":"@happyvertical/smrt-jobs","fields":{},"methods":{"initialize":{"name":"initialize","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"listByStatus":{"name":"listByStatus","async":true,"parameters":[{"name":"status","type":"JobStatus | JobStatus[]","optional":false,"unionBranches":[{"type":"JobStatus"},{"type":"JobStatus[]"}]},{"name":"options","type":"object","optional":true,"memberTypes":["number","string"]}],"returnType":"Promise<SmrtJob[]>","isStatic":false,"isPublic":true},"listReady":{"name":"listReady","async":true,"parameters":[{"name":"options","type":"object","optional":true,"memberTypes":["number","string[]"]}],"returnType":"Promise<SmrtJob[]>","isStatic":false,"isPublic":true},"claimReady":{"name":"claimReady","async":true,"parameters":[{"name":"options","type":"ClaimReadyOptions","optional":false}],"returnType":"Promise<SmrtJob[]>","isStatic":false,"isPublic":true},"countInFlightForTenant":{"name":"countInFlightForTenant","async":true,"parameters":[{"name":"tenantId","type":"string | null","optional":false,"unionBranches":[{"type":"string"},{"type":"null"}]}],"returnType":"Promise<number>","isStatic":false,"isPublic":true},"enqueueJob":{"name":"enqueueJob","async":true,"parameters":[{"name":"data","type":"SmrtJobData","optional":false},{"name":"options","type":"EnqueueJobOptions","optional":true}],"returnType":"Promise<SmrtJob>","isStatic":false,"isPublic":true},"stats":{"name":"stats","async":true,"parameters":[{"name":"queue","type":"string","optional":true}],"returnType":"Promise<object>","isStatic":false,"isPublic":true},"cleanup":{"name":"cleanup","async":true,"parameters":[{"name":"options","type":"object","optional":false,"memberTypes":["Date","number","boolean"]}],"returnType":"Promise<number>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_jobs","api":false,"mcp":false},"extends":"SmrtCollection","extendsTypeArg":"SmrtJob","exportName":"SmrtJobCollection","collectionExportName":"SmrtJobCollectionCollection","schema":{"tableName":"_smrt_jobs","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_jobs\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"queue\\" TEXT NOT NULL DEFAULT 'default',\\n \\"object_type\\" TEXT NOT NULL,\\n \\"object_id\\" TEXT,\\n \\"method\\" TEXT NOT NULL,\\n \\"args\\" JSON,\\n \\"run_at\\" TIMESTAMP NOT NULL,\\n \\"priority\\" INTEGER NOT NULL DEFAULT 50,\\n \\"status\\" TEXT NOT NULL DEFAULT 'pending',\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"max_attempts\\" INTEGER NOT NULL DEFAULT 3,\\n \\"timeout\\" INTEGER NOT NULL DEFAULT 300000,\\n \\"timeout_behavior\\" TEXT NOT NULL DEFAULT 'fail',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"last_error\\" TEXT,\\n \\"result_pointer\\" TEXT,\\n \\"task_id\\" TEXT UNIQUE,\\n \\"task_owner_id\\" TEXT,\\n \\"task_result\\" JSON,\\n \\"task_input_requests\\" JSON,\\n \\"task_input_responses\\" JSON,\\n \\"retry_strategy\\" JSON,\\n \\"worker_id\\" TEXT,\\n \\"worker_heartbeat\\" TIMESTAMP\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"queue":{"type":"TEXT","notNull":true,"unique":false,"default":"default"},"object_type":{"type":"TEXT","notNull":true,"unique":false},"object_id":{"type":"TEXT","notNull":false,"unique":false},"method":{"type":"TEXT","notNull":true,"unique":false},"args":{"type":"JSON","notNull":false,"unique":false},"run_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"priority":{"type":"INTEGER","notNull":true,"unique":false,"default":50},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"pending"},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"max_attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":3},"timeout":{"type":"INTEGER","notNull":true,"unique":false,"default":300000},"timeout_behavior":{"type":"TEXT","notNull":true,"unique":false,"default":"fail"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_error":{"type":"TEXT","notNull":false,"unique":false},"result_pointer":{"type":"TEXT","notNull":false,"unique":false},"task_id":{"type":"TEXT","notNull":false,"unique":true},"task_owner_id":{"type":"TEXT","notNull":false,"unique":false},"task_result":{"type":"JSON","notNull":false,"unique":false},"task_input_requests":{"type":"JSON","notNull":false,"unique":false},"task_input_responses":{"type":"JSON","notNull":false,"unique":false},"retry_strategy":{"type":"JSON","notNull":false,"unique":false},"worker_id":{"type":"TEXT","notNull":false,"unique":false},"worker_heartbeat":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_jobs_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_jobs_run_at_idx","columns":["run_at"]},{"name":"_smrt_jobs_status_idx","columns":["status"]},{"name":"_smrt_jobs_status_run_at_idx","columns":["status","run_at"]},{"name":"_smrt_jobs_ready_idx","columns":["status","run_at"],"where":"status = 'pending' AND (task_input_requests IS NULL OR task_input_responses IS NOT NULL)"},{"name":"_smrt_jobs_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"43e1324f"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtWorker":{"name":"smrtworker","className":"SmrtWorker","qualifiedName":"@happyvertical/smrt-jobs:SmrtWorker","collection":"smrtworkers","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-worker.ts","packageName":"@happyvertical/smrt-jobs","fields":{"workerId":{"type":"text","required":true,"_meta":{"required":true}},"pid":{"type":"integer","required":false,"_meta":{"nullable":true}},"hostname":{"type":"text","required":false,"_meta":{"nullable":true}},"startedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"heartbeatAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"leaseExpiresAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"status":{"type":"text","required":true,"default":"running","_meta":{"required":true,"default":"running"}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_workers","conflictColumns":["worker_id"],"api":false,"cli":false,"mcp":false},"extends":"SmrtObject","exportName":"SmrtWorker","collectionExportName":"SmrtWorkerCollection","validationRules":[{"field":"workerId","rule":"required","fieldType":"text"},{"field":"status","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_workers","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_workers\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"worker_id\\" TEXT NOT NULL,\\n \\"pid\\" INTEGER,\\n \\"hostname\\" TEXT,\\n \\"started_at\\" TIMESTAMP,\\n \\"heartbeat_at\\" TIMESTAMP,\\n \\"lease_expires_at\\" TIMESTAMP,\\n \\"status\\" TEXT NOT NULL DEFAULT 'running'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"worker_id":{"type":"TEXT","notNull":true,"unique":false},"pid":{"type":"INTEGER","notNull":false,"unique":false},"hostname":{"type":"TEXT","notNull":false,"unique":false},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"heartbeat_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"lease_expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"running"}},"indexes":[{"name":"_smrt_workers_worker_id_idx","columns":["worker_id"],"unique":true},{"name":"_smrt_workers_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_workers_created_at_idx","columns":["created_at"]}],"version":"bfaad669"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtWorkerCollection":{"name":"smrtworkercollection","className":"SmrtWorkerCollection","qualifiedName":"@happyvertical/smrt-jobs:SmrtWorkerCollection","collection":"smrtworkers","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-worker.ts","packageName":"@happyvertical/smrt-jobs","fields":{},"methods":{"assertReady":{"name":"assertReady","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"tableReady":{"name":"tableReady","async":true,"parameters":[],"returnType":"Promise<boolean>","isStatic":false,"isPublic":true},"registerWorker":{"name":"registerWorker","async":true,"parameters":[{"name":"input","type":"RegisterWorkerInput","optional":false}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"renewLease":{"name":"renewLease","async":true,"parameters":[{"name":"workerKey","type":"string","optional":false},{"name":"leaseTtlMs","type":"number","optional":false}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"expireWorker":{"name":"expireWorker","async":true,"parameters":[{"name":"workerKey","type":"string","optional":false}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"freshLeaseWorkerKeys":{"name":"freshLeaseWorkerKeys","async":true,"parameters":[],"returnType":"Promise<Set<string>>","isStatic":false,"isPublic":true},"pruneExpired":{"name":"pruneExpired","async":true,"parameters":[{"name":"graceMs","type":"number","optional":false}],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_workers","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"SmrtWorker","exportName":"SmrtWorkerCollection","collectionExportName":"SmrtWorkerCollectionCollection","schema":{"tableName":"_smrt_workers","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_workers\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"worker_id\\" TEXT NOT NULL,\\n \\"pid\\" INTEGER,\\n \\"hostname\\" TEXT,\\n \\"started_at\\" TIMESTAMP,\\n \\"heartbeat_at\\" TIMESTAMP,\\n \\"lease_expires_at\\" TIMESTAMP,\\n \\"status\\" TEXT NOT NULL DEFAULT 'running'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"worker_id":{"type":"TEXT","notNull":true,"unique":false},"pid":{"type":"INTEGER","notNull":false,"unique":false},"hostname":{"type":"TEXT","notNull":false,"unique":false},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"heartbeat_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"lease_expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"running"}},"indexes":[{"name":"_smrt_workers_worker_id_idx","columns":["worker_id"],"unique":true},{"name":"_smrt_workers_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_workers_created_at_idx","columns":["created_at"]}],"version":"bfaad669"},"importPath":"@happyvertical/smrt-jobs"}},"moduleType":"smrt","smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-tenancy"],"stampsConstructors":true}`));
13
+ ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-jobs","packageVersion":"0.55.7","objects":{"@happyvertical/smrt-jobs:ForgeDelivery":{"name":"forgedelivery","className":"ForgeDelivery","qualifiedName":"@happyvertical/smrt-jobs:ForgeDelivery","collection":"forgedeliveries","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/forge-projection.ts","packageName":"@happyvertical/smrt-jobs","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"provider":{"type":"text","required":true,"_meta":{"required":true}},"deliveryId":{"type":"text","required":true,"_meta":{"required":true}},"installationKey":{"type":"text","required":false,"_meta":{"nullable":true}},"repositoryKey":{"type":"text","required":false,"_meta":{"nullable":true}},"eventName":{"type":"text","required":true,"_meta":{"required":true}},"payload":{"type":"json","required":false,"_meta":{}},"status":{"type":"text","required":true,"default":"pending","_meta":{"required":true,"default":"pending"},"enum":["pending","leased","retry","completed","dead_letter"]},"attempts":{"type":"integer","required":true,"default":0,"_meta":{"required":true,"default":0}},"maxAttempts":{"type":"integer","required":true,"default":5,"_meta":{"required":true,"default":5}},"receivedAt":{"type":"datetime","required":true,"_meta":{"required":true}},"nextAttemptAt":{"type":"datetime","required":true,"_meta":{"required":true}},"leaseOwner":{"type":"text","required":false,"_meta":{"nullable":true}},"leaseToken":{"type":"text","required":false,"_meta":{"nullable":true}},"leaseExpiresAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"lastError":{"type":"text","required":false,"_meta":{"nullable":true}},"completedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"replayCount":{"type":"integer","required":true,"default":0,"_meta":{"required":true,"default":0}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_forge_deliveries","conflictColumns":["tenant_id","provider","delivery_id"],"api":false,"cli":false,"mcp":false,"indexes":[{"name":"_smrt_forge_deliveries_status_next_attempt_at_idx","columns":["status","nextAttemptAt"]},{"name":"_smrt_forge_deliveries_status_lease_expires_at_idx","columns":["status","leaseExpiresAt"]}],"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"ForgeDelivery","collectionExportName":"ForgeDeliveryCollection","validationRules":[{"field":"provider","rule":"required","fieldType":"text"},{"field":"deliveryId","rule":"required","fieldType":"text"},{"field":"eventName","rule":"required","fieldType":"text"},{"field":"status","rule":"required","fieldType":"text"},{"field":"attempts","rule":"required","fieldType":"integer"},{"field":"maxAttempts","rule":"required","fieldType":"integer"},{"field":"receivedAt","rule":"required","fieldType":"datetime"},{"field":"nextAttemptAt","rule":"required","fieldType":"datetime"},{"field":"replayCount","rule":"required","fieldType":"integer"}],"schema":{"tableName":"_smrt_forge_deliveries","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_forge_deliveries\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"provider\\" TEXT NOT NULL,\\n \\"delivery_id\\" TEXT NOT NULL,\\n \\"installation_key\\" TEXT,\\n \\"repository_key\\" TEXT,\\n \\"event_name\\" TEXT NOT NULL,\\n \\"payload\\" JSON,\\n \\"status\\" TEXT NOT NULL DEFAULT 'pending',\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"max_attempts\\" INTEGER NOT NULL DEFAULT 5,\\n \\"received_at\\" TIMESTAMP NOT NULL,\\n \\"next_attempt_at\\" TIMESTAMP NOT NULL,\\n \\"lease_owner\\" TEXT,\\n \\"lease_token\\" TEXT,\\n \\"lease_expires_at\\" TIMESTAMP,\\n \\"last_error\\" TEXT,\\n \\"completed_at\\" TIMESTAMP,\\n \\"replay_count\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"provider":{"type":"TEXT","notNull":true,"unique":false},"delivery_id":{"type":"TEXT","notNull":true,"unique":false},"installation_key":{"type":"TEXT","notNull":false,"unique":false},"repository_key":{"type":"TEXT","notNull":false,"unique":false},"event_name":{"type":"TEXT","notNull":true,"unique":false},"payload":{"type":"JSON","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"pending"},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"max_attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":5},"received_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"next_attempt_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"lease_owner":{"type":"TEXT","notNull":false,"unique":false},"lease_token":{"type":"TEXT","notNull":false,"unique":false},"lease_expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_error":{"type":"TEXT","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"replay_count":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"_smrt_forge_deliveries_tenant_id_provider_idx","columns":["tenant_id","provider","delivery_id"],"unique":true},{"name":"_smrt_forge_deliveries_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_forge_deliveries_status_next_attempt_at_idx","columns":["status","next_attempt_at"]},{"name":"_smrt_forge_deliveries_status_lease_expires_at_idx","columns":["status","lease_expires_at"]},{"name":"_smrt_forge_deliveries_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"38d3f476"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:ForgeProjectionCheckpoint":{"name":"forgeprojectioncheckpoint","className":"ForgeProjectionCheckpoint","qualifiedName":"@happyvertical/smrt-jobs:ForgeProjectionCheckpoint","collection":"forgeprojectioncheckpoints","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/forge-projection.ts","packageName":"@happyvertical/smrt-jobs","fields":{"tenantId":{"type":"text","required":true,"_meta":{"sqlType":"UUID","__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":true,"autoPopulate":true,"nullable":false,"mode":"required","field":"tenantId","allowSuperAdminBypass":false}}},"projection":{"type":"text","required":true,"_meta":{"required":true}},"subjectKey":{"type":"text","required":true,"_meta":{"required":true}},"observationVersion":{"type":"integer","required":true,"_meta":{"required":true}},"deliveryId":{"type":"text","required":true,"_meta":{"required":true}},"observedAt":{"type":"datetime","required":true,"_meta":{"required":true}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_forge_projection_checkpoints","conflictColumns":["tenant_id","projection","subject_key"],"api":false,"cli":false,"mcp":false,"tenantScoped":{"mode":"required"}},"extends":"SmrtObject","exportName":"ForgeProjectionCheckpoint","collectionExportName":"ForgeProjectionCheckpointCollection","validationRules":[{"field":"projection","rule":"required","fieldType":"text"},{"field":"subjectKey","rule":"required","fieldType":"text"},{"field":"observationVersion","rule":"required","fieldType":"integer"},{"field":"deliveryId","rule":"required","fieldType":"text"},{"field":"observedAt","rule":"required","fieldType":"datetime"}],"schema":{"tableName":"_smrt_forge_projection_checkpoints","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_forge_projection_checkpoints\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"projection\\" TEXT NOT NULL,\\n \\"subject_key\\" TEXT NOT NULL,\\n \\"observation_version\\" INTEGER NOT NULL,\\n \\"delivery_id\\" TEXT NOT NULL,\\n \\"observed_at\\" TIMESTAMP NOT NULL\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"projection":{"type":"TEXT","notNull":true,"unique":false},"subject_key":{"type":"TEXT","notNull":true,"unique":false},"observation_version":{"type":"INTEGER","notNull":true,"unique":false},"delivery_id":{"type":"TEXT","notNull":true,"unique":false},"observed_at":{"type":"TIMESTAMP","notNull":true,"unique":false}},"indexes":[{"name":"_smrt_forge_projection_checkpoints_tenant_id_projection_idx","columns":["tenant_id","projection","subject_key"],"unique":true},{"name":"_smrt_forge_projection_checkpoints_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_forge_projection_checkpoints_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"39c12493"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:ForgeDeliveryCollection":{"name":"forgedeliverycollection","className":"ForgeDeliveryCollection","qualifiedName":"@happyvertical/smrt-jobs:ForgeDeliveryCollection","collection":"forgedeliveries","description":"Durable provider delivery inbox.","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/forge-projection.ts","packageName":"@happyvertical/smrt-jobs","fields":{},"methods":{"accept":{"name":"accept","async":true,"parameters":[{"name":"input","type":"AcceptForgeDeliveryInput","optional":false},{"name":"audit","type":"ForgeProjectionAuditHook","optional":true}],"returnType":"Promise<AcceptedForgeDelivery>","isStatic":false,"isPublic":true},"claimReady":{"name":"claimReady","async":true,"parameters":[{"name":"options","type":"ClaimForgeDeliveryOptions","optional":false},{"name":"audit","type":"ForgeProjectionAuditHook","optional":true}],"returnType":"Promise<ForgeDelivery | null>","isStatic":false,"isPublic":true},"replay":{"name":"replay","async":true,"parameters":[{"name":"id","type":"string","optional":false},{"name":"audit","type":"ForgeProjectionAuditHook","optional":true}],"returnType":"Promise<boolean>","isStatic":false,"isPublic":true}},"decoratorConfig":{"api":false,"cli":false,"mcp":false,"tableName":"_smrt_forge_deliveries"},"extends":"SmrtCollection","extendsTypeArg":"ForgeDelivery","exportName":"ForgeDeliveryCollection","collectionExportName":"ForgeDeliveryCollectionCollection","schema":{"tableName":"_smrt_forge_deliveries","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_forge_deliveries\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID NOT NULL,\\n \\"provider\\" TEXT NOT NULL,\\n \\"delivery_id\\" TEXT NOT NULL,\\n \\"installation_key\\" TEXT,\\n \\"repository_key\\" TEXT,\\n \\"event_name\\" TEXT NOT NULL,\\n \\"payload\\" JSON,\\n \\"status\\" TEXT NOT NULL DEFAULT 'pending',\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"max_attempts\\" INTEGER NOT NULL DEFAULT 5,\\n \\"received_at\\" TIMESTAMP NOT NULL,\\n \\"next_attempt_at\\" TIMESTAMP NOT NULL,\\n \\"lease_owner\\" TEXT,\\n \\"lease_token\\" TEXT,\\n \\"lease_expires_at\\" TIMESTAMP,\\n \\"last_error\\" TEXT,\\n \\"completed_at\\" TIMESTAMP,\\n \\"replay_count\\" INTEGER NOT NULL DEFAULT 0\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":true,"unique":false},"provider":{"type":"TEXT","notNull":true,"unique":false},"delivery_id":{"type":"TEXT","notNull":true,"unique":false},"installation_key":{"type":"TEXT","notNull":false,"unique":false},"repository_key":{"type":"TEXT","notNull":false,"unique":false},"event_name":{"type":"TEXT","notNull":true,"unique":false},"payload":{"type":"JSON","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"pending"},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"max_attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":5},"received_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"next_attempt_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"lease_owner":{"type":"TEXT","notNull":false,"unique":false},"lease_token":{"type":"TEXT","notNull":false,"unique":false},"lease_expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_error":{"type":"TEXT","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"replay_count":{"type":"INTEGER","notNull":true,"unique":false,"default":0}},"indexes":[{"name":"_smrt_forge_deliveries_tenant_id_provider_idx","columns":["tenant_id","provider","delivery_id"],"unique":true},{"name":"_smrt_forge_deliveries_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_forge_deliveries_status_next_attempt_at_idx","columns":["status","next_attempt_at"]},{"name":"_smrt_forge_deliveries_status_lease_expires_at_idx","columns":["status","lease_expires_at"]},{"name":"_smrt_forge_deliveries_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"38d3f476"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtJobEvent":{"name":"smrtjobevent","className":"SmrtJobEvent","qualifiedName":"@happyvertical/smrt-jobs:SmrtJobEvent","collection":"smrtjobevents","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-job-event.ts","packageName":"@happyvertical/smrt-jobs","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"jobId":{"type":"foreignKey","required":true,"related":"SmrtJob","_meta":{"required":true,"constraint":false}},"type":{"type":"text","required":true,"default":"log","_meta":{"required":true,"default":"log"}},"level":{"type":"text","required":true,"default":"info","_meta":{"required":true,"default":"info"},"enum":["debug","info","warn","error"]},"stage":{"type":"text","required":false,"_meta":{"nullable":true}},"progress":{"type":"integer","required":false,"_meta":{"nullable":true}},"message":{"type":"text","required":true,"default":"","_meta":{"required":true,"default":""}},"data":{"type":"json","required":false,"_meta":{}},"createdAt":{"type":"datetime","required":true,"_meta":{"required":true}}},"methods":{"toCursor":{"name":"toCursor","async":false,"parameters":[],"returnType":"string","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_job_events","api":false,"cli":{"include":["list","get"],"http":false,"skipApiCheck":true},"mcp":false,"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"SmrtJobEvent","collectionExportName":"SmrtJobEventCollection","validationRules":[{"field":"jobId","rule":"required","fieldType":"foreignKey"},{"field":"type","rule":"required","fieldType":"text"},{"field":"level","rule":"required","fieldType":"text"},{"field":"message","rule":"required","fieldType":"text"},{"field":"createdAt","rule":"required","fieldType":"datetime"}],"schema":{"tableName":"_smrt_job_events","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_job_events\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"job_id\\" UUID NOT NULL,\\n \\"type\\" TEXT NOT NULL DEFAULT 'log',\\n \\"level\\" TEXT NOT NULL DEFAULT 'info',\\n \\"stage\\" TEXT,\\n \\"progress\\" INTEGER,\\n \\"message\\" TEXT NOT NULL DEFAULT '',\\n \\"data\\" JSON\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"job_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false},"type":{"type":"TEXT","notNull":true,"unique":false,"default":"log"},"level":{"type":"TEXT","notNull":true,"unique":false,"default":"info"},"stage":{"type":"TEXT","notNull":false,"unique":false},"progress":{"type":"INTEGER","notNull":false,"unique":false},"message":{"type":"TEXT","notNull":true,"unique":false,"default":""},"data":{"type":"JSON","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_job_events_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_job_events_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"_smrt_job_events_job_id_idx","columns":["job_id"]}],"version":"8a9faeec"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtJobEventCollection":{"name":"smrtjobeventcollection","className":"SmrtJobEventCollection","qualifiedName":"@happyvertical/smrt-jobs:SmrtJobEventCollection","collection":"smrtjobevents","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-job-event.ts","packageName":"@happyvertical/smrt-jobs","fields":{},"methods":{"initialize":{"name":"initialize","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"append":{"name":"append","async":true,"parameters":[{"name":"input","type":"SmrtJobEventData","optional":false}],"returnType":"Promise<SmrtJobEvent>","isStatic":false,"isPublic":true},"listByJob":{"name":"listByJob","async":true,"parameters":[{"name":"jobId","type":"string","optional":false},{"name":"options","type":"ListJobEventsOptions","optional":true}],"returnType":"Promise<SmrtJobEvent[]>","isStatic":false,"isPublic":true},"listSinceCursor":{"name":"listSinceCursor","async":true,"parameters":[{"name":"options","type":"any","optional":true,"typeUnresolved":true}],"returnType":"Promise<SmrtJobEvent[]>","isStatic":false,"isPublic":true},"latestProgressByJobIds":{"name":"latestProgressByJobIds","async":true,"parameters":[{"name":"jobIds","type":"string[]","optional":false},{"name":"options","type":"object","optional":true,"memberTypes":["string | null"]}],"returnType":"Promise<Map<string, SmrtJobEvent>>","isStatic":false,"isPublic":true},"listTerminalOutcomes":{"name":"listTerminalOutcomes","async":true,"parameters":[{"name":"options","type":"ListTerminalOutcomesOptions","optional":false}],"returnType":"Promise<SmrtJobTerminalOutcomePage>","isStatic":false,"isPublic":true},"cleanup":{"name":"cleanup","async":true,"parameters":[{"name":"options","type":"object","optional":false,"memberTypes":["Date","boolean"]}],"returnType":"Promise<number>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_job_events","api":false,"mcp":false},"extends":"SmrtCollection","extendsTypeArg":"SmrtJobEvent","exportName":"SmrtJobEventCollection","collectionExportName":"SmrtJobEventCollectionCollection","schema":{"tableName":"_smrt_job_events","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_job_events\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"job_id\\" UUID NOT NULL,\\n \\"type\\" TEXT NOT NULL DEFAULT 'log',\\n \\"level\\" TEXT NOT NULL DEFAULT 'info',\\n \\"stage\\" TEXT,\\n \\"progress\\" INTEGER,\\n \\"message\\" TEXT NOT NULL DEFAULT '',\\n \\"data\\" JSON\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"job_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false},"type":{"type":"TEXT","notNull":true,"unique":false,"default":"log"},"level":{"type":"TEXT","notNull":true,"unique":false,"default":"info"},"stage":{"type":"TEXT","notNull":false,"unique":false},"progress":{"type":"INTEGER","notNull":false,"unique":false},"message":{"type":"TEXT","notNull":true,"unique":false,"default":""},"data":{"type":"JSON","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_job_events_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_job_events_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"_smrt_job_events_job_id_idx","columns":["job_id"]}],"version":"8a9faeec"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtJob":{"name":"smrtjob","className":"SmrtJob","qualifiedName":"@happyvertical/smrt-jobs:SmrtJob","collection":"smrtjobs","description":"Persistent job record stored in the _smrt_jobs system table.","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-job.ts","packageName":"@happyvertical/smrt-jobs","fields":{"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true,"__tenancy":{"isTenantIdField":true,"autoFilter":true,"required":false,"autoPopulate":true,"nullable":true,"mode":"optional","field":"tenantId","allowSuperAdminBypass":false}}},"queue":{"type":"text","required":true,"default":"default","_meta":{"required":true,"default":"default"}},"objectType":{"type":"text","required":true,"_meta":{"required":true}},"objectId":{"type":"text","required":false,"_meta":{"nullable":true}},"method":{"type":"text","required":true,"_meta":{"required":true}},"args":{"type":"json","required":false,"_meta":{}},"runAt":{"type":"datetime","required":true,"_meta":{"required":true,"indexed":true}},"priority":{"type":"integer","required":true,"default":50,"_meta":{"required":true,"default":50}},"status":{"type":"text","required":true,"default":"pending","_meta":{"required":true,"default":"pending","indexed":true},"enum":["pending","ready","running","completed","failed","cancelled"]},"attempts":{"type":"integer","required":true,"default":0,"_meta":{"required":true,"default":0}},"maxAttempts":{"type":"integer","required":true,"default":3,"_meta":{"required":true,"default":3}},"timeout":{"type":"integer","required":true,"default":300000,"_meta":{"required":true,"default":300000}},"timeoutBehavior":{"type":"text","required":true,"default":"fail","_meta":{"required":true,"default":"fail"},"enum":["fail","kill","warn"]},"startedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"completedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"lastError":{"type":"text","required":false,"_meta":{"nullable":true}},"resultPointer":{"type":"text","required":false,"_meta":{"nullable":true}},"taskId":{"type":"text","required":false,"_meta":{"nullable":true,"unique":true}},"taskOwnerId":{"type":"text","required":false,"_meta":{"nullable":true}},"taskResult":{"type":"json","required":false,"_meta":{"nullable":true}},"taskInputRequests":{"type":"json","required":false,"_meta":{"nullable":true}},"taskInputResponses":{"type":"json","required":false,"_meta":{"nullable":true}},"retryStrategy":{"type":"json","required":false,"_meta":{}},"workerId":{"type":"text","required":false,"_meta":{"nullable":true}},"workerHeartbeat":{"type":"datetime","required":false,"_meta":{"nullable":true}}},"methods":{"save":{"name":"save","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"retry":{"name":"retry","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"cancel":{"name":"cancel","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"getDescription":{"name":"getDescription","async":false,"parameters":[],"returnType":"string","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_jobs","api":false,"cli":{"include":["list","get","retry","cancel"],"skipApiCheck":true,"http":false},"mcp":false,"indexes":[{"name":"_smrt_jobs_status_run_at_idx","columns":["status","runAt"]},{"name":"_smrt_jobs_ready_idx","columns":["status","runAt"],"where":"status = 'pending' AND (task_input_requests IS NULL OR task_input_responses IS NOT NULL)"}],"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"SmrtJob","collectionExportName":"SmrtJobCollection","validationRules":[{"field":"queue","rule":"required","fieldType":"text"},{"field":"objectType","rule":"required","fieldType":"text"},{"field":"method","rule":"required","fieldType":"text"},{"field":"runAt","rule":"required","fieldType":"datetime"},{"field":"priority","rule":"required","fieldType":"integer"},{"field":"status","rule":"required","fieldType":"text"},{"field":"attempts","rule":"required","fieldType":"integer"},{"field":"maxAttempts","rule":"required","fieldType":"integer"},{"field":"timeout","rule":"required","fieldType":"integer"},{"field":"timeoutBehavior","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_jobs","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_jobs\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"queue\\" TEXT NOT NULL DEFAULT 'default',\\n \\"object_type\\" TEXT NOT NULL,\\n \\"object_id\\" TEXT,\\n \\"method\\" TEXT NOT NULL,\\n \\"args\\" JSON,\\n \\"run_at\\" TIMESTAMP NOT NULL,\\n \\"priority\\" INTEGER NOT NULL DEFAULT 50,\\n \\"status\\" TEXT NOT NULL DEFAULT 'pending',\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"max_attempts\\" INTEGER NOT NULL DEFAULT 3,\\n \\"timeout\\" INTEGER NOT NULL DEFAULT 300000,\\n \\"timeout_behavior\\" TEXT NOT NULL DEFAULT 'fail',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"last_error\\" TEXT,\\n \\"result_pointer\\" TEXT,\\n \\"task_id\\" TEXT UNIQUE,\\n \\"task_owner_id\\" TEXT,\\n \\"task_result\\" JSON,\\n \\"task_input_requests\\" JSON,\\n \\"task_input_responses\\" JSON,\\n \\"retry_strategy\\" JSON,\\n \\"worker_id\\" TEXT,\\n \\"worker_heartbeat\\" TIMESTAMP\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"queue":{"type":"TEXT","notNull":true,"unique":false,"default":"default"},"object_type":{"type":"TEXT","notNull":true,"unique":false},"object_id":{"type":"TEXT","notNull":false,"unique":false},"method":{"type":"TEXT","notNull":true,"unique":false},"args":{"type":"JSON","notNull":false,"unique":false},"run_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"priority":{"type":"INTEGER","notNull":true,"unique":false,"default":50},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"pending"},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"max_attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":3},"timeout":{"type":"INTEGER","notNull":true,"unique":false,"default":300000},"timeout_behavior":{"type":"TEXT","notNull":true,"unique":false,"default":"fail"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_error":{"type":"TEXT","notNull":false,"unique":false},"result_pointer":{"type":"TEXT","notNull":false,"unique":false},"task_id":{"type":"TEXT","notNull":false,"unique":true},"task_owner_id":{"type":"TEXT","notNull":false,"unique":false},"task_result":{"type":"JSON","notNull":false,"unique":false},"task_input_requests":{"type":"JSON","notNull":false,"unique":false},"task_input_responses":{"type":"JSON","notNull":false,"unique":false},"retry_strategy":{"type":"JSON","notNull":false,"unique":false},"worker_id":{"type":"TEXT","notNull":false,"unique":false},"worker_heartbeat":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_jobs_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_jobs_run_at_idx","columns":["run_at"]},{"name":"_smrt_jobs_status_idx","columns":["status"]},{"name":"_smrt_jobs_status_run_at_idx","columns":["status","run_at"]},{"name":"_smrt_jobs_ready_idx","columns":["status","run_at"],"where":"status = 'pending' AND (task_input_requests IS NULL OR task_input_responses IS NOT NULL)"},{"name":"_smrt_jobs_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"43e1324f"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtJobCollection":{"name":"smrtjobcollection","className":"SmrtJobCollection","qualifiedName":"@happyvertical/smrt-jobs:SmrtJobCollection","collection":"smrtjobs","description":"Collection for managing SmrtJob objects","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-job.ts","packageName":"@happyvertical/smrt-jobs","fields":{},"methods":{"initialize":{"name":"initialize","async":true,"parameters":[],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"listByStatus":{"name":"listByStatus","async":true,"parameters":[{"name":"status","type":"JobStatus | JobStatus[]","optional":false,"unionBranches":[{"type":"JobStatus"},{"type":"JobStatus[]"}]},{"name":"options","type":"object","optional":true,"memberTypes":["number","string"]}],"returnType":"Promise<SmrtJob[]>","isStatic":false,"isPublic":true},"listReady":{"name":"listReady","async":true,"parameters":[{"name":"options","type":"object","optional":true,"memberTypes":["number","string[]"]}],"returnType":"Promise<SmrtJob[]>","isStatic":false,"isPublic":true},"claimReady":{"name":"claimReady","async":true,"parameters":[{"name":"options","type":"ClaimReadyOptions","optional":false}],"returnType":"Promise<SmrtJob[]>","isStatic":false,"isPublic":true},"countInFlightForTenant":{"name":"countInFlightForTenant","async":true,"parameters":[{"name":"tenantId","type":"string | null","optional":false,"unionBranches":[{"type":"string"},{"type":"null"}]}],"returnType":"Promise<number>","isStatic":false,"isPublic":true},"enqueueJob":{"name":"enqueueJob","async":true,"parameters":[{"name":"data","type":"SmrtJobData","optional":false},{"name":"options","type":"EnqueueJobOptions","optional":true}],"returnType":"Promise<SmrtJob>","isStatic":false,"isPublic":true},"stats":{"name":"stats","async":true,"parameters":[{"name":"queue","type":"string","optional":true}],"returnType":"Promise<object>","isStatic":false,"isPublic":true},"cleanup":{"name":"cleanup","async":true,"parameters":[{"name":"options","type":"object","optional":false,"memberTypes":["Date","number","boolean"]}],"returnType":"Promise<number>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_jobs","api":false,"mcp":false},"extends":"SmrtCollection","extendsTypeArg":"SmrtJob","exportName":"SmrtJobCollection","collectionExportName":"SmrtJobCollectionCollection","schema":{"tableName":"_smrt_jobs","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_jobs\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"tenant_id\\" UUID,\\n \\"queue\\" TEXT NOT NULL DEFAULT 'default',\\n \\"object_type\\" TEXT NOT NULL,\\n \\"object_id\\" TEXT,\\n \\"method\\" TEXT NOT NULL,\\n \\"args\\" JSON,\\n \\"run_at\\" TIMESTAMP NOT NULL,\\n \\"priority\\" INTEGER NOT NULL DEFAULT 50,\\n \\"status\\" TEXT NOT NULL DEFAULT 'pending',\\n \\"attempts\\" INTEGER NOT NULL DEFAULT 0,\\n \\"max_attempts\\" INTEGER NOT NULL DEFAULT 3,\\n \\"timeout\\" INTEGER NOT NULL DEFAULT 300000,\\n \\"timeout_behavior\\" TEXT NOT NULL DEFAULT 'fail',\\n \\"started_at\\" TIMESTAMP,\\n \\"completed_at\\" TIMESTAMP,\\n \\"last_error\\" TEXT,\\n \\"result_pointer\\" TEXT,\\n \\"task_id\\" TEXT UNIQUE,\\n \\"task_owner_id\\" TEXT,\\n \\"task_result\\" JSON,\\n \\"task_input_requests\\" JSON,\\n \\"task_input_responses\\" JSON,\\n \\"retry_strategy\\" JSON,\\n \\"worker_id\\" TEXT,\\n \\"worker_heartbeat\\" TIMESTAMP\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"tenant_id":{"type":"UUID","referenceKind":"tenantId","notNull":false,"unique":false},"queue":{"type":"TEXT","notNull":true,"unique":false,"default":"default"},"object_type":{"type":"TEXT","notNull":true,"unique":false},"object_id":{"type":"TEXT","notNull":false,"unique":false},"method":{"type":"TEXT","notNull":true,"unique":false},"args":{"type":"JSON","notNull":false,"unique":false},"run_at":{"type":"TIMESTAMP","notNull":true,"unique":false},"priority":{"type":"INTEGER","notNull":true,"unique":false,"default":50},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"pending"},"attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":0},"max_attempts":{"type":"INTEGER","notNull":true,"unique":false,"default":3},"timeout":{"type":"INTEGER","notNull":true,"unique":false,"default":300000},"timeout_behavior":{"type":"TEXT","notNull":true,"unique":false,"default":"fail"},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"completed_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"last_error":{"type":"TEXT","notNull":false,"unique":false},"result_pointer":{"type":"TEXT","notNull":false,"unique":false},"task_id":{"type":"TEXT","notNull":false,"unique":true},"task_owner_id":{"type":"TEXT","notNull":false,"unique":false},"task_result":{"type":"JSON","notNull":false,"unique":false},"task_input_requests":{"type":"JSON","notNull":false,"unique":false},"task_input_responses":{"type":"JSON","notNull":false,"unique":false},"retry_strategy":{"type":"JSON","notNull":false,"unique":false},"worker_id":{"type":"TEXT","notNull":false,"unique":false},"worker_heartbeat":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_jobs_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"_smrt_jobs_run_at_idx","columns":["run_at"]},{"name":"_smrt_jobs_status_idx","columns":["status"]},{"name":"_smrt_jobs_status_run_at_idx","columns":["status","run_at"]},{"name":"_smrt_jobs_ready_idx","columns":["status","run_at"],"where":"status = 'pending' AND (task_input_requests IS NULL OR task_input_responses IS NOT NULL)"},{"name":"_smrt_jobs_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"43e1324f"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtWorker":{"name":"smrtworker","className":"SmrtWorker","qualifiedName":"@happyvertical/smrt-jobs:SmrtWorker","collection":"smrtworkers","description":"Liveness record for a single TaskRunner / ScheduleRunner incarnation, stored in the _smrt_workers system table.","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-worker.ts","packageName":"@happyvertical/smrt-jobs","fields":{"workerId":{"type":"text","required":true,"_meta":{"required":true}},"pid":{"type":"integer","required":false,"_meta":{"nullable":true}},"hostname":{"type":"text","required":false,"_meta":{"nullable":true}},"startedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"heartbeatAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"leaseExpiresAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"status":{"type":"text","required":true,"default":"running","_meta":{"required":true,"default":"running"}}},"methods":{},"decoratorConfig":{"tableName":"_smrt_workers","conflictColumns":["worker_id"],"api":false,"cli":false,"mcp":false},"extends":"SmrtObject","exportName":"SmrtWorker","collectionExportName":"SmrtWorkerCollection","validationRules":[{"field":"workerId","rule":"required","fieldType":"text"},{"field":"status","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_workers","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_workers\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"worker_id\\" TEXT NOT NULL,\\n \\"pid\\" INTEGER,\\n \\"hostname\\" TEXT,\\n \\"started_at\\" TIMESTAMP,\\n \\"heartbeat_at\\" TIMESTAMP,\\n \\"lease_expires_at\\" TIMESTAMP,\\n \\"status\\" TEXT NOT NULL DEFAULT 'running'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"worker_id":{"type":"TEXT","notNull":true,"unique":false},"pid":{"type":"INTEGER","notNull":false,"unique":false},"hostname":{"type":"TEXT","notNull":false,"unique":false},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"heartbeat_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"lease_expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"running"}},"indexes":[{"name":"_smrt_workers_worker_id_idx","columns":["worker_id"],"unique":true},{"name":"_smrt_workers_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_workers_created_at_idx","columns":["created_at"]}],"version":"bfaad669"},"importPath":"@happyvertical/smrt-jobs"},"@happyvertical/smrt-jobs:SmrtWorkerCollection":{"name":"smrtworkercollection","className":"SmrtWorkerCollection","qualifiedName":"@happyvertical/smrt-jobs:SmrtWorkerCollection","collection":"smrtworkers","description":"Collection for managing _smrt_workers liveness rows.","filePath":"/home/runner/work/smrt/smrt/packages/jobs/src/smrt-worker.ts","packageName":"@happyvertical/smrt-jobs","fields":{},"methods":{"assertReady":{"name":"assertReady","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"tableReady":{"name":"tableReady","async":true,"parameters":[],"returnType":"Promise<boolean>","isStatic":false,"isPublic":true},"registerWorker":{"name":"registerWorker","async":true,"parameters":[{"name":"input","type":"RegisterWorkerInput","optional":false}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"renewLease":{"name":"renewLease","async":true,"parameters":[{"name":"workerKey","type":"string","optional":false},{"name":"leaseTtlMs","type":"number","optional":false}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"expireWorker":{"name":"expireWorker","async":true,"parameters":[{"name":"workerKey","type":"string","optional":false}],"returnType":"Promise<void>","isStatic":false,"isPublic":true},"freshLeaseWorkerKeys":{"name":"freshLeaseWorkerKeys","async":true,"parameters":[],"returnType":"Promise<Set<string>>","isStatic":false,"isPublic":true},"pruneExpired":{"name":"pruneExpired","async":true,"parameters":[{"name":"graceMs","type":"number","optional":false}],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_workers","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"SmrtWorker","exportName":"SmrtWorkerCollection","collectionExportName":"SmrtWorkerCollectionCollection","schema":{"tableName":"_smrt_workers","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_workers\\" (\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"worker_id\\" TEXT NOT NULL,\\n \\"pid\\" INTEGER,\\n \\"hostname\\" TEXT,\\n \\"started_at\\" TIMESTAMP,\\n \\"heartbeat_at\\" TIMESTAMP,\\n \\"lease_expires_at\\" TIMESTAMP,\\n \\"status\\" TEXT NOT NULL DEFAULT 'running'\\n);","columns":{"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"worker_id":{"type":"TEXT","notNull":true,"unique":false},"pid":{"type":"INTEGER","notNull":false,"unique":false},"hostname":{"type":"TEXT","notNull":false,"unique":false},"started_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"heartbeat_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"lease_expires_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":true,"unique":false,"default":"running"}},"indexes":[{"name":"_smrt_workers_worker_id_idx","columns":["worker_id"],"unique":true},{"name":"_smrt_workers_slug_context_idx","columns":["slug","context"]},{"name":"_smrt_workers_created_at_idx","columns":["created_at"]}],"version":"bfaad669"},"importPath":"@happyvertical/smrt-jobs"}},"moduleType":"smrt","smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-tenancy"],"stampsConstructors":true}`));
12
14
  //#endregion
13
15
  //#region src/background-policy.ts
14
16
  var MAX_JOB_RETRIES = 25;
@@ -59,7 +61,7 @@ function isBackgroundEligibleMethod(ctor, method) {
59
61
  return allow.has(method);
60
62
  }
61
63
  //#endregion
62
- //#region src/smrt-job.ts
64
+ //#region src/smrt-job-event.ts
63
65
  var __defProp$2 = Object.defineProperty;
64
66
  var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
65
67
  var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, {
@@ -75,461 +77,464 @@ var __decorateClass$2 = (decorators, target, key, kind) => {
75
77
  return result;
76
78
  };
77
79
  var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
78
- var SMRT_JOB_PORTABLE_SELECT_COLUMNS = `
79
- CAST(id AS VARCHAR) AS id,
80
- slug, context, created_at, updated_at, tenant_id, queue,
81
- object_type, object_id, method, args, run_at,
82
- priority, status, attempts, max_attempts, timeout,
83
- timeout_behavior, started_at, completed_at, last_error,
84
- result_pointer, task_id, task_owner_id, task_result,
85
- task_input_requests, task_input_responses, retry_strategy,
86
- worker_id, worker_heartbeat
87
- `;
88
- var SmrtJob = class extends SmrtObject {
80
+ var JOB_EVENT_STORAGE_COLUMNS = [
81
+ "id",
82
+ "slug",
83
+ "context",
84
+ "created_at",
85
+ "updated_at",
86
+ "tenant_id",
87
+ "job_id",
88
+ "type",
89
+ "level",
90
+ "stage",
91
+ "progress",
92
+ "message",
93
+ "data"
94
+ ].join(", ");
95
+ var TERMINAL_OUTCOME_CANDIDATE_LIMIT = 1e3;
96
+ var SmrtJobEvent = class extends SmrtObject {
89
97
  tenantId = void 0;
90
- queue = "default";
91
- objectType = "";
92
- objectId = null;
93
- method = "";
94
- args = {};
95
- runAt = /* @__PURE__ */ new Date();
96
- priority = 50;
97
- status = "pending";
98
- attempts = 0;
99
- maxAttempts = 3;
100
- timeout = 3e5;
101
- timeoutBehavior = "fail";
102
- startedAt = null;
103
- completedAt = null;
104
- lastError = null;
105
- resultPointer = null;
106
- taskId = null;
107
- taskOwnerId = null;
108
- taskResult = null;
109
- taskInputRequests = null;
110
- taskInputResponses = null;
111
- retryStrategy = {
112
- type: "exponential",
113
- config: {
114
- initialDelay: 1e3,
115
- multiplier: 2,
116
- maxDelay: 3e5
117
- }
118
- };
119
- workerId = null;
120
- workerHeartbeat = null;
121
- /**
122
- * Capture ambient tenant context when a job is saved inside withTenant().
123
- *
124
- * Scheduled jobs can also set this explicitly from their owning schedule.
125
- */
126
- async save() {
127
- if (this.tenantId === void 0) {
128
- const contextTenantId = getTenantId();
129
- if (contextTenantId) this.tenantId = contextTenantId;
130
- }
131
- return super.save();
132
- }
133
- /**
134
- * Mark the job for retry
135
- */
136
- async retry() {
137
- if (this.status === "completed") throw new Error("Cannot retry a completed job");
138
- this.status = "pending";
139
- this.attempts = 0;
140
- this.lastError = null;
141
- this.startedAt = null;
142
- this.completedAt = null;
143
- this.workerId = null;
144
- this.workerHeartbeat = null;
145
- await this.save();
146
- }
147
- /**
148
- * Cancel the job
149
- */
150
- async cancel() {
151
- if (this.status === "completed" || this.status === "cancelled") throw new Error(`Cannot cancel job with status: ${this.status}`);
152
- this.status = "cancelled";
153
- this.completedAt = /* @__PURE__ */ new Date();
154
- await this.save();
155
- }
156
- /**
157
- * Get a human-readable description of the job
158
- */
159
- getDescription() {
160
- return `${this.objectId ? `${this.objectType}#${this.objectId}` : this.objectType}.${this.method}()`;
98
+ jobId = "";
99
+ type = "log";
100
+ level = "info";
101
+ stage = null;
102
+ progress = null;
103
+ message = "";
104
+ data = {};
105
+ createdAt = /* @__PURE__ */ new Date();
106
+ toCursor() {
107
+ return `${this.createdAt instanceof Date ? this.createdAt.toISOString() : String(this.createdAt)}|${this.id ?? ""}`;
161
108
  }
162
109
  };
163
- __publicField$2(SmrtJob, "__smrtPackage__", "@happyvertical/smrt-jobs");
164
- __decorateClass$2([tenantId({ nullable: true })], SmrtJob.prototype, "tenantId", 2);
165
- __decorateClass$2([field({
166
- type: "text",
167
- required: true,
168
- default: "default"
169
- })], SmrtJob.prototype, "queue", 2);
170
- __decorateClass$2([field({
171
- type: "text",
172
- required: true
173
- })], SmrtJob.prototype, "objectType", 2);
174
- __decorateClass$2([field({
175
- type: "text",
176
- nullable: true
177
- })], SmrtJob.prototype, "objectId", 2);
178
- __decorateClass$2([field({
179
- type: "text",
180
- required: true
181
- })], SmrtJob.prototype, "method", 2);
182
- __decorateClass$2([field({ type: "json" })], SmrtJob.prototype, "args", 2);
183
- __decorateClass$2([field({
184
- type: "datetime",
110
+ __publicField$2(SmrtJobEvent, "__smrtPackage__", "@happyvertical/smrt-jobs");
111
+ __decorateClass$2([tenantId({ nullable: true })], SmrtJobEvent.prototype, "tenantId", 2);
112
+ __decorateClass$2([foreignKey("SmrtJob", {
185
113
  required: true,
186
- indexed: true
187
- })], SmrtJob.prototype, "runAt", 2);
188
- __decorateClass$2([field({
189
- type: "integer",
190
- required: true,
191
- default: 50
192
- })], SmrtJob.prototype, "priority", 2);
114
+ constraint: false
115
+ })], SmrtJobEvent.prototype, "jobId", 2);
193
116
  __decorateClass$2([field({
194
117
  type: "text",
195
118
  required: true,
196
- default: "pending",
197
- indexed: true
198
- })], SmrtJob.prototype, "status", 2);
199
- __decorateClass$2([field({
200
- type: "integer",
201
- required: true,
202
- default: 0
203
- })], SmrtJob.prototype, "attempts", 2);
204
- __decorateClass$2([field({
205
- type: "integer",
206
- required: true,
207
- default: 3
208
- })], SmrtJob.prototype, "maxAttempts", 2);
209
- __decorateClass$2([field({
210
- type: "integer",
211
- required: true,
212
- default: 3e5
213
- })], SmrtJob.prototype, "timeout", 2);
119
+ default: "log"
120
+ })], SmrtJobEvent.prototype, "type", 2);
214
121
  __decorateClass$2([field({
215
122
  type: "text",
216
123
  required: true,
217
- default: "fail"
218
- })], SmrtJob.prototype, "timeoutBehavior", 2);
219
- __decorateClass$2([field({
220
- type: "datetime",
221
- nullable: true
222
- })], SmrtJob.prototype, "startedAt", 2);
223
- __decorateClass$2([field({
224
- type: "datetime",
225
- nullable: true
226
- })], SmrtJob.prototype, "completedAt", 2);
227
- __decorateClass$2([field({
228
- type: "text",
229
- nullable: true
230
- })], SmrtJob.prototype, "lastError", 2);
231
- __decorateClass$2([field({
232
- type: "text",
233
- nullable: true
234
- })], SmrtJob.prototype, "resultPointer", 2);
235
- __decorateClass$2([field({
236
- type: "text",
237
- nullable: true,
238
- unique: true
239
- })], SmrtJob.prototype, "taskId", 2);
124
+ default: "info"
125
+ })], SmrtJobEvent.prototype, "level", 2);
240
126
  __decorateClass$2([field({
241
127
  type: "text",
242
128
  nullable: true
243
- })], SmrtJob.prototype, "taskOwnerId", 2);
244
- __decorateClass$2([field({
245
- type: "json",
246
- nullable: true
247
- })], SmrtJob.prototype, "taskResult", 2);
248
- __decorateClass$2([field({
249
- type: "json",
250
- nullable: true
251
- })], SmrtJob.prototype, "taskInputRequests", 2);
129
+ })], SmrtJobEvent.prototype, "stage", 2);
252
130
  __decorateClass$2([field({
253
- type: "json",
131
+ type: "integer",
254
132
  nullable: true
255
- })], SmrtJob.prototype, "taskInputResponses", 2);
256
- __decorateClass$2([field({ type: "json" })], SmrtJob.prototype, "retryStrategy", 2);
133
+ })], SmrtJobEvent.prototype, "progress", 2);
257
134
  __decorateClass$2([field({
258
135
  type: "text",
259
- nullable: true
260
- })], SmrtJob.prototype, "workerId", 2);
136
+ required: true,
137
+ default: ""
138
+ })], SmrtJobEvent.prototype, "message", 2);
139
+ __decorateClass$2([field({ type: "json" })], SmrtJobEvent.prototype, "data", 2);
261
140
  __decorateClass$2([field({
262
141
  type: "datetime",
263
- nullable: true
264
- })], SmrtJob.prototype, "workerHeartbeat", 2);
265
- SmrtJob = __decorateClass$2([smrt({
266
- tableName: "_smrt_jobs",
142
+ required: true
143
+ })], SmrtJobEvent.prototype, "createdAt", 2);
144
+ SmrtJobEvent = __decorateClass$2([smrt({
145
+ tableName: "_smrt_job_events",
267
146
  api: false,
268
147
  cli: {
269
- include: [
270
- "list",
271
- "get",
272
- "retry",
273
- "cancel"
274
- ],
275
- skipApiCheck: true,
276
- http: false
148
+ include: ["list", "get"],
149
+ http: false,
150
+ skipApiCheck: true
277
151
  },
278
- mcp: false,
279
- indexes: [{
280
- name: "_smrt_jobs_status_run_at_idx",
281
- columns: ["status", "runAt"]
282
- }, {
283
- name: "_smrt_jobs_ready_idx",
284
- columns: ["status", "runAt"],
285
- where: "status = 'pending' AND (task_input_requests IS NULL OR task_input_responses IS NOT NULL)"
286
- }]
287
- }), TenantScoped({ mode: "optional" })], SmrtJob);
288
- var SmrtJobCollection = class extends SmrtCollection {
289
- static _itemClass = SmrtJob;
290
- async initialize() {
291
- await super.initialize();
292
- await ensureJobsSystemTableCompatibility(this.db);
293
- return this;
294
- }
295
- /**
296
- * List jobs by status
297
- */
298
- async listByStatus(status, options = {}) {
299
- const where = { status: Array.isArray(status) ? status : [status] };
300
- if (options.queue) where.queue = options.queue;
301
- return this.list({
302
- where,
303
- orderBy: ["priority DESC", "run_at ASC"],
304
- limit: options.limit
305
- });
306
- }
307
- /**
308
- * List pending jobs ready to run
309
- */
310
- async listReady(options = {}) {
311
- const now = (/* @__PURE__ */ new Date()).toISOString();
312
- const whereConditions = [
313
- "status = 'pending'",
314
- "run_at <= ?",
315
- "(task_input_requests IS NULL OR task_input_responses IS NOT NULL)"
316
- ];
317
- const params = [now];
318
- if (options.queues?.length) {
319
- const placeholders = options.queues.map(() => "?").join(", ");
320
- whereConditions.push(`queue IN (${placeholders})`);
321
- params.push(...options.queues);
322
- }
323
- params.push(options.limit || 100);
324
- return this.query(`SELECT * FROM _smrt_jobs WHERE ${whereConditions.join(" AND ")} ORDER BY priority DESC, run_at ASC LIMIT ?`, params, { allowRawOnTenantScoped: true });
152
+ mcp: false
153
+ }), TenantScoped({ mode: "optional" })], SmrtJobEvent);
154
+ function normalizeLimit(limit) {
155
+ return Math.max(1, Math.min(1e3, Math.floor(typeof limit === "number" && Number.isFinite(limit) ? limit : 250)));
156
+ }
157
+ function normalizeProgress(progress) {
158
+ if (typeof progress !== "number" || !Number.isFinite(progress)) return null;
159
+ return Math.max(0, Math.min(100, Math.round(progress)));
160
+ }
161
+ function parseCursor(cursor) {
162
+ if (typeof cursor !== "string") return cursor;
163
+ const separator = cursor.lastIndexOf("|");
164
+ if (separator === -1) return {
165
+ createdAt: cursor,
166
+ id: ""
167
+ };
168
+ return {
169
+ createdAt: cursor.slice(0, separator),
170
+ id: cursor.slice(separator + 1)
171
+ };
172
+ }
173
+ function normalizeCursorDate(value) {
174
+ if (value instanceof Date) return value.toISOString();
175
+ const parsed = new Date(value);
176
+ if (!Number.isNaN(parsed.getTime())) return parsed.toISOString();
177
+ return value;
178
+ }
179
+ function databaseEngine(db) {
180
+ const configured = db;
181
+ const engine = detectEngine(db.url || configured.config?.url || "", configured.type || configured.config?.type);
182
+ const clientName = configured.client?.constructor?.name?.toLowerCase() ?? "";
183
+ if (engine === "sqlite" && (clientName.includes("duckdb") || configured.client?.connection !== void 0)) return "duckdb";
184
+ return engine;
185
+ }
186
+ function getQueryRows(result) {
187
+ if (Array.isArray(result)) return result;
188
+ return result.rows ?? [];
189
+ }
190
+ var SmrtJobEventCollection = class extends SmrtCollection {
191
+ static _itemClass = SmrtJobEvent;
192
+ async initialize() {
193
+ await super.initialize();
194
+ await ensureJobEventsSystemTableCompatibility(this.db);
195
+ return this;
325
196
  }
326
- /**
327
- * Atomically claim pending jobs ready to run for a worker.
328
- *
329
- * The claim is performed as one conditional UPDATE so concurrent workers
330
- * cannot receive the same pending row, and it never claims more than
331
- * `limit` rows. PostgreSQL additionally skips rows locked by other workers
332
- * instead of waiting behind them.
333
- *
334
- * On PostgreSQL the candidate set is selected once in a `MATERIALIZED` CTE
335
- * and the UPDATE joins to it. The previous `WHERE id IN (SELECT … LIMIT ?
336
- * FOR UPDATE SKIP LOCKED)` form let the planner choose a nested-loop semi
337
- * join that rescans the subquery for every outer pending row; each rescan
338
- * skipped the row the statement had just updated and returned the next one,
339
- * so a single claim could take the whole backlog and a `concurrency: 1`
340
- * runner started every ready job at once (#3145).
341
- */
342
- async claimReady(options) {
343
- const limit = options.limit ?? 100;
344
- if (limit <= 0) return [];
345
- const nowIso = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
346
- const whereConditions = [
347
- "status = 'pending'",
348
- "run_at <= ?",
349
- "(task_input_requests IS NULL OR task_input_responses IS NOT NULL)"
350
- ];
351
- const whereParams = [nowIso];
352
- if (options.queues?.length) {
353
- const placeholders = options.queues.map(() => "?").join(", ");
354
- whereConditions.push(`queue IN (${placeholders})`);
355
- whereParams.push(...options.queues);
197
+ async append(input) {
198
+ return this.create({
199
+ tenantId: input.tenantId,
200
+ jobId: input.jobId,
201
+ type: input.type ?? "log",
202
+ level: input.level ?? "info",
203
+ stage: input.stage ?? null,
204
+ progress: normalizeProgress(input.progress),
205
+ message: input.message ?? "",
206
+ data: input.data ?? {},
207
+ createdAt: input.createdAt ?? /* @__PURE__ */ new Date()
208
+ });
209
+ }
210
+ async listByJob(jobId, options = {}) {
211
+ return this.listSinceCursor({
212
+ ...options,
213
+ jobId
214
+ });
215
+ }
216
+ async listSinceCursor(options = {}) {
217
+ const where = [];
218
+ const params = [];
219
+ if (options.jobId) {
220
+ where.push("job_id = ?");
221
+ params.push(options.jobId);
356
222
  }
357
- const isPostgres = getDatabaseEngine(this.db) === "postgres";
358
- const candidateSelect = `
359
- SELECT id AS claim_id
360
- FROM _smrt_jobs
361
- WHERE ${whereConditions.join(" AND ")}
362
- ORDER BY priority DESC, run_at ASC, created_at ASC, id ASC
363
- LIMIT ?${isPostgres ? " FOR UPDATE SKIP LOCKED" : ""}
364
- `;
365
- const setClause = `
366
- SET status = 'running',
367
- worker_id = ?,
368
- worker_heartbeat = ?,
369
- started_at = ?,
370
- attempts = attempts + 1,
371
- updated_at = ?`;
372
- const setParams = [
373
- options.workerId,
374
- nowIso,
375
- nowIso,
376
- nowIso
377
- ];
378
- return (isPostgres ? await this.query(`WITH claim_candidates AS MATERIALIZED (${candidateSelect})
379
- UPDATE _smrt_jobs${setClause}
380
- FROM claim_candidates
381
- WHERE _smrt_jobs.id = claim_candidates.claim_id
382
- AND _smrt_jobs.status = 'pending'
383
- RETURNING ${SMRT_JOB_PORTABLE_SELECT_COLUMNS}`, [
384
- ...whereParams,
385
- limit,
386
- ...setParams
387
- ], { allowRawOnTenantScoped: true }) : await this.query(`UPDATE _smrt_jobs${setClause}
388
- WHERE id IN (${candidateSelect})
389
- AND status = 'pending'
390
- RETURNING ${SMRT_JOB_PORTABLE_SELECT_COLUMNS}`, [
391
- ...setParams,
392
- ...whereParams,
393
- limit
394
- ], { allowRawOnTenantScoped: true })).toSorted(compareClaimOrder);
223
+ this.addTenantPredicate(where, params, options);
224
+ if (options.cursor) {
225
+ const cursor = parseCursor(options.cursor);
226
+ const createdAt = await this.resolveCursorCreatedAt(cursor, options);
227
+ const createdAtExpression = this.createdAtComparableExpression();
228
+ where.push(`(${createdAtExpression} > ? OR (${createdAtExpression} = ? AND id > ?))`);
229
+ params.push(createdAt, createdAt, cursor.id);
230
+ } else if (options.since) {
231
+ where.push(`${this.createdAtComparableExpression()} > ?`);
232
+ params.push(normalizeCursorDate(options.since));
233
+ }
234
+ if (options.afterId) {
235
+ where.push("id > ?");
236
+ params.push(options.afterId);
237
+ }
238
+ params.push(normalizeLimit(options.limit));
239
+ const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
240
+ return this.query(`SELECT ${JOB_EVENT_STORAGE_COLUMNS}
241
+ FROM _smrt_job_events
242
+ ${whereSql}
243
+ ORDER BY ${this.createdAtComparableExpression()} ASC, id ASC
244
+ LIMIT ?`, params, { allowRawOnTenantScoped: true });
395
245
  }
396
- /**
397
- * Count non-terminal (pending/running) jobs owned by a tenant.
398
- *
399
- * Used to enforce the per-tenant creation cap so one tenant cannot exhaust
400
- * the shared worker pool (S5 audit #1402). Reads `_smrt_jobs` directly so it
401
- * works regardless of ambient tenant context.
402
- *
403
- * @param tenantId - Tenant to count for. `null` counts global (NULL-tenant)
404
- * jobs.
405
- */
406
- async countInFlightForTenant(tenantId2) {
407
- const predicate = tenantId2 === null ? "tenant_id IS NULL" : "tenant_id = ?";
408
- const params = tenantId2 === null ? [] : [tenantId2];
409
- const row = (await this.db.query(`SELECT COUNT(*) AS count
410
- FROM _smrt_jobs
411
- WHERE status IN ('pending', 'running')
412
- AND ${predicate}`, ...params)).rows[0];
413
- return Number(row?.count ?? 0);
246
+ async latestProgressByJobIds(jobIds, options = {}) {
247
+ const uniqueJobIds = [...new Set(jobIds.filter(Boolean))];
248
+ const latestByJobId = /* @__PURE__ */ new Map();
249
+ if (uniqueJobIds.length === 0) return latestByJobId;
250
+ const where = [`job_id IN (${uniqueJobIds.map(() => "?").join(", ")})`, "type = 'progress'"];
251
+ const params = [...uniqueJobIds];
252
+ this.addTenantPredicate(where, params, options);
253
+ const createdAtExpression = this.createdAtComparableExpression();
254
+ const events = await this.query(`SELECT ${JOB_EVENT_STORAGE_COLUMNS}
255
+ FROM (
256
+ SELECT ${JOB_EVENT_STORAGE_COLUMNS},
257
+ ${createdAtExpression} AS smrt_created_at_sort,
258
+ ROW_NUMBER() OVER (
259
+ PARTITION BY job_id
260
+ ORDER BY ${createdAtExpression} DESC, id DESC
261
+ ) AS smrt_rank
262
+ FROM _smrt_job_events
263
+ WHERE ${where.join(" AND ")}
264
+ ) ranked
265
+ WHERE smrt_rank = 1
266
+ ORDER BY smrt_created_at_sort DESC, id DESC`, params, { allowRawOnTenantScoped: true });
267
+ for (const event of events) latestByJobId.set(event.jobId, event);
268
+ return latestByJobId;
414
269
  }
415
270
  /**
416
- * The single creation path for queued jobs.
271
+ * Read the newest safe, authoritative terminal outcomes for one tenant.
417
272
  *
418
- * Centralizes the two creation-time security guards from the S5 audit (#1402)
419
- * so every enqueue — the fluent {@link "./job-builder".JobBuilder} *and* the
420
- * ScheduleRunner's cron-triggered jobs — goes through one place:
421
- *
422
- * 1. `maxAttempts` is clamped to {@link MAX_JOB_RETRIES} so a misconfigured
423
- * caller cannot pin a worker on a poison job indefinitely.
424
- * 2. A per-tenant in-flight cap bounds how many non-terminal jobs one tenant
425
- * may hold, so one tenant cannot exhaust the shared worker pool
426
- * (cross-tenant denial of service). The cap applies to the row's effective
427
- * tenant (explicit `data.tenantId` or, when absent, the ambient context);
428
- * global (null-tenant) jobs are exempt.
429
- *
430
- * Atomicity note (best-effort soft cap, by design): the cap is a
431
- * count-then-insert, NOT a hard transactional invariant. It is intentionally
432
- * left non-atomic. A plain transaction would not help — under the adapters'
433
- * default isolation two concurrent same-tenant enqueues would each read the
434
- * same COUNT and both insert, so serializing them would require either a
435
- * per-tenant lock row (`SELECT ... FOR UPDATE`) or SERIALIZABLE-isolation
436
- * retry loops. That cross-process locking is fragile (lock-row contention,
437
- * adapter-specific isolation behavior, the `transaction` adapter method being
438
- * optional) and out of proportion to the threat: this cap is defense in depth
439
- * against runaway/accidental creation exhausting the shared worker pool, not a
440
- * billing/quota boundary. So under truly simultaneous enqueues a tenant may
441
- * momentarily overshoot by the number of in-flight creators; the bound still
442
- * prevents unbounded growth and closes the prior ScheduleRunner bypass. If a
443
- * hard guarantee is ever needed, enforce it with a DB CHECK/trigger or a
444
- * dedicated counter row, not an application-level lock.
445
- */
446
- async enqueueJob(data, options = {}) {
447
- const cap = options.tenantJobCap ?? 1e4;
448
- const explicitTenant = typeof data.tenantId === "string" && data.tenantId.length > 0 ? data.tenantId : data.tenantId === null ? null : void 0;
449
- const effectiveTenant = explicitTenant !== void 0 ? explicitTenant : getTenantId() ?? null;
450
- if (effectiveTenant && cap > 0) assertWithinTenantCreationCap(effectiveTenant, await this.countInFlightForTenant(effectiveTenant), cap);
451
- const job = await this.create({
452
- ...data,
453
- maxAttempts: clampRetries(data.maxAttempts ?? 3)
454
- });
455
- await job.save();
456
- return job;
457
- }
458
- /**
459
- * Get job statistics
273
+ * The projection is built only from versioned terminal events written in the
274
+ * same transaction as their job status. It never returns job arguments,
275
+ * object ids, results, raw errors, or stack traces. Events follow the package
276
+ * retention policy (30 days by default), independently of job-row cleanup.
460
277
  */
461
- async stats(queue) {
462
- const query = queue ? "SELECT status, COUNT(*) as count FROM _smrt_jobs WHERE queue = ? GROUP BY status" : "SELECT status, COUNT(*) as count FROM _smrt_jobs GROUP BY status";
463
- const params = queue ? [queue] : [];
464
- const result = await this._db.query(query, ...params);
465
- const counts = {};
466
- for (const row of result.rows) counts[row.status] = row.count;
278
+ async listTerminalOutcomes(options) {
279
+ if (!Object.hasOwn(options, "tenantId") || options.tenantId === void 0) throw new Error("Terminal outcome queries require tenantId or tenantId: null");
280
+ const limit = normalizeLimit(options.limit ?? 100);
281
+ const where = ["stage IN ('completed', 'failed', 'cancelled', 'stale-recovery')"];
282
+ const params = [];
283
+ this.addTenantPredicate(where, params, options);
284
+ if (options.statuses?.length) {
285
+ const stages = options.statuses.flatMap((status) => status === "failed" ? ["failed", "stale-recovery"] : [status]);
286
+ where.push(`stage IN (${stages.map(() => "?").join(", ")})`);
287
+ params.push(...stages);
288
+ }
289
+ if (options.since) {
290
+ where.push(`${this.createdAtComparableExpression()} > ?`);
291
+ params.push(normalizeCursorDate(options.since));
292
+ }
293
+ if (options.before) {
294
+ const cursor = parseCursor(options.before);
295
+ const createdAt = await this.resolveCursorCreatedAt(cursor, options);
296
+ const expression2 = this.createdAtComparableExpression();
297
+ where.push(`(${expression2} < ? OR (${expression2} = ? AND id < ?))`);
298
+ params.push(createdAt, createdAt, cursor.id);
299
+ }
300
+ params.push(TERMINAL_OUTCOME_CANDIDATE_LIMIT);
301
+ const expression = this.createdAtComparableExpression();
302
+ const candidates = await this.query(`SELECT ${JOB_EVENT_STORAGE_COLUMNS}
303
+ FROM _smrt_job_events
304
+ WHERE ${where.join(" AND ")}
305
+ ORDER BY ${expression} DESC, id DESC
306
+ LIMIT ?`, params, { allowRawOnTenantScoped: true });
307
+ const outcomes = [];
308
+ let scanned;
309
+ for (const event of candidates) {
310
+ scanned = event;
311
+ const outcome = terminalOutcome(event);
312
+ if (!outcome || !matchesTerminalOutcome(outcome, options)) continue;
313
+ outcomes.push(outcome);
314
+ if (outcomes.length === limit) break;
315
+ }
316
+ const stoppedAtLimit = outcomes.length === limit;
317
+ const candidateBoundReached = candidates.length === TERMINAL_OUTCOME_CANDIDATE_LIMIT;
467
318
  return {
468
- pending: counts.pending ?? 0,
469
- running: counts.running ?? 0,
470
- completed: counts.completed ?? 0,
471
- failed: counts.failed ?? 0,
472
- cancelled: counts.cancelled ?? 0
319
+ outcomes,
320
+ limit,
321
+ candidateLimit: TERMINAL_OUTCOME_CANDIDATE_LIMIT,
322
+ truncated: stoppedAtLimit || candidateBoundReached,
323
+ nextCursor: (stoppedAtLimit || candidateBoundReached) && scanned ? scanned.toCursor() : null
473
324
  };
474
325
  }
326
+ addTenantPredicate(where, params, options) {
327
+ if (options.tenantId === null) {
328
+ where.push("tenant_id IS NULL");
329
+ return;
330
+ }
331
+ const tenantId2 = typeof options.tenantId === "string" ? options.tenantId : getTenantId();
332
+ if (tenantId2) {
333
+ where.push("tenant_id = ?");
334
+ params.push(tenantId2);
335
+ return;
336
+ }
337
+ throw new Error("Tenant-scoped job event queries require tenantId, tenantId: null, or an ambient tenant context.");
338
+ }
475
339
  /**
476
- * Cleanup old completed/failed jobs.
340
+ * Delete job events recorded before a cutoff (#2375).
477
341
  *
478
- * Predicates are `(status, completed_at)` pairs, covered by
479
- * `idx_smrt_jobs_status_completed_at` (created by the jobs compatibility
480
- * path this collection runs on initialize — #2375).
342
+ * `_smrt_job_events` is append-only — every log line, progress tick and
343
+ * lifecycle transition of every job — so it outgrows `_smrt_jobs` by an
344
+ * order of magnitude and had no prune path at all. Events are pruned on
345
+ * their own clock rather than by following job deletion: a long-running job
346
+ * accumulates events for as long as it runs.
481
347
  *
482
- * @param options.dryRun - Count the jobs the cutoffs select without deleting
483
- * them, so a retention preview reports exactly what a real run removes.
484
- * @returns Number of jobs deleted (or, under `dryRun`, matched).
348
+ * The cutoff uses the same engine-normalized `created_at` expression the
349
+ * cursor reads use. On PostgreSQL that is the bare column, so
350
+ * `idx_smrt_job_events_created_at` serves it; on SQLite the `strftime()`
351
+ * wrapper makes the comparison non-sargable and the prune is a scan. That is
352
+ * acceptable for a periodic maintenance pass and is preferable to comparing
353
+ * two differently-formatted timestamps, which is what the wrapper exists to
354
+ * prevent.
355
+ *
356
+ * @param options.before - Delete events created strictly before this time.
357
+ * @param options.dryRun - Count matching events without deleting them.
358
+ * @returns Number of events deleted (or, under `dryRun`, matched).
485
359
  */
486
360
  async cleanup(options) {
487
- const conditions = [];
488
- const params = [];
489
- if (options.completedBefore) {
490
- conditions.push("(status = 'completed' AND completed_at < ?)");
491
- params.push(options.completedBefore.toISOString());
492
- }
493
- if (options.failedBefore) {
494
- conditions.push("(status = 'failed' AND completed_at < ?)");
495
- params.push(options.failedBefore.toISOString());
496
- }
497
- if (options.cancelledBefore) {
498
- conditions.push("(status = 'cancelled' AND completed_at < ?)");
499
- params.push(options.cancelledBefore.toISOString());
500
- }
501
- if (conditions.length === 0) return 0;
502
- const selector = options.limit ? `id IN (
503
- SELECT id FROM _smrt_jobs
504
- WHERE (${conditions.join(" OR ")})
505
- LIMIT ?
506
- )` : `(${conditions.join(" OR ")})`;
507
- const selectorParams = options.limit ? [...params, options.limit] : [...params];
508
- const counted = await this._db.query(`SELECT COUNT(*) AS total FROM _smrt_jobs WHERE ${selector}`, ...selectorParams);
361
+ const where = `${this.createdAtComparableExpression()} < ?`;
362
+ const cutoff = options.before.toISOString();
363
+ const counted = await this.db.query(`SELECT COUNT(*) AS total FROM _smrt_job_events WHERE ${where}`, cutoff);
509
364
  const total = Number(counted.rows?.[0]?.total ?? 0);
510
365
  if (!Number.isFinite(total) || total <= 0) return 0;
511
- if (!options.dryRun) await this._db.query(`DELETE FROM _smrt_jobs WHERE ${selector}`, ...selectorParams);
366
+ if (!options.dryRun) await this.db.query(`DELETE FROM _smrt_job_events WHERE ${where}`, cutoff);
512
367
  return total;
513
368
  }
369
+ createdAtComparableExpression() {
370
+ const engine = databaseEngine(this.db);
371
+ if (engine === "sqlite") return "strftime('%Y-%m-%dT%H:%M:%fZ', created_at)";
372
+ if (engine === "duckdb") return "strftime(created_at, '%Y-%m-%dT%H:%M:%S.%gZ')";
373
+ return "created_at";
374
+ }
375
+ async resolveCursorCreatedAt(cursor, options) {
376
+ if (!cursor.id) return normalizeCursorDate(cursor.createdAt);
377
+ const where = ["id = ?"];
378
+ const params = [cursor.id];
379
+ if (options.jobId) {
380
+ where.push("job_id = ?");
381
+ params.push(options.jobId);
382
+ }
383
+ this.addTenantPredicate(where, params, options);
384
+ const cursorCreatedAt = getQueryRows(await this.db.query(`SELECT ${this.createdAtComparableExpression()} AS cursor_created_at
385
+ FROM _smrt_job_events
386
+ WHERE ${where.join(" AND ")}
387
+ LIMIT 1`, ...params))[0]?.cursor_created_at;
388
+ return typeof cursorCreatedAt === "string" && cursorCreatedAt.trim() ? cursorCreatedAt : normalizeCursorDate(cursor.createdAt);
389
+ }
514
390
  };
515
- function getDatabaseEngine(db) {
516
- const dbWithConfig = db;
517
- return detectEngine(db.url || dbWithConfig.config?.url || "", dbWithConfig.type || dbWithConfig.config?.type);
391
+ function terminalOutcome(event) {
392
+ const data = event.data;
393
+ if (data?.version !== 1 || data.terminal !== true || !isTerminalStatus(data.status) || !matchesTerminalStage(data.status, data.failureKind, event.stage) || typeof data.queue !== "string" || typeof data.objectType !== "string" || typeof data.method !== "string" || typeof data.attempts !== "number" || !Number.isSafeInteger(data.attempts) || data.attempts < 0 || typeof data.completedAt !== "string" || Number.isNaN(Date.parse(data.completedAt)) || (data.status === "failed" ? !isFailureKind(data.failureKind) : data.failureKind !== void 0) || !event.id) return null;
394
+ const failureKind = data.status === "failed" ? data.failureKind : void 0;
395
+ return {
396
+ eventId: event.id,
397
+ jobId: event.jobId,
398
+ tenantId: event.tenantId ?? null,
399
+ status: data.status,
400
+ queue: data.queue,
401
+ objectType: data.objectType,
402
+ method: data.method,
403
+ attempts: data.attempts,
404
+ ...failureKind ? { failureKind } : {},
405
+ completedAt: new Date(data.completedAt).toISOString(),
406
+ cursor: event.toCursor()
407
+ };
518
408
  }
519
- function compareClaimOrder(left, right) {
520
- const priority = right.priority - left.priority;
521
- if (priority !== 0) return priority;
522
- const runAt = left.runAt.getTime() - right.runAt.getTime();
523
- if (runAt !== 0) return runAt;
524
- const createdAt = timestamp(left.created_at) - timestamp(right.created_at);
525
- if (createdAt !== 0) return createdAt;
526
- return (left.id ?? "").localeCompare(right.id ?? "");
409
+ function isFailureKind(value) {
410
+ return value === "execution" || value === "timeout" || value === "stale-recovery";
527
411
  }
528
- function timestamp(value) {
529
- return value?.getTime() ?? 0;
412
+ function isTerminalStatus(value) {
413
+ return value === "completed" || value === "failed" || value === "cancelled";
414
+ }
415
+ function matchesTerminalStage(status, failureKind, stage) {
416
+ return stage === status || status === "failed" && failureKind === "stale-recovery" && stage === "stale-recovery";
417
+ }
418
+ function matchesTerminalOutcome(outcome, options) {
419
+ return (!options.queues?.length || options.queues.includes(outcome.queue)) && (!options.objectTypes?.length || options.objectTypes.includes(outcome.objectType)) && (!options.methods?.length || options.methods.includes(outcome.method));
530
420
  }
531
421
  //#endregion
532
- //#region src/smrt-job-event.ts
422
+ //#region src/terminal-outcome.ts
423
+ var terminalStorageReady = /* @__PURE__ */ new WeakMap();
424
+ async function ensureTerminalOutcomeStorage(db) {
425
+ const key = db;
426
+ const existing = terminalStorageReady.get(key);
427
+ if (existing) return existing;
428
+ const pending = (async () => {
429
+ await ensureSchema(db, "SmrtJobEvent");
430
+ await ensureJobEventsSystemTableCompatibility(db);
431
+ })();
432
+ terminalStorageReady.set(key, pending);
433
+ try {
434
+ await pending;
435
+ } catch (error) {
436
+ terminalStorageReady.delete(key);
437
+ throw error;
438
+ }
439
+ }
440
+ async function transitionTerminalJob(db, options) {
441
+ if (!db.transaction) throw new Error("Terminal job transitions require transaction support");
442
+ if (options.expectedStatuses.length === 0) throw new Error("Terminal job transitions require an expected status");
443
+ await ensureTerminalOutcomeStorage(db);
444
+ const persisted = await db.transaction(async (tx) => {
445
+ const assignments = [
446
+ ["status", options.status],
447
+ ["completed_at", options.completedAt.toISOString()],
448
+ ["updated_at", options.completedAt.toISOString()]
449
+ ];
450
+ if (options.clearWorker) assignments.push(["worker_id", null], ["worker_heartbeat", null]);
451
+ if (options.lastError !== void 0) assignments.push(["last_error", options.lastError]);
452
+ if (options.resultPointer !== void 0) assignments.push(["result_pointer", options.resultPointer]);
453
+ if (options.taskResult !== void 0) assignments.push(["task_result", options.taskResult === null ? null : JSON.stringify(options.taskResult)]);
454
+ const where = ["id = ?"];
455
+ const whereValues = [options.job.id];
456
+ if (options.job.tenantId === null) where.push("tenant_id IS NULL");
457
+ else {
458
+ where.push("tenant_id = ?");
459
+ whereValues.push(options.job.tenantId);
460
+ }
461
+ where.push(`status IN (${options.expectedStatuses.map(() => "?").join(", ")})`);
462
+ whereValues.push(...options.expectedStatuses);
463
+ if (options.expectedWorkerId === null) where.push("worker_id IS NULL");
464
+ else if (options.expectedWorkerId !== void 0) {
465
+ where.push("worker_id = ?");
466
+ whereValues.push(options.expectedWorkerId);
467
+ }
468
+ if (options.expectedTaskId !== void 0) {
469
+ where.push("task_id = ?");
470
+ whereValues.push(options.expectedTaskId);
471
+ }
472
+ const updated = await tx.query(`UPDATE _smrt_jobs
473
+ SET ${assignments.map(([column]) => `${column} = ?`).join(", ")}
474
+ WHERE ${where.join(" AND ")}
475
+ RETURNING CAST(tenant_id AS VARCHAR) AS tenant_id,
476
+ queue, object_type, method, attempts`, ...assignments.map(([, value]) => value), ...whereValues);
477
+ if (updated.rows.length !== 1) return null;
478
+ const authoritative = updated.rows[0];
479
+ const attempts = Number(authoritative.attempts);
480
+ if (!Number.isSafeInteger(attempts) || attempts < 0) throw new Error("Terminal job transition returned invalid attempts");
481
+ const stage = options.status === "failed" && options.failureKind === "stale-recovery" ? "stale-recovery" : options.status;
482
+ const id = randomUUID();
483
+ const type = options.status === "completed" ? "progress" : options.status === "failed" ? "error" : "status";
484
+ const level = options.status === "failed" ? "error" : "info";
485
+ const progress = options.status === "completed" ? 100 : null;
486
+ const message = `Job ${options.status}`;
487
+ const data = {
488
+ version: 1,
489
+ terminal: true,
490
+ status: options.status,
491
+ queue: authoritative.queue,
492
+ objectType: authoritative.object_type,
493
+ method: authoritative.method,
494
+ attempts,
495
+ completedAt: options.completedAt.toISOString(),
496
+ ...options.status === "failed" && options.failureKind ? { failureKind: options.failureKind } : {}
497
+ };
498
+ const timestamp = options.completedAt.toISOString();
499
+ await tx.query(`INSERT INTO _smrt_job_events
500
+ (id, slug, context, created_at, updated_at, tenant_id, job_id,
501
+ type, level, stage, progress, message, data)
502
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, id, id, "", timestamp, timestamp, authoritative.tenant_id, options.job.id, type, level, stage, progress, message, JSON.stringify(data));
503
+ return {
504
+ id,
505
+ slug: id,
506
+ context: "",
507
+ created_at: timestamp,
508
+ updated_at: timestamp,
509
+ tenant_id: authoritative.tenant_id,
510
+ job_id: options.job.id,
511
+ type,
512
+ level,
513
+ stage,
514
+ progress,
515
+ message,
516
+ data
517
+ };
518
+ });
519
+ if (!persisted) return null;
520
+ const event = new SmrtJobEvent({ db });
521
+ await event.initialize();
522
+ await event.loadDataFromDb(persisted);
523
+ return event;
524
+ }
525
+ function terminalSnapshot(job) {
526
+ if (!job.id) return null;
527
+ return {
528
+ id: job.id,
529
+ tenantId: job.tenantId ?? null,
530
+ queue: job.queue,
531
+ objectType: job.objectType,
532
+ method: job.method,
533
+ attempts: job.attempts
534
+ };
535
+ }
536
+ //#endregion
537
+ //#region src/smrt-job.ts
533
538
  var __defProp$1 = Object.defineProperty;
534
539
  var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
535
540
  var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, {
@@ -545,255 +550,476 @@ var __decorateClass$1 = (decorators, target, key, kind) => {
545
550
  return result;
546
551
  };
547
552
  var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
548
- var JOB_EVENT_STORAGE_COLUMNS = [
549
- "id",
550
- "slug",
551
- "context",
552
- "created_at",
553
- "updated_at",
554
- "tenant_id",
555
- "job_id",
556
- "type",
557
- "level",
558
- "stage",
559
- "progress",
560
- "message",
561
- "data"
562
- ].join(", ");
563
- var SmrtJobEvent = class extends SmrtObject {
553
+ var SMRT_JOB_PORTABLE_SELECT_COLUMNS = `
554
+ CAST(id AS VARCHAR) AS id,
555
+ slug, context, created_at, updated_at, tenant_id, queue,
556
+ object_type, object_id, method, args, run_at,
557
+ priority, status, attempts, max_attempts, timeout,
558
+ timeout_behavior, started_at, completed_at, last_error,
559
+ result_pointer, task_id, task_owner_id, task_result,
560
+ task_input_requests, task_input_responses, retry_strategy,
561
+ worker_id, worker_heartbeat
562
+ `;
563
+ var SmrtJob = class extends SmrtObject {
564
564
  tenantId = void 0;
565
- jobId = "";
566
- type = "log";
567
- level = "info";
568
- stage = null;
569
- progress = null;
570
- message = "";
571
- data = {};
572
- createdAt = /* @__PURE__ */ new Date();
573
- toCursor() {
574
- return `${this.createdAt instanceof Date ? this.createdAt.toISOString() : String(this.createdAt)}|${this.id ?? ""}`;
565
+ queue = "default";
566
+ objectType = "";
567
+ objectId = null;
568
+ method = "";
569
+ args = {};
570
+ runAt = /* @__PURE__ */ new Date();
571
+ priority = 50;
572
+ status = "pending";
573
+ attempts = 0;
574
+ maxAttempts = 3;
575
+ timeout = 3e5;
576
+ timeoutBehavior = "fail";
577
+ startedAt = null;
578
+ completedAt = null;
579
+ lastError = null;
580
+ resultPointer = null;
581
+ taskId = null;
582
+ taskOwnerId = null;
583
+ taskResult = null;
584
+ taskInputRequests = null;
585
+ taskInputResponses = null;
586
+ retryStrategy = {
587
+ type: "exponential",
588
+ config: {
589
+ initialDelay: 1e3,
590
+ multiplier: 2,
591
+ maxDelay: 3e5
592
+ }
593
+ };
594
+ workerId = null;
595
+ workerHeartbeat = null;
596
+ /**
597
+ * Capture ambient tenant context when a job is saved inside withTenant().
598
+ *
599
+ * Scheduled jobs can also set this explicitly from their owning schedule.
600
+ */
601
+ async save() {
602
+ if (this.tenantId === void 0) {
603
+ const contextTenantId = getTenantId();
604
+ if (contextTenantId) this.tenantId = contextTenantId;
605
+ }
606
+ return super.save();
607
+ }
608
+ /**
609
+ * Mark the job for retry
610
+ */
611
+ async retry() {
612
+ if (this.status === "completed") throw new Error("Cannot retry a completed job");
613
+ this.status = "pending";
614
+ this.attempts = 0;
615
+ this.lastError = null;
616
+ this.startedAt = null;
617
+ this.completedAt = null;
618
+ this.workerId = null;
619
+ this.workerHeartbeat = null;
620
+ await this.save();
621
+ }
622
+ /**
623
+ * Cancel the job
624
+ */
625
+ async cancel() {
626
+ if (this.status === "completed" || this.status === "cancelled") throw new Error(`Cannot cancel job with status: ${this.status}`);
627
+ const snapshot = terminalSnapshot(this);
628
+ if (!snapshot) throw new Error("Cannot cancel an unpersisted job");
629
+ const contextTenantId = getTenantId();
630
+ if (contextTenantId && snapshot.tenantId !== contextTenantId && !isSystemContext() && !isSuperAdminBypass()) throw new TenantIsolationError(`Tenant isolation violation: cannot cancel SmrtJob with tenantId '${snapshot.tenantId}' in context of tenant '${contextTenantId}'`, {
631
+ tenantId: contextTenantId,
632
+ attemptedTenantId: snapshot.tenantId ?? "null"
633
+ });
634
+ const completedAt = /* @__PURE__ */ new Date();
635
+ if (!await transitionTerminalJob(this.db, {
636
+ job: snapshot,
637
+ status: "cancelled",
638
+ completedAt,
639
+ expectedStatuses: [
640
+ "pending",
641
+ "running",
642
+ "failed"
643
+ ]
644
+ })) throw new Error("Job status changed before cancellation");
645
+ this.status = "cancelled";
646
+ this.completedAt = completedAt;
647
+ }
648
+ /**
649
+ * Get a human-readable description of the job
650
+ */
651
+ getDescription() {
652
+ return `${this.objectId ? `${this.objectType}#${this.objectId}` : this.objectType}.${this.method}()`;
575
653
  }
576
654
  };
577
- __publicField$1(SmrtJobEvent, "__smrtPackage__", "@happyvertical/smrt-jobs");
578
- __decorateClass$1([tenantId({ nullable: true })], SmrtJobEvent.prototype, "tenantId", 2);
579
- __decorateClass$1([foreignKey("SmrtJob", {
655
+ __publicField$1(SmrtJob, "__smrtPackage__", "@happyvertical/smrt-jobs");
656
+ __decorateClass$1([tenantId({ nullable: true })], SmrtJob.prototype, "tenantId", 2);
657
+ __decorateClass$1([field({
658
+ type: "text",
659
+ required: true,
660
+ default: "default"
661
+ })], SmrtJob.prototype, "queue", 2);
662
+ __decorateClass$1([field({
663
+ type: "text",
664
+ required: true
665
+ })], SmrtJob.prototype, "objectType", 2);
666
+ __decorateClass$1([field({
667
+ type: "text",
668
+ nullable: true
669
+ })], SmrtJob.prototype, "objectId", 2);
670
+ __decorateClass$1([field({
671
+ type: "text",
672
+ required: true
673
+ })], SmrtJob.prototype, "method", 2);
674
+ __decorateClass$1([field({ type: "json" })], SmrtJob.prototype, "args", 2);
675
+ __decorateClass$1([field({
676
+ type: "datetime",
580
677
  required: true,
581
- constraint: false
582
- })], SmrtJobEvent.prototype, "jobId", 2);
678
+ indexed: true
679
+ })], SmrtJob.prototype, "runAt", 2);
680
+ __decorateClass$1([field({
681
+ type: "integer",
682
+ required: true,
683
+ default: 50
684
+ })], SmrtJob.prototype, "priority", 2);
583
685
  __decorateClass$1([field({
584
686
  type: "text",
585
687
  required: true,
586
- default: "log"
587
- })], SmrtJobEvent.prototype, "type", 2);
688
+ default: "pending",
689
+ indexed: true
690
+ })], SmrtJob.prototype, "status", 2);
691
+ __decorateClass$1([field({
692
+ type: "integer",
693
+ required: true,
694
+ default: 0
695
+ })], SmrtJob.prototype, "attempts", 2);
696
+ __decorateClass$1([field({
697
+ type: "integer",
698
+ required: true,
699
+ default: 3
700
+ })], SmrtJob.prototype, "maxAttempts", 2);
701
+ __decorateClass$1([field({
702
+ type: "integer",
703
+ required: true,
704
+ default: 3e5
705
+ })], SmrtJob.prototype, "timeout", 2);
588
706
  __decorateClass$1([field({
589
707
  type: "text",
590
708
  required: true,
591
- default: "info"
592
- })], SmrtJobEvent.prototype, "level", 2);
709
+ default: "fail"
710
+ })], SmrtJob.prototype, "timeoutBehavior", 2);
711
+ __decorateClass$1([field({
712
+ type: "datetime",
713
+ nullable: true
714
+ })], SmrtJob.prototype, "startedAt", 2);
715
+ __decorateClass$1([field({
716
+ type: "datetime",
717
+ nullable: true
718
+ })], SmrtJob.prototype, "completedAt", 2);
593
719
  __decorateClass$1([field({
594
720
  type: "text",
595
721
  nullable: true
596
- })], SmrtJobEvent.prototype, "stage", 2);
722
+ })], SmrtJob.prototype, "lastError", 2);
597
723
  __decorateClass$1([field({
598
- type: "integer",
724
+ type: "text",
599
725
  nullable: true
600
- })], SmrtJobEvent.prototype, "progress", 2);
726
+ })], SmrtJob.prototype, "resultPointer", 2);
601
727
  __decorateClass$1([field({
602
728
  type: "text",
603
- required: true,
604
- default: ""
605
- })], SmrtJobEvent.prototype, "message", 2);
606
- __decorateClass$1([field({ type: "json" })], SmrtJobEvent.prototype, "data", 2);
729
+ nullable: true,
730
+ unique: true
731
+ })], SmrtJob.prototype, "taskId", 2);
732
+ __decorateClass$1([field({
733
+ type: "text",
734
+ nullable: true
735
+ })], SmrtJob.prototype, "taskOwnerId", 2);
736
+ __decorateClass$1([field({
737
+ type: "json",
738
+ nullable: true
739
+ })], SmrtJob.prototype, "taskResult", 2);
740
+ __decorateClass$1([field({
741
+ type: "json",
742
+ nullable: true
743
+ })], SmrtJob.prototype, "taskInputRequests", 2);
744
+ __decorateClass$1([field({
745
+ type: "json",
746
+ nullable: true
747
+ })], SmrtJob.prototype, "taskInputResponses", 2);
748
+ __decorateClass$1([field({ type: "json" })], SmrtJob.prototype, "retryStrategy", 2);
749
+ __decorateClass$1([field({
750
+ type: "text",
751
+ nullable: true
752
+ })], SmrtJob.prototype, "workerId", 2);
607
753
  __decorateClass$1([field({
608
754
  type: "datetime",
609
- required: true
610
- })], SmrtJobEvent.prototype, "createdAt", 2);
611
- SmrtJobEvent = __decorateClass$1([smrt({
612
- tableName: "_smrt_job_events",
755
+ nullable: true
756
+ })], SmrtJob.prototype, "workerHeartbeat", 2);
757
+ SmrtJob = __decorateClass$1([smrt({
758
+ tableName: "_smrt_jobs",
613
759
  api: false,
614
760
  cli: {
615
- include: ["list", "get"],
616
- http: false,
617
- skipApiCheck: true
761
+ include: [
762
+ "list",
763
+ "get",
764
+ "retry",
765
+ "cancel"
766
+ ],
767
+ skipApiCheck: true,
768
+ http: false
618
769
  },
619
- mcp: false
620
- }), TenantScoped({ mode: "optional" })], SmrtJobEvent);
621
- function normalizeLimit(limit) {
622
- return Math.max(1, Math.min(1e3, Math.floor(typeof limit === "number" && Number.isFinite(limit) ? limit : 250)));
623
- }
624
- function normalizeProgress(progress) {
625
- if (typeof progress !== "number" || !Number.isFinite(progress)) return null;
626
- return Math.max(0, Math.min(100, Math.round(progress)));
627
- }
628
- function parseCursor(cursor) {
629
- if (typeof cursor !== "string") return cursor;
630
- const separator = cursor.lastIndexOf("|");
631
- if (separator === -1) return {
632
- createdAt: cursor,
633
- id: ""
634
- };
635
- return {
636
- createdAt: cursor.slice(0, separator),
637
- id: cursor.slice(separator + 1)
638
- };
639
- }
640
- function normalizeCursorDate(value) {
641
- if (value instanceof Date) return value.toISOString();
642
- const parsed = new Date(value);
643
- if (!Number.isNaN(parsed.getTime())) return parsed.toISOString();
644
- return value;
645
- }
646
- function usesSqliteDateFunctions(dbUrl) {
647
- const normalized = dbUrl.toLowerCase();
648
- return !(normalized.startsWith("postgres:") || normalized.startsWith("postgresql:"));
649
- }
650
- function getQueryRows(result) {
651
- if (Array.isArray(result)) return result;
652
- return result.rows ?? [];
653
- }
654
- var SmrtJobEventCollection = class extends SmrtCollection {
655
- static _itemClass = SmrtJobEvent;
770
+ mcp: false,
771
+ indexes: [{
772
+ name: "_smrt_jobs_status_run_at_idx",
773
+ columns: ["status", "runAt"]
774
+ }, {
775
+ name: "_smrt_jobs_ready_idx",
776
+ columns: ["status", "runAt"],
777
+ where: "status = 'pending' AND (task_input_requests IS NULL OR task_input_responses IS NOT NULL)"
778
+ }]
779
+ }), TenantScoped({ mode: "optional" })], SmrtJob);
780
+ var SmrtJobCollection = class extends SmrtCollection {
781
+ static _itemClass = SmrtJob;
656
782
  async initialize() {
657
783
  await super.initialize();
658
- await ensureJobEventsSystemTableCompatibility(this.db);
784
+ await ensureJobsSystemTableCompatibility(this.db);
659
785
  return this;
660
786
  }
661
- async append(input) {
662
- return this.create({
663
- tenantId: input.tenantId,
664
- jobId: input.jobId,
665
- type: input.type ?? "log",
666
- level: input.level ?? "info",
667
- stage: input.stage ?? null,
668
- progress: normalizeProgress(input.progress),
669
- message: input.message ?? "",
670
- data: input.data ?? {},
671
- createdAt: input.createdAt ?? /* @__PURE__ */ new Date()
787
+ /**
788
+ * List jobs by status
789
+ */
790
+ async listByStatus(status, options = {}) {
791
+ const where = { status: Array.isArray(status) ? status : [status] };
792
+ if (options.queue) where.queue = options.queue;
793
+ return this.list({
794
+ where,
795
+ orderBy: ["priority DESC", "run_at ASC"],
796
+ limit: options.limit
672
797
  });
673
798
  }
674
- async listByJob(jobId, options = {}) {
675
- return this.listSinceCursor({
676
- ...options,
677
- jobId
799
+ /**
800
+ * List pending jobs ready to run
801
+ */
802
+ async listReady(options = {}) {
803
+ const now = (/* @__PURE__ */ new Date()).toISOString();
804
+ const whereConditions = [
805
+ "status = 'pending'",
806
+ "run_at <= ?",
807
+ "(task_input_requests IS NULL OR task_input_responses IS NOT NULL)"
808
+ ];
809
+ const params = [now];
810
+ if (options.queues?.length) {
811
+ const placeholders = options.queues.map(() => "?").join(", ");
812
+ whereConditions.push(`queue IN (${placeholders})`);
813
+ params.push(...options.queues);
814
+ }
815
+ params.push(options.limit || 100);
816
+ return this.query(`SELECT * FROM _smrt_jobs WHERE ${whereConditions.join(" AND ")} ORDER BY priority DESC, run_at ASC LIMIT ?`, params, { allowRawOnTenantScoped: true });
817
+ }
818
+ /**
819
+ * Atomically claim pending jobs ready to run for a worker.
820
+ *
821
+ * The claim is performed as one conditional UPDATE so concurrent workers
822
+ * cannot receive the same pending row, and it never claims more than
823
+ * `limit` rows. PostgreSQL additionally skips rows locked by other workers
824
+ * instead of waiting behind them.
825
+ *
826
+ * On PostgreSQL the candidate set is selected once in a `MATERIALIZED` CTE
827
+ * and the UPDATE joins to it. The previous `WHERE id IN (SELECT … LIMIT ?
828
+ * FOR UPDATE SKIP LOCKED)` form let the planner choose a nested-loop semi
829
+ * join that rescans the subquery for every outer pending row; each rescan
830
+ * skipped the row the statement had just updated and returned the next one,
831
+ * so a single claim could take the whole backlog and a `concurrency: 1`
832
+ * runner started every ready job at once (#3145).
833
+ */
834
+ async claimReady(options) {
835
+ const limit = options.limit ?? 100;
836
+ if (limit <= 0) return [];
837
+ const nowIso = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
838
+ const whereConditions = [
839
+ "status = 'pending'",
840
+ "run_at <= ?",
841
+ "(task_input_requests IS NULL OR task_input_responses IS NOT NULL)"
842
+ ];
843
+ const whereParams = [nowIso];
844
+ if (options.queues?.length) {
845
+ const placeholders = options.queues.map(() => "?").join(", ");
846
+ whereConditions.push(`queue IN (${placeholders})`);
847
+ whereParams.push(...options.queues);
848
+ }
849
+ const isPostgres = getDatabaseEngine(this.db) === "postgres";
850
+ const candidateSelect = `
851
+ SELECT id AS claim_id
852
+ FROM _smrt_jobs
853
+ WHERE ${whereConditions.join(" AND ")}
854
+ ORDER BY priority DESC, run_at ASC, created_at ASC, id ASC
855
+ LIMIT ?${isPostgres ? " FOR UPDATE SKIP LOCKED" : ""}
856
+ `;
857
+ const setClause = `
858
+ SET status = 'running',
859
+ worker_id = ?,
860
+ worker_heartbeat = ?,
861
+ started_at = ?,
862
+ attempts = attempts + 1,
863
+ updated_at = ?`;
864
+ const setParams = [
865
+ options.workerId,
866
+ nowIso,
867
+ nowIso,
868
+ nowIso
869
+ ];
870
+ return (isPostgres ? await this.query(`WITH claim_candidates AS MATERIALIZED (${candidateSelect})
871
+ UPDATE _smrt_jobs${setClause}
872
+ FROM claim_candidates
873
+ WHERE _smrt_jobs.id = claim_candidates.claim_id
874
+ AND _smrt_jobs.status = 'pending'
875
+ RETURNING ${SMRT_JOB_PORTABLE_SELECT_COLUMNS}`, [
876
+ ...whereParams,
877
+ limit,
878
+ ...setParams
879
+ ], { allowRawOnTenantScoped: true }) : await this.query(`UPDATE _smrt_jobs${setClause}
880
+ WHERE id IN (${candidateSelect})
881
+ AND status = 'pending'
882
+ RETURNING ${SMRT_JOB_PORTABLE_SELECT_COLUMNS}`, [
883
+ ...setParams,
884
+ ...whereParams,
885
+ limit
886
+ ], { allowRawOnTenantScoped: true })).toSorted(compareClaimOrder);
887
+ }
888
+ /**
889
+ * Count non-terminal (pending/running) jobs owned by a tenant.
890
+ *
891
+ * Used to enforce the per-tenant creation cap so one tenant cannot exhaust
892
+ * the shared worker pool (S5 audit #1402). Reads `_smrt_jobs` directly so it
893
+ * works regardless of ambient tenant context.
894
+ *
895
+ * @param tenantId - Tenant to count for. `null` counts global (NULL-tenant)
896
+ * jobs.
897
+ */
898
+ async countInFlightForTenant(tenantId2) {
899
+ const predicate = tenantId2 === null ? "tenant_id IS NULL" : "tenant_id = ?";
900
+ const params = tenantId2 === null ? [] : [tenantId2];
901
+ const row = (await this.db.query(`SELECT COUNT(*) AS count
902
+ FROM _smrt_jobs
903
+ WHERE status IN ('pending', 'running')
904
+ AND ${predicate}`, ...params)).rows[0];
905
+ return Number(row?.count ?? 0);
906
+ }
907
+ /**
908
+ * The single creation path for queued jobs.
909
+ *
910
+ * Centralizes the two creation-time security guards from the S5 audit (#1402)
911
+ * so every enqueue — the fluent {@link "./job-builder".JobBuilder} *and* the
912
+ * ScheduleRunner's cron-triggered jobs — goes through one place:
913
+ *
914
+ * 1. `maxAttempts` is clamped to {@link MAX_JOB_RETRIES} so a misconfigured
915
+ * caller cannot pin a worker on a poison job indefinitely.
916
+ * 2. A per-tenant in-flight cap bounds how many non-terminal jobs one tenant
917
+ * may hold, so one tenant cannot exhaust the shared worker pool
918
+ * (cross-tenant denial of service). The cap applies to the row's effective
919
+ * tenant (explicit `data.tenantId` or, when absent, the ambient context);
920
+ * global (null-tenant) jobs are exempt.
921
+ *
922
+ * Atomicity note (best-effort soft cap, by design): the cap is a
923
+ * count-then-insert, NOT a hard transactional invariant. It is intentionally
924
+ * left non-atomic. A plain transaction would not help — under the adapters'
925
+ * default isolation two concurrent same-tenant enqueues would each read the
926
+ * same COUNT and both insert, so serializing them would require either a
927
+ * per-tenant lock row (`SELECT ... FOR UPDATE`) or SERIALIZABLE-isolation
928
+ * retry loops. That cross-process locking is fragile (lock-row contention,
929
+ * adapter-specific isolation behavior, the `transaction` adapter method being
930
+ * optional) and out of proportion to the threat: this cap is defense in depth
931
+ * against runaway/accidental creation exhausting the shared worker pool, not a
932
+ * billing/quota boundary. So under truly simultaneous enqueues a tenant may
933
+ * momentarily overshoot by the number of in-flight creators; the bound still
934
+ * prevents unbounded growth and closes the prior ScheduleRunner bypass. If a
935
+ * hard guarantee is ever needed, enforce it with a DB CHECK/trigger or a
936
+ * dedicated counter row, not an application-level lock.
937
+ */
938
+ async enqueueJob(data, options = {}) {
939
+ const cap = options.tenantJobCap ?? 1e4;
940
+ const explicitTenant = typeof data.tenantId === "string" && data.tenantId.length > 0 ? data.tenantId : data.tenantId === null ? null : void 0;
941
+ const effectiveTenant = explicitTenant !== void 0 ? explicitTenant : getTenantId() ?? null;
942
+ if (effectiveTenant && cap > 0) assertWithinTenantCreationCap(effectiveTenant, await this.countInFlightForTenant(effectiveTenant), cap);
943
+ const job = await this.create({
944
+ ...data,
945
+ maxAttempts: clampRetries(data.maxAttempts ?? 3)
678
946
  });
947
+ await job.save();
948
+ return job;
679
949
  }
680
- async listSinceCursor(options = {}) {
681
- const where = [];
682
- const params = [];
683
- if (options.jobId) {
684
- where.push("job_id = ?");
685
- params.push(options.jobId);
686
- }
687
- this.addTenantPredicate(where, params, options);
688
- if (options.cursor) {
689
- const cursor = parseCursor(options.cursor);
690
- const createdAt = await this.resolveCursorCreatedAt(cursor, options);
691
- const createdAtExpression = this.createdAtComparableExpression();
692
- where.push(`(${createdAtExpression} > ? OR (${createdAtExpression} = ? AND id > ?))`);
693
- params.push(createdAt, createdAt, cursor.id);
694
- } else if (options.since) {
695
- where.push(`${this.createdAtComparableExpression()} > ?`);
696
- params.push(normalizeCursorDate(options.since));
697
- }
698
- if (options.afterId) {
699
- where.push("id > ?");
700
- params.push(options.afterId);
701
- }
702
- params.push(normalizeLimit(options.limit));
703
- const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
704
- return this.query(`SELECT ${JOB_EVENT_STORAGE_COLUMNS}
705
- FROM _smrt_job_events
706
- ${whereSql}
707
- ORDER BY ${this.createdAtComparableExpression()} ASC, id ASC
708
- LIMIT ?`, params, { allowRawOnTenantScoped: true });
709
- }
710
- async latestProgressByJobIds(jobIds, options = {}) {
711
- const uniqueJobIds = [...new Set(jobIds.filter(Boolean))];
712
- const latestByJobId = /* @__PURE__ */ new Map();
713
- if (uniqueJobIds.length === 0) return latestByJobId;
714
- const where = [`job_id IN (${uniqueJobIds.map(() => "?").join(", ")})`, "type = 'progress'"];
715
- const params = [...uniqueJobIds];
716
- this.addTenantPredicate(where, params, options);
717
- const createdAtExpression = this.createdAtComparableExpression();
718
- const events = await this.query(`SELECT ${JOB_EVENT_STORAGE_COLUMNS}
719
- FROM (
720
- SELECT ${JOB_EVENT_STORAGE_COLUMNS},
721
- ${createdAtExpression} AS smrt_created_at_sort,
722
- ROW_NUMBER() OVER (
723
- PARTITION BY job_id
724
- ORDER BY ${createdAtExpression} DESC, id DESC
725
- ) AS smrt_rank
726
- FROM _smrt_job_events
727
- WHERE ${where.join(" AND ")}
728
- ) ranked
729
- WHERE smrt_rank = 1
730
- ORDER BY smrt_created_at_sort DESC, id DESC`, params, { allowRawOnTenantScoped: true });
731
- for (const event of events) latestByJobId.set(event.jobId, event);
732
- return latestByJobId;
733
- }
734
- addTenantPredicate(where, params, options) {
735
- if (options.tenantId === null) {
736
- where.push("tenant_id IS NULL");
737
- return;
738
- }
739
- const tenantId2 = typeof options.tenantId === "string" ? options.tenantId : getTenantId();
740
- if (tenantId2) {
741
- where.push("tenant_id = ?");
742
- params.push(tenantId2);
743
- return;
744
- }
745
- throw new Error("Tenant-scoped job event queries require tenantId, tenantId: null, or an ambient tenant context.");
950
+ /**
951
+ * Get job statistics
952
+ */
953
+ async stats(queue) {
954
+ const query = queue ? "SELECT status, COUNT(*) as count FROM _smrt_jobs WHERE queue = ? GROUP BY status" : "SELECT status, COUNT(*) as count FROM _smrt_jobs GROUP BY status";
955
+ const params = queue ? [queue] : [];
956
+ const result = await this._db.query(query, ...params);
957
+ const counts = {};
958
+ for (const row of result.rows) counts[row.status] = row.count;
959
+ return {
960
+ pending: counts.pending ?? 0,
961
+ running: counts.running ?? 0,
962
+ completed: counts.completed ?? 0,
963
+ failed: counts.failed ?? 0,
964
+ cancelled: counts.cancelled ?? 0
965
+ };
746
966
  }
747
967
  /**
748
- * Delete job events recorded before a cutoff (#2375).
749
- *
750
- * `_smrt_job_events` is append-only — every log line, progress tick and
751
- * lifecycle transition of every job — so it outgrows `_smrt_jobs` by an
752
- * order of magnitude and had no prune path at all. Events are pruned on
753
- * their own clock rather than by following job deletion: a long-running job
754
- * accumulates events for as long as it runs.
968
+ * Cleanup old completed/failed jobs.
755
969
  *
756
- * The cutoff uses the same engine-normalized `created_at` expression the
757
- * cursor reads use. On PostgreSQL that is the bare column, so
758
- * `idx_smrt_job_events_created_at` serves it; on SQLite the `strftime()`
759
- * wrapper makes the comparison non-sargable and the prune is a scan. That is
760
- * acceptable for a periodic maintenance pass and is preferable to comparing
761
- * two differently-formatted timestamps, which is what the wrapper exists to
762
- * prevent.
970
+ * Predicates are `(status, completed_at)` pairs, covered by
971
+ * `idx_smrt_jobs_status_completed_at` (created by the jobs compatibility
972
+ * path this collection runs on initialize — #2375).
763
973
  *
764
- * @param options.before - Delete events created strictly before this time.
765
- * @param options.dryRun - Count matching events without deleting them.
766
- * @returns Number of events deleted (or, under `dryRun`, matched).
974
+ * @param options.dryRun - Count the jobs the cutoffs select without deleting
975
+ * them, so a retention preview reports exactly what a real run removes.
976
+ * @returns Number of jobs deleted (or, under `dryRun`, matched).
767
977
  */
768
978
  async cleanup(options) {
769
- const where = `${this.createdAtComparableExpression()} < ?`;
770
- const cutoff = options.before.toISOString();
771
- const counted = await this.db.query(`SELECT COUNT(*) AS total FROM _smrt_job_events WHERE ${where}`, cutoff);
979
+ const conditions = [];
980
+ const params = [];
981
+ if (options.completedBefore) {
982
+ conditions.push("(status = 'completed' AND completed_at < ?)");
983
+ params.push(options.completedBefore.toISOString());
984
+ }
985
+ if (options.failedBefore) {
986
+ conditions.push("(status = 'failed' AND completed_at < ?)");
987
+ params.push(options.failedBefore.toISOString());
988
+ }
989
+ if (options.cancelledBefore) {
990
+ conditions.push("(status = 'cancelled' AND completed_at < ?)");
991
+ params.push(options.cancelledBefore.toISOString());
992
+ }
993
+ if (conditions.length === 0) return 0;
994
+ const selector = options.limit ? `id IN (
995
+ SELECT id FROM _smrt_jobs
996
+ WHERE (${conditions.join(" OR ")})
997
+ LIMIT ?
998
+ )` : `(${conditions.join(" OR ")})`;
999
+ const selectorParams = options.limit ? [...params, options.limit] : [...params];
1000
+ const counted = await this._db.query(`SELECT COUNT(*) AS total FROM _smrt_jobs WHERE ${selector}`, ...selectorParams);
772
1001
  const total = Number(counted.rows?.[0]?.total ?? 0);
773
1002
  if (!Number.isFinite(total) || total <= 0) return 0;
774
- if (!options.dryRun) await this.db.query(`DELETE FROM _smrt_job_events WHERE ${where}`, cutoff);
1003
+ if (!options.dryRun) await this._db.query(`DELETE FROM _smrt_jobs WHERE ${selector}`, ...selectorParams);
775
1004
  return total;
776
1005
  }
777
- createdAtComparableExpression() {
778
- if (usesSqliteDateFunctions(this.db.url)) return "strftime('%Y-%m-%dT%H:%M:%fZ', created_at)";
779
- return "created_at";
780
- }
781
- async resolveCursorCreatedAt(cursor, options) {
782
- if (!cursor.id) return normalizeCursorDate(cursor.createdAt);
783
- const where = ["id = ?"];
784
- const params = [cursor.id];
785
- if (options.jobId) {
786
- where.push("job_id = ?");
787
- params.push(options.jobId);
788
- }
789
- this.addTenantPredicate(where, params, options);
790
- const cursorCreatedAt = getQueryRows(await this.db.query(`SELECT ${this.createdAtComparableExpression()} AS cursor_created_at
791
- FROM _smrt_job_events
792
- WHERE ${where.join(" AND ")}
793
- LIMIT 1`, ...params))[0]?.cursor_created_at;
794
- return typeof cursorCreatedAt === "string" && cursorCreatedAt.trim() ? cursorCreatedAt : normalizeCursorDate(cursor.createdAt);
795
- }
796
1006
  };
1007
+ function getDatabaseEngine(db) {
1008
+ const dbWithConfig = db;
1009
+ return detectEngine(db.url || dbWithConfig.config?.url || "", dbWithConfig.type || dbWithConfig.config?.type);
1010
+ }
1011
+ function compareClaimOrder(left, right) {
1012
+ const priority = right.priority - left.priority;
1013
+ if (priority !== 0) return priority;
1014
+ const runAt = left.runAt.getTime() - right.runAt.getTime();
1015
+ if (runAt !== 0) return runAt;
1016
+ const createdAt = timestamp(left.created_at) - timestamp(right.created_at);
1017
+ if (createdAt !== 0) return createdAt;
1018
+ return (left.id ?? "").localeCompare(right.id ?? "");
1019
+ }
1020
+ function timestamp(value) {
1021
+ return value?.getTime() ?? 0;
1022
+ }
797
1023
  //#endregion
798
1024
  //#region src/retention.ts
799
1025
  var logger = createLogger({ level: "info" });
@@ -1061,15 +1287,15 @@ var McpTaskStore = class McpTaskStore {
1061
1287
  async cancelTask(taskId) {
1062
1288
  const job = await this.findTaskJob(taskId);
1063
1289
  if (job.status === "completed" || job.status === "failed" || job.status === "cancelled") return;
1064
- const now = (/* @__PURE__ */ new Date()).toISOString();
1065
- await this.collection.query(`UPDATE _smrt_jobs
1066
- SET status = 'cancelled', completed_at = ?, updated_at = ?
1067
- WHERE id = ? AND task_id = ? AND status IN ('pending', 'running')`, [
1068
- now,
1069
- now,
1070
- job.id,
1071
- taskId
1072
- ], { allowRawOnTenantScoped: true });
1290
+ const snapshot = terminalSnapshot(job);
1291
+ if (!snapshot) throw new McpTaskNotFoundError(taskId);
1292
+ await transitionTerminalJob(this.collection.db, {
1293
+ job: snapshot,
1294
+ status: "cancelled",
1295
+ completedAt: /* @__PURE__ */ new Date(),
1296
+ expectedStatuses: ["pending", "running"],
1297
+ expectedTaskId: taskId
1298
+ });
1073
1299
  }
1074
1300
  async findTaskJob(taskId) {
1075
1301
  const ownerPredicate = this.ownerId === null ? "task_owner_id IS NULL" : "task_owner_id = ?";
@@ -1673,24 +1899,28 @@ var TaskRunner = class extends EventEmitter {
1673
1899
  timeoutHandle = handle;
1674
1900
  });
1675
1901
  const completedAt = /* @__PURE__ */ new Date();
1676
- if (await this.writeOwnedJob(jobId, {
1677
- status: "completed",
1678
- completed_at: completedAt.toISOString(),
1679
- result_pointer: result?.resultPointer ?? null,
1680
- task_result: result?.taskResult ? JSON.stringify(result.taskResult) : null,
1681
- updated_at: completedAt.toISOString()
1682
- })) {
1902
+ const snapshot = terminalSnapshot(job);
1903
+ let event = null;
1904
+ try {
1905
+ event = snapshot ? await transitionTerminalJob(this.db, {
1906
+ job: snapshot,
1907
+ status: "completed",
1908
+ completedAt,
1909
+ expectedStatuses: ["running"],
1910
+ expectedWorkerId: this.workerKey,
1911
+ resultPointer: result?.resultPointer ?? null,
1912
+ taskResult: result?.taskResult ?? null
1913
+ }) : null;
1914
+ } catch (persistenceError) {
1915
+ this.emit("runner:error", persistenceError);
1916
+ return;
1917
+ }
1918
+ if (event) {
1683
1919
  job.status = "completed";
1684
1920
  job.completedAt = completedAt;
1685
1921
  job.resultPointer = result?.resultPointer ?? null;
1686
1922
  job.taskResult = result?.taskResult ?? null;
1687
- await this.appendJobEvent(job, {
1688
- type: "progress",
1689
- level: "info",
1690
- stage: "completed",
1691
- progress: 100,
1692
- message: `Completed job: ${job.getDescription()}`
1693
- });
1923
+ this.emitPersistedJobEvent(job, event);
1694
1924
  this.emit("job:completed", job, result);
1695
1925
  }
1696
1926
  } catch (error) {
@@ -1860,22 +2090,21 @@ var TaskRunner = class extends EventEmitter {
1860
2090
  this.emit("job:retrying", job, error, decision.delay);
1861
2091
  } else {
1862
2092
  const completedAt = /* @__PURE__ */ new Date();
1863
- if (!await this.writeOwnedJob(jobId, {
2093
+ const snapshot = terminalSnapshot(job);
2094
+ const event = snapshot ? await transitionTerminalJob(this.db, {
2095
+ job: snapshot,
1864
2096
  status: "failed",
1865
- completed_at: completedAt.toISOString(),
1866
- last_error: safeMessage,
1867
- updated_at: completedAt.toISOString()
1868
- })) return;
2097
+ completedAt,
2098
+ expectedStatuses: ["running"],
2099
+ expectedWorkerId: this.workerKey,
2100
+ lastError: safeMessage,
2101
+ failureKind: isTimeout ? "timeout" : "execution"
2102
+ }) : null;
2103
+ if (!event) return;
1869
2104
  job.status = "failed";
1870
2105
  job.completedAt = completedAt;
1871
2106
  job.lastError = safeMessage;
1872
- await this.appendJobEvent(job, {
1873
- type: "error",
1874
- level: "error",
1875
- stage: "failed",
1876
- message: safeMessage,
1877
- data: { attempts: job.attempts }
1878
- });
2107
+ this.emitPersistedJobEvent(job, event);
1879
2108
  this.emit("job:failed", job, error);
1880
2109
  }
1881
2110
  }
@@ -1963,8 +2192,7 @@ var TaskRunner = class extends EventEmitter {
1963
2192
  message: input.message ?? "",
1964
2193
  data: input.data ?? {}
1965
2194
  });
1966
- this.emit("job:event", job, event);
1967
- if (event.type === "progress") this.emit("job:progress", job, event);
2195
+ this.emitPersistedJobEvent(job, event);
1968
2196
  return event;
1969
2197
  } catch (error) {
1970
2198
  const telemetryError = error instanceof Error ? error : /* @__PURE__ */ new Error(`Failed to append job telemetry: ${String(error)}`);
@@ -1974,6 +2202,24 @@ var TaskRunner = class extends EventEmitter {
1974
2202
  return null;
1975
2203
  }
1976
2204
  }
2205
+ emitPersistedJobEvent(job, event) {
2206
+ try {
2207
+ this.emit("job:event", job, event);
2208
+ } catch (error) {
2209
+ this.emitTelemetryError(error);
2210
+ }
2211
+ if (event.type === "progress") try {
2212
+ this.emit("job:progress", job, event);
2213
+ } catch (error) {
2214
+ this.emitTelemetryError(error);
2215
+ }
2216
+ }
2217
+ emitTelemetryError(error) {
2218
+ const telemetryError = error instanceof Error ? error : /* @__PURE__ */ new Error(`Job telemetry listener failed: ${String(error)}`);
2219
+ try {
2220
+ this.emit("runner:error", telemetryError);
2221
+ } catch {}
2222
+ }
1977
2223
  /**
1978
2224
  * Whether the `_smrt_workers` table exists. Cached once positive — the table
1979
2225
  * never disappears mid-run, so this avoids a probe query on every poll.
@@ -2018,36 +2264,29 @@ var TaskRunner = class extends EventEmitter {
2018
2264
  return !isWorkerAlive(job.workerId, freshLeaseKeys);
2019
2265
  });
2020
2266
  if (orphans.length === 0) return;
2021
- const orphanIds = orphans.map((job) => job.id).filter((jobId) => typeof jobId === "string");
2022
- if (orphanIds.length === 0) return;
2023
- const placeholders = orphanIds.map(() => "?").join(", ");
2024
2267
  const recoveredAt = /* @__PURE__ */ new Date();
2025
2268
  const errorMessage = "Recovered orphaned running job: its owning worker is no longer alive (no fresh liveness lease in _smrt_workers and not running in this process).";
2026
- const updated = await this.db.query(`UPDATE _smrt_jobs
2027
- SET status = 'failed',
2028
- completed_at = ?,
2029
- last_error = ?,
2030
- worker_id = NULL,
2031
- worker_heartbeat = NULL
2032
- WHERE status = 'running'
2033
- AND id IN (${placeholders})
2034
- RETURNING CAST(id AS VARCHAR) AS id`, recoveredAt.toISOString(), errorMessage, ...orphanIds);
2035
- const recoveredIds = new Set(updated.rows.map((row) => row.id).filter((id) => typeof id === "string"));
2036
- if (recoveredIds.size === 0) return;
2037
2269
  for (const job of orphans) {
2038
- if (!job.id || !recoveredIds.has(job.id)) continue;
2270
+ const snapshot = terminalSnapshot(job);
2271
+ if (!snapshot) continue;
2272
+ const event = await transitionTerminalJob(this.db, {
2273
+ job: snapshot,
2274
+ status: "failed",
2275
+ completedAt: recoveredAt,
2276
+ expectedStatuses: ["running"],
2277
+ expectedWorkerId: job.workerId ?? null,
2278
+ clearWorker: true,
2279
+ lastError: errorMessage,
2280
+ failureKind: "stale-recovery"
2281
+ });
2282
+ if (!event) continue;
2039
2283
  job.status = "failed";
2040
2284
  job.completedAt = recoveredAt;
2041
2285
  job.lastError = errorMessage;
2042
2286
  job.workerId = null;
2043
2287
  job.workerHeartbeat = null;
2044
2288
  const error = /* @__PURE__ */ new Error(errorMessage);
2045
- await this.appendJobEvent(job, {
2046
- type: "error",
2047
- level: "error",
2048
- stage: "stale-recovery",
2049
- message: errorMessage
2050
- });
2289
+ this.emitPersistedJobEvent(job, event);
2051
2290
  this.emit("job:failed", job, error);
2052
2291
  }
2053
2292
  }
@@ -2205,6 +2444,6 @@ function createTaskRunner(config) {
2205
2444
  return new TaskRunner(config);
2206
2445
  }
2207
2446
  //#endregion
2208
- export { SmrtJobEventCollection as A, isBackgroundEligibleMethod as B, DEFAULT_RETENTION_SWEEP_INTERVAL_MS as C, startRetentionSweeper as D, registerJobRetentionTasks as E, TenantJobCapExceededError as F, assertWithinTenantCreationCap as I, backgroundEligible as L, SmrtJobCollection as M, DEFAULT_TENANT_JOB_CAP as N, unregisterJobRetentionTasks as O, MAX_JOB_RETRIES as P, clampRetries as R, DEFAULT_JOB_RETENTION as S, JOB_EVENTS_RETENTION_TASK as T, markBackgroundEligible as V, requestMcpTaskInput as _, isRunnerExecutionContext as a, redactErrorForPersistence as b, SmrtWorkerCollection as c, McpTaskNotFoundError as d, McpTaskStore as f, requestMcpTaskContinuation as g, getMcpTaskMarker as h, getActiveJobExecutionContext as i, SmrtJob as j, SmrtJobEvent as k, MCP_TASKS_EXTENSION as l, createMcpTaskResult as m, TaskRunner as n, DEFAULT_TASK_HEARTBEAT_INTERVAL_MS as o, McpTaskSuspendedError as p, createTaskRunner as r, SmrtWorker as s, JobTimeoutError as t, McpTaskCancelledError as u, taskFromJob as v, JOBS_RETENTION_TASK as w, redactErrorMessage as x, JobContextLogger as y, getBackgroundEligibleMethods as z };
2447
+ export { SmrtJobCollection as A, isBackgroundEligibleMethod as B, DEFAULT_RETENTION_SWEEP_INTERVAL_MS as C, startRetentionSweeper as D, registerJobRetentionTasks as E, TenantJobCapExceededError as F, assertWithinTenantCreationCap as I, backgroundEligible as L, SmrtJobEventCollection as M, DEFAULT_TENANT_JOB_CAP as N, unregisterJobRetentionTasks as O, MAX_JOB_RETRIES as P, clampRetries as R, DEFAULT_JOB_RETENTION as S, JOB_EVENTS_RETENTION_TASK as T, markBackgroundEligible as V, requestMcpTaskInput as _, isRunnerExecutionContext as a, redactErrorForPersistence as b, SmrtWorkerCollection as c, McpTaskNotFoundError as d, McpTaskStore as f, requestMcpTaskContinuation as g, getMcpTaskMarker as h, getActiveJobExecutionContext as i, SmrtJobEvent as j, SmrtJob as k, MCP_TASKS_EXTENSION as l, createMcpTaskResult as m, TaskRunner as n, DEFAULT_TASK_HEARTBEAT_INTERVAL_MS as o, McpTaskSuspendedError as p, createTaskRunner as r, SmrtWorker as s, JobTimeoutError as t, McpTaskCancelledError as u, taskFromJob as v, JOBS_RETENTION_TASK as w, redactErrorMessage as x, JobContextLogger as y, getBackgroundEligibleMethods as z };
2209
2448
 
2210
- //# sourceMappingURL=runner-uZZwhrat.js.map
2449
+ //# sourceMappingURL=runner-C6_0QXo2.js.map