effect-mq 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/dist/Job.d.ts +222 -0
  4. package/dist/Job.d.ts.map +1 -0
  5. package/dist/Job.js +218 -0
  6. package/dist/Job.js.map +1 -0
  7. package/dist/JobStore.d.ts +401 -0
  8. package/dist/JobStore.d.ts.map +1 -0
  9. package/dist/JobStore.js +89 -0
  10. package/dist/JobStore.js.map +1 -0
  11. package/dist/MemoryJobStore.d.ts +34 -0
  12. package/dist/MemoryJobStore.d.ts.map +1 -0
  13. package/dist/MemoryJobStore.js +381 -0
  14. package/dist/MemoryJobStore.js.map +1 -0
  15. package/dist/Worker.d.ts +127 -0
  16. package/dist/Worker.d.ts.map +1 -0
  17. package/dist/Worker.js +274 -0
  18. package/dist/Worker.js.map +1 -0
  19. package/dist/drizzle/DrizzleJobStore.d.ts +59 -0
  20. package/dist/drizzle/DrizzleJobStore.d.ts.map +1 -0
  21. package/dist/drizzle/DrizzleJobStore.js +426 -0
  22. package/dist/drizzle/DrizzleJobStore.js.map +1 -0
  23. package/dist/drizzle/index.d.ts +19 -0
  24. package/dist/drizzle/index.d.ts.map +1 -0
  25. package/dist/drizzle/index.js +19 -0
  26. package/dist/drizzle/index.js.map +1 -0
  27. package/dist/drizzle/schema.d.ts +464 -0
  28. package/dist/drizzle/schema.d.ts.map +1 -0
  29. package/dist/drizzle/schema.js +68 -0
  30. package/dist/drizzle/schema.js.map +1 -0
  31. package/dist/index.d.ts +30 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +30 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/testing/conformance.d.ts +27 -0
  36. package/dist/testing/conformance.d.ts.map +1 -0
  37. package/dist/testing/conformance.js +451 -0
  38. package/dist/testing/conformance.js.map +1 -0
  39. package/dist/testing/index.d.ts +8 -0
  40. package/dist/testing/index.d.ts.map +1 -0
  41. package/dist/testing/index.js +8 -0
  42. package/dist/testing/index.js.map +1 -0
  43. package/package.json +71 -0
  44. package/src/Job.ts +606 -0
  45. package/src/JobStore.ts +446 -0
  46. package/src/MemoryJobStore.ts +467 -0
  47. package/src/Worker.ts +514 -0
  48. package/src/drizzle/DrizzleJobStore.ts +599 -0
  49. package/src/drizzle/index.ts +20 -0
  50. package/src/drizzle/schema.ts +116 -0
  51. package/src/index.ts +33 -0
  52. package/src/testing/conformance.ts +654 -0
  53. package/src/testing/index.ts +7 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam Rankin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,235 @@
