@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.
- package/AGENTS.md +20 -0
- package/README.md +32 -0
- package/dist/chunks/{runner-uZZwhrat.js → runner-C6_0QXo2.js} +919 -680
- package/dist/chunks/runner-C6_0QXo2.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +20 -1
- package/dist/mcp-task.d.ts.map +1 -1
- package/dist/runner.d.ts +2 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +1 -1
- package/dist/smrt-job-event.d.ts +41 -0
- package/dist/smrt-job-event.d.ts.map +1 -1
- package/dist/smrt-job.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +39 -6
- package/dist/terminal-outcome.d.ts +33 -0
- package/dist/terminal-outcome.d.ts.map +1 -0
- package/package.json +11 -11
- package/dist/chunks/runner-uZZwhrat.js.map +0 -1
|
@@ -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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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(
|
|
164
|
-
__decorateClass$2([tenantId({ nullable: true })],
|
|
165
|
-
__decorateClass$2([
|
|
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
|
-
|
|
187
|
-
})],
|
|
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: "
|
|
197
|
-
|
|
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: "
|
|
218
|
-
})],
|
|
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
|
-
})],
|
|
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: "
|
|
131
|
+
type: "integer",
|
|
254
132
|
nullable: true
|
|
255
|
-
})],
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
|
|
264
|
-
})],
|
|
265
|
-
|
|
266
|
-
tableName: "
|
|
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
|
-
|
|
271
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
"
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
*
|
|
271
|
+
* Read the newest safe, authoritative terminal outcomes for one tenant.
|
|
417
272
|
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
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
|
|
462
|
-
|
|
463
|
-
const
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
*
|
|
340
|
+
* Delete job events recorded before a cutoff (#2375).
|
|
477
341
|
*
|
|
478
|
-
*
|
|
479
|
-
* `
|
|
480
|
-
*
|
|
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
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
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
|
|
488
|
-
const
|
|
489
|
-
|
|
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.
|
|
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
|
|
516
|
-
const
|
|
517
|
-
|
|
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
|
|
520
|
-
|
|
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
|
|
529
|
-
return value
|
|
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/
|
|
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
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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(
|
|
578
|
-
__decorateClass$1([tenantId({ nullable: true })],
|
|
579
|
-
__decorateClass$1([
|
|
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
|
-
|
|
582
|
-
})],
|
|
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: "
|
|
587
|
-
|
|
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: "
|
|
592
|
-
})],
|
|
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
|
-
})],
|
|
722
|
+
})], SmrtJob.prototype, "lastError", 2);
|
|
597
723
|
__decorateClass$1([field({
|
|
598
|
-
type: "
|
|
724
|
+
type: "text",
|
|
599
725
|
nullable: true
|
|
600
|
-
})],
|
|
726
|
+
})], SmrtJob.prototype, "resultPointer", 2);
|
|
601
727
|
__decorateClass$1([field({
|
|
602
728
|
type: "text",
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
})],
|
|
606
|
-
__decorateClass$1([field({
|
|
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
|
-
|
|
610
|
-
})],
|
|
611
|
-
|
|
612
|
-
tableName: "
|
|
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: [
|
|
616
|
-
|
|
617
|
-
|
|
761
|
+
include: [
|
|
762
|
+
"list",
|
|
763
|
+
"get",
|
|
764
|
+
"retry",
|
|
765
|
+
"cancel"
|
|
766
|
+
],
|
|
767
|
+
skipApiCheck: true,
|
|
768
|
+
http: false
|
|
618
769
|
},
|
|
619
|
-
mcp: false
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
|
784
|
+
await ensureJobsSystemTableCompatibility(this.db);
|
|
659
785
|
return this;
|
|
660
786
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
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.
|
|
765
|
-
*
|
|
766
|
-
* @returns Number of
|
|
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
|
|
770
|
-
const
|
|
771
|
-
|
|
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.
|
|
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
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
taskId
|
|
1072
|
-
|
|
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
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2093
|
+
const snapshot = terminalSnapshot(job);
|
|
2094
|
+
const event = snapshot ? await transitionTerminalJob(this.db, {
|
|
2095
|
+
job: snapshot,
|
|
1864
2096
|
status: "failed",
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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-
|
|
2449
|
+
//# sourceMappingURL=runner-C6_0QXo2.js.map
|