1
+ # effect-mq
2
+
3
+ Effect-native background jobs. Schema-first job definitions, a storage-agnostic
4
+ queue core, a worker runtime, and a Postgres store that lives inside your
5
+ drizzle schema — inspired by BullMQ's semantics and `effect/workflow`'s DX.
6
+ Built on Effect v4.
7
+
8
+ ```
9
+ bun add effect-mq effect # or npm / pnpm / yarn
10
+ ```
11
+
12
+ One package, tree-shakeable modules:
13
+
14
+ | Import | Contents | Extra peers |
15
+ | --- | --- | --- |
16
+ | `effect-mq` | `Job`, `JobStore`, `MemoryJobStore`, `Worker` | — |
17
+ | `effect-mq/drizzle` | drizzle schema factories + the Postgres `JobStore` | `drizzle-orm` (v1), `@effect/sql-pg` |
18
+ | `effect-mq/testing` | the `JobStore` conformance suite for driver authors | `@effect/vitest` |
19
+
20
+ ## Five-minute tour
21
+
22
+ ```ts
23
+ import { Job, MemoryJobStore, Worker } from "effect-mq"
24
+ import { Effect, Layer, Schema } from "effect"
25
+
26
+ // 1. Define a job — shared by producers and runners.
27
+ class SendEmail extends Job.make("SendEmail", {
28
+ payload: { to: Schema.String, subject: Schema.String },
29
+ success: Schema.String,
30
+ idempotencyKey: ({ to, subject }) => `${to}:${subject}`,
31
+ metadata: ({ to }) => ({ to }), // indexed, queryable
32
+ queue: "email",
33
+ defaults: { attempts: 3, backoff: { type: "exponential", delay: "1 second" } }
34
+ }) {}
35
+
36
+ // 2. Produce. Only the store is required — no worker anywhere in sight.
37
+ const producer = Effect.gen(function*() {
38
+ const jobId = yield* SendEmail.enqueue({ to: "ada@example.com", subject: "hi" })
39
+ // ^ JobId — deterministic here, thanks to idempotencyKey
40
+
41
+ // ...or enqueue-and-wait for the typed result:
42
+ const messageId = yield* SendEmail.execute(
43
+ { to: "grace@example.com", subject: "now" },
44
+ { delay: "5 seconds", priority: 2 }
45
+ )
46
+ })
47
+
48
+ // 3. Run. Workers are layers — deploy them in the same process or across
49
+ // machines against shared storage.
50
+ const RunnerLive = SendEmail.toLayer(
51
+ (payload, ctx) => Effect.succeed(`message-${ctx.jobId}`),
52
+ { concurrency: 5 }
53
+ ).pipe(
54
+ Layer.provideMerge(Worker.layer()),
55
+ Layer.provideMerge(MemoryJobStore.layer) // swap for Postgres below
56
+ )
57
+ ```
58
+
59
+ What you get out of the box:
60
+
61
+ - **At-least-once execution** — token-guarded locks, heartbeat renewal, and a
62
+ stalled-job sweeper that recovers work from crashed workers.
63
+ - **Durable retries** — a failed attempt is written back to the store and
64
+ re-claimed after its backoff by *any* worker; no lock or worker slot is held
65
+ while waiting.
66
+ - **A full run ledger** — every run (success, retry, failure, stall) persists
67
+ as an `AttemptRecord`; `Job.attempts(id)` decodes them back to typed exits.
68
+ - **Idempotency** — `idempotencyKey` makes enqueue a no-op while a job with
69
+ that key exists, and makes the job id *computable from business data* — the
70
+ natural join key between the queue and your own domain tables.
71
+ - **A dashboard data layer** — `store.list({ name, states, metadata, cursor })`,
72
+ `Job.poll`, `Job.retry(id)` (failed → fresh attempt budget, ledger intact),
73
+ and per-job retention via `keep: { count, age }`.
74
+ - **Graceful shutdown** — interrupting a worker releases in-flight jobs back
75
+ to `waiting` without consuming an attempt.
76
+
77
+ ## Postgres through drizzle
78
+
79
+ The Postgres store runs on drizzle v1's Effect driver
80
+ (`drizzle-orm/effect-postgres`, built on `@effect/sql-pg` — works on Node and
81
+ Bun). Claims use `FOR UPDATE SKIP LOCKED`; wake-ups use LISTEN/NOTIFY, so
82
+ workers in other processes pick jobs up promptly.
83
+
84
+ ```
85
+ bun add drizzle-orm@rc @effect/sql-pg
86
+ ```
87
+
88
+ ### 1. Put the tables in your drizzle schema
89
+
90
+ The factories are the single source of truth for the table layout. Re-export
91
+ them from your schema file and **your drizzle-kit pipeline owns the
92
+ migrations** — no library-run DDL, no parallel migration system:
93
+
94
+ ```ts
95
+ // db/schema.ts
96
+ import { mqJobAttempts, mqJobs } from "effect-mq/drizzle"
97
+
98
+ // The `name` column is typed to your job tags (derived, not hand-written):
99
+ type JobNames = typeof SyncBenefits._tag | typeof GenerateReport._tag
100
+
101
+ export const jobs = mqJobs<JobNames>() // default table: effect_mq_jobs
102
+ export const jobAttempts = mqJobAttempts(jobs) // default: effect_mq_job_attempts
103
+ ```
104
+
105
+ ```
106
+ drizzle-kit generate # emits the CREATE TABLE migration next to your others
107
+ drizzle-kit migrate
108
+ ```
109
+
110
+ When a future effect-mq version changes the layout, the factory changes and
111
+ `drizzle-kit generate` diffs it — you get a normal, reviewable migration.
112
+
113
+ ### 2. Provide the store layer
114
+
115
+ ```ts
116
+ import { DrizzleJobStore } from "effect-mq/drizzle"
117
+ import { PgClient } from "@effect/sql-pg"
118
+ import { Layer, Redacted } from "effect"
119
+ import { jobAttempts, jobs } from "./db/schema.ts"
120
+
121
+ const JobStoreLive = DrizzleJobStore.layer({ jobs, attempts: jobAttempts }).pipe(
122
+ Layer.provide(PgClient.layer({ url: Redacted.make(process.env.DATABASE_URL!) }))
123
+ )
124
+ ```
125
+
126
+ The layer probes the tables at startup and fails fast with a clear message if
127
+ migrations haven't run (`validate: false` defers that to first use).
128
+
129
+ ### 3. Query it like any other table
130
+
131
+ Product UIs read the tables directly with drizzle — fully typed:
132
+
133
+ ```ts
134
+ db.select().from(jobs).where(and(
135
+ eq(jobs.name, "sync-benefits"), // a typo is a compile error
136
+ sql`${jobs.metadata} @> ${{ employerId }}::jsonb` // GIN-indexed containment
137
+ ))
138
+
139
+ db.select().from(jobAttempts)
140
+ .innerJoin(jobs, eq(jobAttempts.jobId, jobs.id))
141
+ .where(eq(jobAttempts.outcome, "failed"))
142
+ ```
143
+
144
+ **Reads yes, writes no.** Mutations must go through the store —
145
+ `MyJob.retry(id)`, `store.remove(id)` — so lock tokens, attempt accounting,
146
+ and wake-up notifications stay coherent.
147
+
148
+ Worker tip: `awaitWake` is LISTEN/NOTIFY-driven, and the worker's
149
+ `pollInterval` is the fallback — `Worker.layer({ pollInterval: "500 millis" })`
150
+ is a good Postgres setting.
151
+
152
+ ## Multiple stores on different infrastructure
153
+
154
+ Bind jobs to *named stores* so business-critical runs live in Postgres while
155
+ disposable ones live elsewhere — enforced by the type system:
156
+
157
+ ```ts
158
+ import { Job, JobStore, MemoryJobStore, Worker } from "effect-mq"
159
+
160
+ const Durable = JobStore.named("durable") // -> Postgres in prod
161
+ const Ephemeral = JobStore.named("ephemeral") // -> memory/Redis
162
+
163
+ class SyncBenefits extends Job.make("sync-benefits", {
164
+ payload: { employerId: Schema.String },
165
+ idempotencyKey: ({ employerId }) => employerId,
166
+ store: Durable
167
+ }) {}
168
+
169
+ // Forgetting the Durable layer is now a COMPILE error at every enqueue site.
170
+ // Workers bind to one store:
171
+ const durableWorkers = SyncBenefits.toLayer(handler).pipe(
172
+ Layer.provide(Worker.layer({ store: Durable })) // local provide: several
173
+ ) // workers can coexist
174
+ ```
175
+
176
+ A **queue** is an ordering/concurrency domain *within* a store
177
+ (`Job.make({ queue })`, `Worker.layer({ queues: { email: { concurrency: 5 } } })`);
178
+ a **store** is an infrastructure/durability domain hosting many queues.
179
+
180
+ ## Metadata vs. your own tables
181
+
182
+ Two kinds of "business context", two homes:
183
+
184
+ - **Ops UI** ("list sync runs for employer X, retry that one"): use the
185
+ `metadata` projection — a flat `Record<string, string>` derived from the
186
+ payload, indexed by every driver, filterable via `store.list` or raw SQL.
187
+ - **Domain history** ("what did this sync actually change"): your own table,
188
+ joined by the *deterministic* job id from `idempotencyKey`. The queue's
189
+ retention (`keep`) can then prune freely while your business history lives
190
+ forever. Don't let queue infrastructure own business data lifecycles.
191
+
192
+ ## Secrets and redaction
193
+
194
+ Payloads and results are persisted **schema-encoded** — nothing reaches a
195
+ store un-encoded. For `Schema.Redacted` fields, Effect's semantics apply:
196
+
197
+ - `Schema.Redacted(Schema.String)` **round-trips**: handlers receive a real
198
+ `Redacted` value (safe to log — it prints `<redacted>`), but the underlying
199
+ value *is* stored in the payload/exit JSON. Redaction protects logs and
200
+ inspection, not the database at rest.
201
+ - `Schema.Redacted(inner, { disallowJsonEncode: true })` **refuses
202
+ persistence**: enqueueing such a payload dies with `Cannot serialize
203
+ Redacted` before anything reaches the store. Use it for values that must
204
+ never be written down; pass them to handlers via context/services instead.
205
+
206
+ Both behaviors are pinned by tests.
207
+
208
+ ## Writing a storage driver
209
+
210
+ Implement the `JobStore` service (one atomic seam: `enqueue`, `claim`, `ack`,
211
+ `release`, `extendLocks`, `recoverStalled`, `awaitWake`, `getJob`,
212
+ `getAttempts`, `list`, `retry`, `counts`, `remove`) and run the conformance
213
+ suite against it:
214
+
215
+ ```ts
216
+ import { jobStoreConformance } from "effect-mq/testing"
217
+
218
+ jobStoreConformance("MyDriver", () => MyDriver.layer)
219
+ ```
220
+
221
+ Two rules make the suite work against real storage: derive **all time from
222
+ the Effect `Clock`** (pass `now` as a bind parameter — never SQL `now()`), so
223
+ tests run under `TestClock`; and keep every operation atomic. The in-memory
224
+ driver (`MemoryJobStore`) is the reference implementation, and the Postgres
225
+ suite in this repo runs the same conformance tests against a real database.
226
+
227
+ ## Roadmap
228
+
229
+ - `effect-mq/redis` (Bun.redis / ioredis-free) behind the same conformance suite
230
+ - A standalone non-drizzle Postgres driver on plain `@effect/sql-pg`
231
+ - Repeatable/cron jobs, rate limiting, a reference dashboard
232
+
233
+ ## License
234
+
235
+ MIT
package/dist/Job.d.ts ADDED
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Schema-first background job definitions.
3
+ *
4
+ * A `Job` is defined once (name, payload/success/error schemas, defaults) and
5
+ * used from both sides:
6
+ *
7
+ * - producers call `MyJob.enqueue(payload, options)` (requires the job's store)
8
+ * - runners provide `MyJob.toLayer(handler)` on top of a `Worker.layer`
9
+ *
10
+ * ```ts
11
+ * import { Job, JobStore } from "effect-mq"
12
+ * import { Effect, Schema } from "effect"
13
+ *
14
+ * const Durable = JobStore.named("durable")
15
+ *
16
+ * class SendEmail extends Job.make("SendEmail", {
17
+ * payload: { to: Schema.String, body: Schema.String },
18
+ * queue: "email",
19
+ * store: Durable,
20
+ * metadata: ({ to }) => ({ to }),
21
+ * defaults: { attempts: 5, backoff: { type: "exponential", delay: "1 second" } }
22
+ * }) {}
23
+ *
24
+ * // producer — requires the Durable store in context, enforced at compile time
25
+ * const jobId = yield* SendEmail.enqueue({ to: "a@b.c", body: "hi" }, { delay: "5 seconds" })
26
+ *
27
+ * // runner
28
+ * const SendEmailWorker = SendEmail.toLayer((payload) => Effect.log(`sending to ${payload.to}`))
29
+ * ```
30
+ *
31
+ * @since 0.1.0
32
+ */
33
+ import { type Context, Duration, Effect, type Exit, Layer, Option, Schedule, Schema } from "effect";
34
+ import { type BackoffPolicy, JobId, JobNotFoundError, type JobNotRetryableError, type JobState, JobStore, type KeepPolicy, QueueName, type Service as StoreService } from "./JobStore.ts";
35
+ import { type JobContext, type RegisterOptions, Worker } from "./Worker.ts";
36
+ declare const TypeId: "~effect-mq/Job";
37
+ /**
38
+ * A struct schema (or anything with struct fields).
39
+ *
40
+ * @since 0.1.0
41
+ */
42
+ export interface AnyStructSchema extends Schema.Top {
43
+ readonly fields: Schema.Struct.Fields;
44
+ }
45
+ /**
46
+ * User-facing backoff configuration.
47
+ *
48
+ * @since 0.1.0
49
+ */
50
+ export interface BackoffInput {
51
+ readonly type: "fixed" | "exponential";
52
+ readonly delay: Duration.Input;
53
+ /** Exponential growth factor (default 2). */
54
+ readonly factor?: number | undefined;
55
+ }
56
+ /**
57
+ * User-facing retention configuration for terminal jobs.
58
+ *
59
+ * @since 0.1.0
60
+ */
61
+ export interface KeepInput {
62
+ /** Keep at most this many terminal records (per name + state). */
63
+ readonly count?: number | undefined;
64
+ /** Remove terminal records older than this. */
65
+ readonly age?: Duration.Input | undefined;
66
+ }
67
+ /**
68
+ * Options shared between job defaults and per-enqueue overrides.
69
+ *
70
+ * @since 0.1.0
71
+ */
72
+ export interface JobOptions {
73
+ /** Do not run before this long from enqueue time. */
74
+ readonly delay?: Duration.Input | undefined;
75
+ /** Higher runs first; ties are FIFO. Default 0. */
76
+ readonly priority?: number | undefined;
77
+ /** Total attempts including the first run. Default 1 (no retries). */
78
+ readonly attempts?: number | undefined;
79
+ readonly backoff?: BackoffInput | undefined;
80
+ /** Retention for terminal records. Default: keep forever. */
81
+ readonly keep?: KeepInput | undefined;
82
+ }
83
+ /**
84
+ * @since 0.1.0
85
+ */
86
+ export interface EnqueueOptions extends JobOptions {
87
+ /**
88
+ * Explicit job id. Enqueueing an id that already exists is a no-op that
89
+ * returns the existing id (idempotency). Overrides the definition's
90
+ * `idempotencyKey`.
91
+ */
92
+ readonly jobId?: string | undefined;
93
+ /** Send to a different queue than the definition's. */
94
+ readonly queue?: string | undefined;
95
+ /** Queryable business context, merged over the definition's `metadata`. */
96
+ readonly metadata?: Readonly<Record<string, string>> | undefined;
97
+ }
98
+ interface ResolvedDefaults {
99
+ readonly delayMs: number;
100
+ readonly priority: number;
101
+ readonly attempts: number;
102
+ readonly backoff: BackoffPolicy | undefined;
103
+ readonly keep: KeepPolicy | undefined;
104
+ }
105
+ /**
106
+ * The status of a job as seen by `poll`. Fetch the full run ledger with
107
+ * `Job.attempts`.
108
+ *
109
+ * @since 0.1.0
110
+ */
111
+ export interface JobStatus<A, E> {
112
+ readonly state: JobState;
113
+ readonly attemptsMade: number;
114
+ readonly metadata: Readonly<Record<string, string>>;
115
+ /** Present for completed/failed jobs (except store-side failures like stalling). */
116
+ readonly exit: Option.Option<Exit.Exit<A, E>>;
117
+ readonly failedReason: string | undefined;
118
+ }
119
+ /**
120
+ * One decoded entry of a job's run ledger.
121
+ *
122
+ * @since 0.1.0
123
+ */
124
+ export interface JobAttempt<A, E> {
125
+ readonly attempt: number;
126
+ readonly startedAt: number | undefined;
127
+ readonly finishedAt: number;
128
+ readonly outcome: "completed" | "retried" | "failed" | "stalled";
129
+ /** Absent for `stalled` entries. */
130
+ readonly exit: Option.Option<Exit.Exit<A, E>>;
131
+ }
132
+ /**
133
+ * @since 0.1.0
134
+ */
135
+ export interface Job<Name extends string, Payload extends AnyStructSchema, Success extends Schema.Top, Error extends Schema.Top, StoreId = JobStore> {
136
+ new (_: never): {};
137
+ readonly [TypeId]: typeof TypeId;
138
+ /**
139
+ * The job's unique name. (Named `_tag` rather than `name` because a
140
+ * `class X extends Job.make(...) {}` subclass shadows `Function.name`.)
141
+ */
142
+ readonly _tag: Name;
143
+ readonly queue: QueueName;
144
+ /** The store this job's runs live on. */
145
+ readonly store: Context.Key<StoreId, StoreService>;
146
+ readonly payloadSchema: Payload;
147
+ readonly successSchema: Success;
148
+ readonly errorSchema: Error;
149
+ /** JSON codec for the payload — what is actually persisted. */
150
+ readonly payloadJsonSchema: Schema.toCodecJson<Payload>;
151
+ /** JSON codec for handler exits — what is actually persisted. */
152
+ readonly exitSchema: Schema.toCodecJson<Schema.Exit<Schema.toCodecJson<Success>, Schema.toCodecJson<Error>, Schema.Defect>>;
153
+ readonly idempotencyKey: ((payload: Payload["Type"]) => string) | undefined;
154
+ readonly metadata: ((payload: Payload["Type"]) => Readonly<Record<string, string>>) | undefined;
155
+ readonly defaults: ResolvedDefaults;
156
+ /**
157
+ * Queue this job. Returns the job id. Duplicate ids (via `jobId` or
158
+ * `idempotencyKey`) are a silent no-op returning the existing id.
159
+ */
160
+ readonly enqueue: (payload: Payload["~type.make.in"], options?: EnqueueOptions | undefined) => Effect.Effect<JobId, never, StoreId | Payload["EncodingServices"]>;
161
+ /** Read the current status of a previously enqueued job. */
162
+ readonly poll: (jobId: JobId) => Effect.Effect<Option.Option<JobStatus<Success["Type"], Error["Type"]>>, never, StoreId | Success["DecodingServices"] | Error["DecodingServices"]>;
163
+ /** The job's decoded run ledger, oldest first. */
164
+ readonly attempts: (jobId: JobId) => Effect.Effect<ReadonlyArray<JobAttempt<Success["Type"], Error["Type"]>>, never, StoreId | Success["DecodingServices"] | Error["DecodingServices"]>;
165
+ /**
166
+ * Wait (by polling) until the job finishes, then return its result. Dies
167
+ * if the job id does not exist or the job was failed by the store itself.
168
+ */
169
+ readonly awaitResult: (jobId: JobId, options?: {
170
+ readonly pollSchedule?: Schedule.Schedule<unknown> | undefined;
171
+ } | undefined) => Effect.Effect<Success["Type"], Error["Type"], StoreId | Success["DecodingServices"] | Error["DecodingServices"]>;
172
+ /** `enqueue` + `awaitResult` in one call. */
173
+ readonly execute: (payload: Payload["~type.make.in"], options?: EnqueueOptions | undefined) => Effect.Effect<Success["Type"], Error["Type"], StoreId | Payload["EncodingServices"] | Success["DecodingServices"] | Error["DecodingServices"]>;
174
+ /**
175
+ * Re-run a failed job with a fresh attempt budget. The run ledger is
176
+ * preserved. (The admin op behind a dashboard's "retry" button.)
177
+ */
178
+ readonly retry: (jobId: JobId) => Effect.Effect<void, JobNotFoundError | JobNotRetryableError, StoreId>;
179
+ /**
180
+ * Attach the handler that processes this job, as a layer to provide on top
181
+ * of `Worker.layer` (bound to the same store).
182
+ */
183
+ readonly toLayer: <R>(handler: (payload: Payload["Type"], context: JobContext) => Effect.Effect<Success["Type"], Error["Type"], R>, options?: RegisterOptions | undefined) => Layer.Layer<never, never, Worker | R | Payload["DecodingServices"] | Success["EncodingServices"] | Error["EncodingServices"]>;
184
+ }
185
+ /**
186
+ * @since 0.1.0
187
+ */
188
+ export interface Any {
189
+ readonly [TypeId]: typeof TypeId;
190
+ readonly _tag: string;
191
+ readonly queue: QueueName;
192
+ }
193
+ /**
194
+ * Define a job.
195
+ *
196
+ * @since 0.1.0
197
+ */
198
+ export declare const make: <const Name extends string, Payload extends Schema.Struct.Fields | AnyStructSchema, Success extends Schema.Top = Schema.Void, Error extends Schema.Top = Schema.Never, StoreId = JobStore>(name: Name, options: {
199
+ readonly payload: Payload;
200
+ readonly success?: Success | undefined;
201
+ readonly error?: Error | undefined;
202
+ /**
203
+ * Derive a stable job id from the payload. Enqueueing the same key twice
204
+ * while the first job still exists is a no-op (returns the existing id).
205
+ */
206
+ readonly idempotencyKey?: ((payload: Payload extends Schema.Struct.Fields ? Schema.Struct.Type<Payload> : Payload["Type"]) => string) | undefined;
207
+ /**
208
+ * Derive queryable business context from the payload (flat string map, so
209
+ * every driver can index it). Merged with per-enqueue `metadata`.
210
+ */
211
+ readonly metadata?: ((payload: Payload extends Schema.Struct.Fields ? Schema.Struct.Type<Payload> : Payload["Type"]) => Readonly<Record<string, string>>) | undefined;
212
+ /** The queue this job runs on. Default `"default"`. */
213
+ readonly queue?: string | undefined;
214
+ /**
215
+ * The store this job's runs live on (a `JobStore.named(...)` key).
216
+ * Default: the default `JobStore`.
217
+ */
218
+ readonly store?: Context.Key<StoreId, StoreService> | undefined;
219
+ readonly defaults?: JobOptions | undefined;
220
+ }) => Job<Name, Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload, Success, Error, StoreId>;
221
+ export {};
222
+ //# sourceMappingURL=Job.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Job.d.ts","sourceRoot":"","sources":["../src/Job.ts"],"names":[],"mappings":"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"}