@fungi.computer/watchdog 0.0.0-stage → 0.0.1

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.
@@ -0,0 +1,336 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import { WatchdogError } from "./internal/watchdog-error.js";
4
+ /** The public queued-job grammar. Payload is deliberately uninterpreted. */
5
+ export declare const WatchdogQueuedJobSchema: Schema.Struct<{
6
+ readonly state: Schema.Literal<"queued">;
7
+ readonly jobId: Schema.String;
8
+ readonly queue: Schema.String;
9
+ readonly lane: Schema.String;
10
+ readonly priority: Schema.Int;
11
+ readonly payload: Schema.Unknown;
12
+ readonly recovery: Schema.optional<Schema.Struct<{
13
+ readonly maxRecoveries: Schema.Int;
14
+ }>>;
15
+ }>;
16
+ /** Generic durable work waiting for execution. */
17
+ export type WatchdogQueuedJob = Schema.Schema.Type<typeof WatchdogQueuedJobSchema>;
18
+ /** The public terminal-job grammar. */
19
+ export declare const WatchdogSettledJobSchema: Schema.Struct<{
20
+ readonly state: Schema.Literal<"settled">;
21
+ readonly outcome: Schema.Literals<readonly ["completed", "failed", "cancelled", "outcome_unknown", "interrupted"]>;
22
+ /** Why a job failed or exhausted disappearance recovery, when known. */
23
+ readonly failureReason: Schema.optionalKey<Schema.String>;
24
+ readonly jobId: Schema.String;
25
+ readonly queue: Schema.String;
26
+ readonly lane: Schema.String;
27
+ readonly priority: Schema.Int;
28
+ readonly payload: Schema.Unknown;
29
+ readonly recovery: Schema.optional<Schema.Struct<{
30
+ readonly maxRecoveries: Schema.Int;
31
+ }>>;
32
+ }>;
33
+ /** Generic durable work with a terminal outcome. */
34
+ export type WatchdogSettledJob = Schema.Schema.Type<typeof WatchdogSettledJobSchema>;
35
+ /** One bounded terminal-job recovery record with its durable ordering cursor. */
36
+ export type WatchdogSettledRecord = Readonly<{
37
+ sequence: number;
38
+ job: WatchdogSettledJob;
39
+ }>;
40
+ /** The public running-job grammar. Private claim data is deliberately absent. */
41
+ export declare const WatchdogRunningJobSchema: Schema.Struct<{
42
+ readonly state: Schema.Literal<"running">;
43
+ readonly jobId: Schema.String;
44
+ readonly queue: Schema.String;
45
+ readonly lane: Schema.String;
46
+ readonly priority: Schema.Int;
47
+ readonly payload: Schema.Unknown;
48
+ readonly recovery: Schema.optional<Schema.Struct<{
49
+ readonly maxRecoveries: Schema.Int;
50
+ }>>;
51
+ }>;
52
+ /** Generic durable work held by Watchdog's private claim. */
53
+ export type WatchdogRunningJob = Schema.Schema.Type<typeof WatchdogRunningJobSchema>;
54
+ /** The complete public job lifecycle grammar. */
55
+ export declare const WatchdogStoredJobSchema: Schema.Union<readonly [Schema.Struct<{
56
+ readonly state: Schema.Literal<"queued">;
57
+ readonly jobId: Schema.String;
58
+ readonly queue: Schema.String;
59
+ readonly lane: Schema.String;
60
+ readonly priority: Schema.Int;
61
+ readonly payload: Schema.Unknown;
62
+ readonly recovery: Schema.optional<Schema.Struct<{
63
+ readonly maxRecoveries: Schema.Int;
64
+ }>>;
65
+ }>, Schema.Struct<{
66
+ readonly state: Schema.Literal<"running">;
67
+ readonly jobId: Schema.String;
68
+ readonly queue: Schema.String;
69
+ readonly lane: Schema.String;
70
+ readonly priority: Schema.Int;
71
+ readonly payload: Schema.Unknown;
72
+ readonly recovery: Schema.optional<Schema.Struct<{
73
+ readonly maxRecoveries: Schema.Int;
74
+ }>>;
75
+ }>, Schema.Struct<{
76
+ readonly state: Schema.Literal<"settled">;
77
+ readonly outcome: Schema.Literals<readonly ["completed", "failed", "cancelled", "outcome_unknown", "interrupted"]>;
78
+ /** Why a job failed or exhausted disappearance recovery, when known. */
79
+ readonly failureReason: Schema.optionalKey<Schema.String>;
80
+ readonly jobId: Schema.String;
81
+ readonly queue: Schema.String;
82
+ readonly lane: Schema.String;
83
+ readonly priority: Schema.Int;
84
+ readonly payload: Schema.Unknown;
85
+ readonly recovery: Schema.optional<Schema.Struct<{
86
+ readonly maxRecoveries: Schema.Int;
87
+ }>>;
88
+ }>]>;
89
+ /** Any publicly readable lifecycle state for a generic job. */
90
+ export type WatchdogStoredJob = Schema.Schema.Type<typeof WatchdogStoredJobSchema>;
91
+ /** Terminal decisions returned by the execution adapter. */
92
+ export declare const WatchdogExecutionDecisionSchema: Schema.Union<readonly [Schema.Struct<{
93
+ readonly kind: Schema.Literal<"completed">;
94
+ }>, Schema.Struct<{
95
+ readonly kind: Schema.Literal<"failed">;
96
+ readonly reason: Schema.optionalKey<Schema.String>;
97
+ }>, Schema.Struct<{
98
+ readonly kind: Schema.Literal<"cancelled">;
99
+ }>, Schema.Struct<{
100
+ readonly kind: Schema.Literal<"outcome_unknown">;
101
+ }>]>;
102
+ /** Terminal decision returned by the host executor. */
103
+ export type WatchdogExecutionDecision = Schema.Schema.Type<typeof WatchdogExecutionDecisionSchema>;
104
+ /** Durable cancellation requested by the enclosing owner. */
105
+ export declare const WatchdogCancellationIntentSchema: Schema.Struct<{
106
+ readonly jobId: Schema.String;
107
+ readonly cancellationId: Schema.String;
108
+ readonly onlyIfQueued: Schema.optional<Schema.Boolean>;
109
+ }>;
110
+ /** Owner-issued durable cancellation intent. */
111
+ export type WatchdogCancellationIntent = Schema.Schema.Type<typeof WatchdogCancellationIntentSchema>;
112
+ /** Result of recording an owner cancellation in its surrounding transaction. */
113
+ export type WatchdogCancellationDecision = Readonly<{
114
+ status: "recorded" | "replayed";
115
+ intent: WatchdogCancellationIntent;
116
+ }> | Readonly<{
117
+ status: "conflict" | "missing";
118
+ }>;
119
+ /** Narrow synchronous seam for owner-atomic queue inspection, ingest, and cancellation. */
120
+ export type WatchdogTransactionalProjection = Readonly<{
121
+ /** Reads the bounded current queue head inside an enclosing owner transaction. */
122
+ readQueueHead: (queue: string) => WatchdogQueueHead;
123
+ /** Reads the most recently settled job of one queue, if any. */
124
+ readLatestSettledJob: (queue: string) => WatchdogSettledJob | undefined;
125
+ /** Applies the same durable queued-row mutation as store.ingest. */
126
+ enqueueAcceptedJob: (job: WatchdogQueuedJob) => void;
127
+ /** Records or replays cancellation without interpreting the payload. */
128
+ recordCancellationIntent: (intent: WatchdogCancellationIntent) => WatchdogCancellationDecision;
129
+ }>;
130
+ /** Results returned by public job inspection. */
131
+ export declare const WatchdogReadResultSchema: Schema.Union<readonly [Schema.Struct<{
132
+ readonly status: Schema.Literal<"found">;
133
+ readonly job: Schema.Union<readonly [Schema.Struct<{
134
+ readonly state: Schema.Literal<"queued">;
135
+ readonly jobId: Schema.String;
136
+ readonly queue: Schema.String;
137
+ readonly lane: Schema.String;
138
+ readonly priority: Schema.Int;
139
+ readonly payload: Schema.Unknown;
140
+ readonly recovery: Schema.optional<Schema.Struct<{
141
+ readonly maxRecoveries: Schema.Int;
142
+ }>>;
143
+ }>, Schema.Struct<{
144
+ readonly state: Schema.Literal<"running">;
145
+ readonly jobId: Schema.String;
146
+ readonly queue: Schema.String;
147
+ readonly lane: Schema.String;
148
+ readonly priority: Schema.Int;
149
+ readonly payload: Schema.Unknown;
150
+ readonly recovery: Schema.optional<Schema.Struct<{
151
+ readonly maxRecoveries: Schema.Int;
152
+ }>>;
153
+ }>, Schema.Struct<{
154
+ readonly state: Schema.Literal<"settled">;
155
+ readonly outcome: Schema.Literals<readonly ["completed", "failed", "cancelled", "outcome_unknown", "interrupted"]>;
156
+ /** Why a job failed or exhausted disappearance recovery, when known. */
157
+ readonly failureReason: Schema.optionalKey<Schema.String>;
158
+ readonly jobId: Schema.String;
159
+ readonly queue: Schema.String;
160
+ readonly lane: Schema.String;
161
+ readonly priority: Schema.Int;
162
+ readonly payload: Schema.Unknown;
163
+ readonly recovery: Schema.optional<Schema.Struct<{
164
+ readonly maxRecoveries: Schema.Int;
165
+ }>>;
166
+ }>]>;
167
+ readonly cancellation: Schema.Union<readonly [Schema.Struct<{
168
+ readonly status: Schema.Literal<"absent">;
169
+ }>, Schema.Struct<{
170
+ readonly status: Schema.Literal<"present">;
171
+ readonly cancellation: Schema.Struct<{
172
+ readonly jobId: Schema.String;
173
+ readonly cancellationId: Schema.String;
174
+ readonly onlyIfQueued: Schema.optional<Schema.Boolean>;
175
+ }>;
176
+ }>]>;
177
+ }>, Schema.Struct<{
178
+ readonly status: Schema.Literal<"missing">;
179
+ }>]>;
180
+ /** Result of reading one generic job and its cancellation state. */
181
+ export type WatchdogReadResult = Schema.Schema.Type<typeof WatchdogReadResultSchema>;
182
+ /** Bounded current-work projection for one generic queue. */
183
+ export declare const WatchdogQueueHeadSchema: Schema.Union<readonly [Schema.Struct<{
184
+ readonly state: Schema.Literal<"idle">;
185
+ }>, Schema.Struct<{
186
+ readonly state: Schema.Literals<readonly ["queued", "running"]>;
187
+ readonly jobId: Schema.String;
188
+ readonly cancellationRequested: Schema.Boolean;
189
+ }>]>;
190
+ /** Current queued or running job for one queue, excluding terminal history. */
191
+ export type WatchdogQueueHead = Schema.Schema.Type<typeof WatchdogQueueHeadSchema>;
192
+ /** One queue's state after a durable transition. */
193
+ export type WatchdogQueueTransition = Readonly<{
194
+ queue: string;
195
+ /** Current queued or running head after the transition. */
196
+ head: WatchdogQueueHead;
197
+ /** The job this transition settled, when it settled one. */
198
+ settled?: WatchdogSettledJob;
199
+ }>;
200
+ /**
201
+ * Owner projection written inside the same SQLite transaction as every queue
202
+ * transition: ingest, claim, cancellation, settlement, recovery and purge. It
203
+ * must be a deterministic owner-local write. A throw fails the transition
204
+ * exactly like any other storage failure.
205
+ */
206
+ export type WatchdogQueueProjector = (transition: WatchdogQueueTransition) => void;
207
+ /** Results returned by idempotent public ingest. */
208
+ export declare const WatchdogIngestResultSchema: Schema.Union<readonly [Schema.Struct<{
209
+ readonly status: Schema.Literal<"queued">;
210
+ }>, Schema.Struct<{
211
+ readonly status: Schema.Literal<"duplicate">;
212
+ }>]>;
213
+ /** Result of idempotent durable ingest. */
214
+ export type WatchdogIngestResult = Schema.Schema.Type<typeof WatchdogIngestResultSchema>;
215
+ /** Results returned by durable cancellation recording. */
216
+ export declare const WatchdogCancellationResultSchema: Schema.Union<readonly [Schema.Struct<{
217
+ readonly status: Schema.Literals<readonly ["recorded", "replayed"]>;
218
+ }>, Schema.Struct<{
219
+ readonly status: Schema.Literals<readonly ["conflict", "missing"]>;
220
+ }>]>;
221
+ /** Result of recording a cancellation through the asynchronous runtime. */
222
+ export type WatchdogCancellationResult = Schema.Schema.Type<typeof WatchdogCancellationResultSchema>;
223
+ /** Effect-native host executor for one generic running job. */
224
+ export type EffectWatchdogExecutor = Readonly<{
225
+ /** Runs one generic claimed job until a terminal decision. */
226
+ execute: (job: WatchdogRunningJob) => Effect.Effect<WatchdogExecutionDecision, WatchdogError>;
227
+ }>;
228
+ /** Scalar grammar shared by native SQLite adapters and their storage owners. */
229
+ export declare const watchdogSqliteValueSchema: Schema.Union<readonly [Schema.instanceOf<ArrayBuffer, unknown>, Schema.String, Schema.Number, Schema.Null]>;
230
+ /** A native row is a scalar record; each query owner decodes its own projection. */
231
+ export declare const watchdogSqliteRowSchema: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.instanceOf<ArrayBuffer, unknown>, Schema.String, Schema.Number, Schema.Null]>>;
232
+ /** Parsed native SQLite scalar shared by the durable storage adapters. */
233
+ export type WatchdogSqliteValue = Schema.Schema.Type<typeof watchdogSqliteValueSchema>;
234
+ /** Parsed scalar record; query-specific columns require their owner schema. */
235
+ export type WatchdogSqliteRow = Schema.Schema.Type<typeof watchdogSqliteRowSchema>;
236
+ /** Structural result cursor required by Watchdog's SQLite implementation. */
237
+ export type WatchdogSqliteCursor = Readonly<{
238
+ /** Materialize raw rows, never a caller-selected projection type. */
239
+ toArray: () => readonly WatchdogSqliteRow[];
240
+ }>;
241
+ /** Native owner-local SQLite capability consumed by Watchdog. */
242
+ export type WatchdogSqliteOwner = Readonly<{
243
+ /** Synchronous SQLite execution owned by the surrounding runtime. */
244
+ sql: Readonly<{
245
+ /** Execute one statement; its query owner parses the returned rows. */
246
+ exec: (statement: string, ...bindings: WatchdogSqliteValue[]) => WatchdogSqliteCursor;
247
+ }>;
248
+ /** Join one synchronous native owner transaction. */
249
+ transactionSync: <A>(operation: () => A) => A;
250
+ }>;
251
+ /** Effect-native construction inputs for the deep SQLite Watchdog. */
252
+ export type EffectWatchdogOptions = Readonly<{
253
+ /** Optional opaque lane, 1 to 512 characters, with its own single-flight slot. */
254
+ concurrentLane?: string;
255
+ /** Owner-local SQLite and synchronous transaction capability. */
256
+ owner: WatchdogSqliteOwner;
257
+ /** Answers whether a running job is still alive. */
258
+ isAlive: (job: WatchdogRunningJob) => Effect.Effect<boolean, WatchdogError>;
259
+ executor: EffectWatchdogExecutor;
260
+ /** Wake scheduling owned by the host. */
261
+ wake: Readonly<{
262
+ /** Recomputes the next wake from durable state. */
263
+ recompute: () => Effect.Effect<void, WatchdogError>;
264
+ }>;
265
+ /** Optional owner projection written in each transition's transaction. */
266
+ project?: WatchdogQueueProjector | undefined;
267
+ }>;
268
+ /**
269
+ * A running job's liveness probe failed. The job is left exactly as it was:
270
+ * Watchdog neither treats it as alive nor fabricates a disappearance.
271
+ */
272
+ export type WatchdogLivenessUnknown = Readonly<{
273
+ status: "liveness_unknown";
274
+ jobId: string;
275
+ /** The closed category of the probe's failure. */
276
+ reason: WatchdogError["cause"];
277
+ }>;
278
+ /** Result of one bounded deep tick. */
279
+ export type WatchdogTickResult = Readonly<{
280
+ status: "busy";
281
+ }> | Readonly<{
282
+ status: "no_ready";
283
+ }> | WatchdogLivenessUnknown | Readonly<{
284
+ status: "settled";
285
+ job: WatchdogSettledJob;
286
+ }>;
287
+ /** Owner-issued terminal lane refusal; reason is an owner code, at most 200 characters. */
288
+ export type WatchdogLaneFailure = Readonly<{
289
+ /** Exact opaque lane, 1 to 512 characters. */
290
+ lane: string;
291
+ /** Owner-defined failure code, 1 to 200 characters; never provider text. */
292
+ reason: string;
293
+ }>;
294
+ /** One bounded refusal batch; call again only while more is true. */
295
+ export type WatchdogLaneFailureResult = Readonly<{
296
+ /** Number newly settled in this call, at most 256. */
297
+ settled: number;
298
+ /** Whether this lane still has queued or running work. */
299
+ more: boolean;
300
+ }>;
301
+ /** Effect-native Watchdog runtime surface. */
302
+ export type WatchdogRuntimeEffect = Readonly<{
303
+ /** Whether any queued or running job still needs owner reconciliation. */
304
+ hasPendingWork: Effect.Effect<boolean, WatchdogError>;
305
+ /** Persists one queued job. */
306
+ ingest: (job: WatchdogQueuedJob) => Effect.Effect<WatchdogIngestResult, WatchdogError>;
307
+ /** Reads one durable job. */
308
+ read: (jobId: string) => Effect.Effect<WatchdogReadResult, WatchdogError>;
309
+ /** Reads the bounded current head of one generic queue. */
310
+ readQueueHead: (queue: string) => Effect.Effect<WatchdogQueueHead, WatchdogError>;
311
+ /** Reads a bounded terminal prefix of the serial partition for owner recovery. */
312
+ readSettled: (limit: number, afterSequence?: number) => Effect.Effect<readonly WatchdogSettledRecord[], WatchdogError>;
313
+ /** Runs one bounded generic lifecycle tick. */
314
+ tick: () => Effect.Effect<WatchdogTickResult, WatchdogError>;
315
+ /** Records cancellation and signals live execution. */
316
+ cancel: (input: WatchdogCancellationIntent) => Effect.Effect<WatchdogCancellationResult, WatchdogError>;
317
+ /** Fail up to 256 lane jobs, running first, with transactional projection.
318
+ * Refused claims are invalidated; both late success and failure reports are rejected.
319
+ */
320
+ failLane: (input: WatchdogLaneFailure) => Effect.Effect<WatchdogLaneFailureResult, WatchdogError>;
321
+ /** Removes queued jobs in one queue. */
322
+ purge: (queue: string) => Effect.Effect<void, WatchdogError>;
323
+ /** Joins accepted enqueue and cancellation to an existing owner transaction. */
324
+ transactional: WatchdogTransactionalProjection;
325
+ }>;
326
+ /** Runtime configured with one independent host lane at construction. */
327
+ export type ConcurrentWatchdogRuntimeEffect = WatchdogRuntimeEffect & Readonly<{
328
+ /** Run one deep tick in the configured concurrent partition. */
329
+ tickConcurrent: () => Effect.Effect<WatchdogTickResult, WatchdogError>;
330
+ }>;
331
+ /** Construct two private single-flight slots over the same owner and job table. */
332
+ export declare function makeRuntime(options: EffectWatchdogOptions & {
333
+ concurrentLane: string;
334
+ }): Effect.Effect<ConcurrentWatchdogRuntimeEffect, WatchdogError>;
335
+ /** Construct a serial owner-local runtime. */
336
+ export declare function makeRuntime(options: EffectWatchdogOptions): Effect.Effect<WatchdogRuntimeEffect, WatchdogError>;
package/dist/effect.js ADDED
@@ -0,0 +1,140 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
3
+ import { bindWatchdogEngine } from "./internal/runtime.js";
4
+ import { openSqliteState } from "./internal/sqlite-store.js";
5
+ import { WatchdogError } from "./internal/watchdog-error.js";
6
+ import { laneSchema } from "./internal/lane.js";
7
+ const BoundedString = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256));
8
+ /** Closed-vocabulary code for why work failed; never free-form provider text. */
9
+ const FailureReason = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(200));
10
+ const Priority = Schema.Int.check(Schema.isGreaterThanOrEqualTo(Number.MIN_SAFE_INTEGER), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
11
+ const RecoveryCount = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
12
+ const RecoveryPolicy = Schema.Struct({
13
+ maxRecoveries: RecoveryCount,
14
+ });
15
+ const PublicJobFields = {
16
+ jobId: BoundedString,
17
+ queue: BoundedString,
18
+ lane: laneSchema,
19
+ priority: Priority,
20
+ payload: Schema.Unknown,
21
+ recovery: Schema.optional(RecoveryPolicy),
22
+ };
23
+ /** The public queued-job grammar. Payload is deliberately uninterpreted. */
24
+ export const WatchdogQueuedJobSchema = Schema.Struct({
25
+ ...PublicJobFields,
26
+ state: Schema.Literal("queued"),
27
+ });
28
+ /** The public terminal-job grammar. */
29
+ export const WatchdogSettledJobSchema = Schema.Struct({
30
+ ...PublicJobFields,
31
+ state: Schema.Literal("settled"),
32
+ outcome: Schema.Literals([
33
+ "completed",
34
+ "failed",
35
+ "cancelled",
36
+ "outcome_unknown",
37
+ "interrupted",
38
+ ]),
39
+ /** Why a job failed or exhausted disappearance recovery, when known. */
40
+ failureReason: Schema.optionalKey(FailureReason),
41
+ });
42
+ /** The public running-job grammar. Private claim data is deliberately absent. */
43
+ export const WatchdogRunningJobSchema = Schema.Struct({
44
+ ...PublicJobFields,
45
+ state: Schema.Literal("running"),
46
+ });
47
+ /** The complete public job lifecycle grammar. */
48
+ export const WatchdogStoredJobSchema = Schema.Union([
49
+ WatchdogQueuedJobSchema,
50
+ WatchdogRunningJobSchema,
51
+ WatchdogSettledJobSchema,
52
+ ]);
53
+ /** Terminal decisions returned by the execution adapter. */
54
+ export const WatchdogExecutionDecisionSchema = Schema.Union([
55
+ Schema.Struct({ kind: Schema.Literal("completed") }),
56
+ Schema.Struct({
57
+ kind: Schema.Literal("failed"),
58
+ reason: Schema.optionalKey(FailureReason),
59
+ }),
60
+ Schema.Struct({ kind: Schema.Literal("cancelled") }),
61
+ Schema.Struct({ kind: Schema.Literal("outcome_unknown") }),
62
+ ]);
63
+ /** Durable cancellation requested by the enclosing owner. */
64
+ export const WatchdogCancellationIntentSchema = Schema.Struct({
65
+ jobId: BoundedString,
66
+ cancellationId: BoundedString,
67
+ onlyIfQueued: Schema.optional(Schema.Boolean),
68
+ });
69
+ const CancellationReadSchema = Schema.Union([
70
+ Schema.Struct({ status: Schema.Literal("absent") }),
71
+ Schema.Struct({
72
+ status: Schema.Literal("present"),
73
+ cancellation: WatchdogCancellationIntentSchema,
74
+ }),
75
+ ]);
76
+ /** Results returned by public job inspection. */
77
+ export const WatchdogReadResultSchema = Schema.Union([
78
+ Schema.Struct({
79
+ status: Schema.Literal("found"),
80
+ job: WatchdogStoredJobSchema,
81
+ cancellation: CancellationReadSchema,
82
+ }),
83
+ Schema.Struct({ status: Schema.Literal("missing") }),
84
+ ]);
85
+ /** Bounded current-work projection for one generic queue. */
86
+ export const WatchdogQueueHeadSchema = Schema.Union([
87
+ Schema.Struct({ state: Schema.Literal("idle") }),
88
+ Schema.Struct({
89
+ state: Schema.Literals(["queued", "running"]),
90
+ jobId: BoundedString,
91
+ cancellationRequested: Schema.Boolean,
92
+ }),
93
+ ]);
94
+ /** Results returned by idempotent public ingest. */
95
+ export const WatchdogIngestResultSchema = Schema.Union([
96
+ Schema.Struct({ status: Schema.Literal("queued") }),
97
+ Schema.Struct({ status: Schema.Literal("duplicate") }),
98
+ ]);
99
+ /** Results returned by durable cancellation recording. */
100
+ export const WatchdogCancellationResultSchema = Schema.Union([
101
+ Schema.Struct({ status: Schema.Literals(["recorded", "replayed"]) }),
102
+ Schema.Struct({ status: Schema.Literals(["conflict", "missing"]) }),
103
+ ]);
104
+ /** Scalar grammar shared by native SQLite adapters and their storage owners. */
105
+ export const watchdogSqliteValueSchema = Schema.Union([
106
+ Schema.instanceOf(ArrayBuffer),
107
+ Schema.String,
108
+ Schema.Number,
109
+ Schema.Null,
110
+ ]);
111
+ /** A native row is a scalar record; each query owner decodes its own projection. */
112
+ export const watchdogSqliteRowSchema = Schema.Record(Schema.String, watchdogSqliteValueSchema);
113
+ export function makeRuntime(options) {
114
+ return Effect.try({
115
+ try: () => {
116
+ const lane = options.concurrentLane === undefined
117
+ ? undefined
118
+ : Schema.decodeUnknownSync(laneSchema)(options.concurrentLane);
119
+ const state = openSqliteState(options.owner, options.project, lane === undefined ? undefined : { lane, include: false });
120
+ const serial = bindWatchdogEngine(options, state.store);
121
+ const base = { ...serial, transactional: state.transactional };
122
+ if (lane === undefined)
123
+ return Object.freeze(base);
124
+ const concurrent = bindWatchdogEngine(options, state.partition({ lane, include: true }));
125
+ return Object.freeze({
126
+ ...base,
127
+ tickConcurrent: concurrent.tick,
128
+ failLane: (input) => input.lane === lane
129
+ ? concurrent.failLane(input)
130
+ : serial.failLane(input),
131
+ cancel: (intent) => serial
132
+ .read(intent.jobId)
133
+ .pipe(Effect.flatMap((found) => found.status === "found" && found.job.lane === lane
134
+ ? concurrent.cancel(intent)
135
+ : serial.cancel(intent))),
136
+ });
137
+ },
138
+ catch: (error) => new WatchdogError("store_unavailable", error),
139
+ });
140
+ }
@@ -0,0 +1,65 @@
1
+ import * as core from "./effect.js";
2
+ export { WatchdogError } from "./internal/watchdog-error.js";
3
+ export { WatchdogCancellationIntentSchema, WatchdogCancellationResultSchema, WatchdogExecutionDecisionSchema, WatchdogIngestResultSchema, WatchdogQueuedJobSchema, WatchdogQueueHeadSchema, WatchdogReadResultSchema, WatchdogRunningJobSchema, WatchdogSettledJobSchema, WatchdogStoredJobSchema, } from "./effect.js";
4
+ export type { EffectWatchdogOptions, EffectWatchdogExecutor, WatchdogCancellationDecision, WatchdogCancellationIntent, WatchdogCancellationResult, WatchdogExecutionDecision, WatchdogIngestResult, WatchdogLivenessUnknown, WatchdogLaneFailure, WatchdogLaneFailureResult, WatchdogQueuedJob, WatchdogQueueHead, WatchdogQueueProjector, WatchdogQueueTransition, WatchdogReadResult, WatchdogRunningJob, WatchdogRuntimeEffect, ConcurrentWatchdogRuntimeEffect, WatchdogSettledJob, WatchdogSqliteCursor, WatchdogSqliteOwner, WatchdogSqliteValue, WatchdogSqliteRow, WatchdogStoredJob, WatchdogTickResult, WatchdogTransactionalProjection, } from "./effect.js";
5
+ /** Promise host executor for one generic running job. */
6
+ export type WatchdogExecutor = (job: core.WatchdogRunningJob, signal: AbortSignal) => Promise<core.WatchdogExecutionDecision>;
7
+ /** Options for constructing the Promise facade. */
8
+ export type WatchdogOptions = Readonly<{
9
+ /** Optional opaque lane with its own single-flight execution slot. */
10
+ concurrentLane?: string;
11
+ /** Owner-local SQLite and synchronous transaction capability. */
12
+ owner: core.WatchdogSqliteOwner;
13
+ /** Owner liveness probe for disappearance recovery. */
14
+ isAlive: (job: core.WatchdogRunningJob) => Promise<boolean>;
15
+ /** Executes one generic claimed job. */
16
+ execute: WatchdogExecutor;
17
+ /** Recomputes the owner wake after durable state changes. */
18
+ wake: Readonly<{
19
+ /** Arms the next wake from current owner state. */
20
+ recompute: () => Promise<void>;
21
+ }>;
22
+ /** Optional owner projection written in each transition's transaction. */
23
+ project?: core.WatchdogQueueProjector;
24
+ }>;
25
+ /** Promise facade over the canonical Effect runtime. */
26
+ export type WatchdogRuntime = Readonly<{
27
+ /** Whether any queued or running job still needs owner reconciliation. */
28
+ hasPendingWork: () => Promise<boolean>;
29
+ /** Persists one queued job. */
30
+ ingest: (job: core.WatchdogQueuedJob) => Promise<core.WatchdogIngestResult>;
31
+ /** Reads one durable job. */
32
+ read: (jobId: string) => Promise<core.WatchdogReadResult>;
33
+ /** Reads the bounded current head of one generic queue. */
34
+ readQueueHead: (queue: string) => Promise<core.WatchdogQueueHead>;
35
+ /** Runs one bounded generic lifecycle tick. */
36
+ tick: () => Promise<core.WatchdogTickResult>;
37
+ /** Records cancellation and signals a live execution when present. */
38
+ cancel: (input: core.WatchdogCancellationIntent) => Promise<core.WatchdogCancellationResult>;
39
+ /** Terminally fail a bounded lane batch; repeat while more is true.
40
+ * Refused claims are invalidated; both late success and failure reports are rejected.
41
+ */
42
+ failLane: (input: core.WatchdogLaneFailure) => Promise<core.WatchdogLaneFailureResult>;
43
+ /** Removes queued jobs in one queue. */
44
+ purge: (queue: string) => Promise<void>;
45
+ /** Joins accepted enqueue and cancellation to an existing owner transaction. */
46
+ transactional: core.WatchdogTransactionalProjection;
47
+ }>;
48
+ /** Promise runtime with the construction-time concurrent lane. */
49
+ export type ConcurrentWatchdogRuntime = WatchdogRuntime & Readonly<{
50
+ /** Run one deep tick in the configured concurrent partition. */
51
+ tickConcurrent: () => Promise<core.WatchdogTickResult>;
52
+ }>;
53
+ /** Factory for the Promise facade. */
54
+ export type WatchdogApi = Readonly<{
55
+ /** Constructs one Promise facade over the Effect runtime. */
56
+ make: {
57
+ (options: WatchdogOptions & {
58
+ concurrentLane: string;
59
+ }): Promise<ConcurrentWatchdogRuntime>;
60
+ (options: WatchdogOptions): Promise<WatchdogRuntime>;
61
+ };
62
+ }>;
63
+ /** Promise facade over the canonical Effect-owned runtime. */
64
+ export declare const Watchdog: WatchdogApi;
65
+ export { watchdogSqliteValueSchema, watchdogSqliteRowSchema, } from "./effect.js";
package/dist/index.js ADDED
@@ -0,0 +1,41 @@
1
+ import * as Cause from "effect/Cause";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Exit from "effect/Exit";
4
+ import * as Option from "effect/Option";
5
+ import * as core from "./effect.js";
6
+ import { adaptPromiseWatchdogHost } from "./internal/promise-watchdog-host.js";
7
+ import { WatchdogError } from "./internal/watchdog-error.js";
8
+ export { WatchdogError } from "./internal/watchdog-error.js";
9
+ export { WatchdogCancellationIntentSchema, WatchdogCancellationResultSchema, WatchdogExecutionDecisionSchema, WatchdogIngestResultSchema, WatchdogQueuedJobSchema, WatchdogQueueHeadSchema, WatchdogReadResultSchema, WatchdogRunningJobSchema, WatchdogSettledJobSchema, WatchdogStoredJobSchema, } from "./effect.js";
10
+ const runPromise = (effect) => Effect.runPromiseExit(effect).then((exit) => {
11
+ if (Exit.isSuccess(exit))
12
+ return exit.value;
13
+ const failure = Option.getOrUndefined(Cause.findErrorOption(exit.cause));
14
+ const error = failure ?? Cause.squash(exit.cause);
15
+ return Promise.reject(error instanceof Error ? error : new Error(String(error)));
16
+ });
17
+ function make(options) {
18
+ const adapted = adaptPromiseWatchdogHost(options);
19
+ if (options.concurrentLane === undefined)
20
+ return runPromise(core.makeRuntime(adapted)).then(promiseRuntime);
21
+ return runPromise(core.makeRuntime({ ...adapted, concurrentLane: options.concurrentLane })).then((runtime) => Object.freeze({
22
+ ...promiseRuntime(runtime),
23
+ tickConcurrent: () => runPromise(runtime.tickConcurrent()),
24
+ }));
25
+ }
26
+ function promiseRuntime(runtime) {
27
+ return Object.freeze({
28
+ hasPendingWork: () => runPromise(runtime.hasPendingWork),
29
+ ingest: (job) => runPromise(runtime.ingest(job)),
30
+ read: (jobId) => runPromise(runtime.read(jobId)),
31
+ readQueueHead: (queue) => runPromise(runtime.readQueueHead(queue)),
32
+ tick: () => runPromise(runtime.tick()),
33
+ cancel: (input) => runPromise(runtime.cancel(input)),
34
+ failLane: (input) => runPromise(runtime.failLane(input)),
35
+ purge: (queue) => runPromise(runtime.purge(queue)),
36
+ transactional: runtime.transactional,
37
+ });
38
+ }
39
+ /** Promise facade over the canonical Effect-owned runtime. */
40
+ export const Watchdog = Object.freeze({ make });
41
+ export { watchdogSqliteValueSchema, watchdogSqliteRowSchema, } from "./effect.js";
@@ -0,0 +1,17 @@
1
+ import type { WatchdogExecutor, WatchdogRuntime } from "../index.js";
2
+ import type { SqlDatabase } from "./async-sql.js";
3
+ /** Host execution and wake scheduling for a shared-database Watchdog. */
4
+ export type AsyncWatchdogOptions = Readonly<{
5
+ /** Execute one claimed job; honour abort when its lease is lost. */
6
+ execute: WatchdogExecutor;
7
+ /** Recompute a durable wake after a transition commits. */
8
+ wake: Readonly<{
9
+ /** Arm the next durable owner wake. */
10
+ recompute: () => Promise<void>;
11
+ }>;
12
+ /** Clock used consistently for durable leases; defaults to Date.now. */
13
+ now?: () => number;
14
+ }>;
15
+ /** Shared-database runtime; native admission replaces the SQLite projection. */
16
+ export type AsyncWatchdogRuntime = Omit<WatchdogRuntime, "transactional">;
17
+ export declare function createAsyncRuntime(database: SqlDatabase, options: AsyncWatchdogOptions): AsyncWatchdogRuntime;
@@ -0,0 +1,39 @@
1
+ import * as Cause from "effect/Cause";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Exit from "effect/Exit";
4
+ import * as Option from "effect/Option";
5
+ import { WatchdogError } from "./watchdog-error.js";
6
+ import { bindWatchdogEngine } from "./runtime.js";
7
+ import { openAsyncStore, heartbeatMs, attempt } from "./async-store.js";
8
+ function run(effect) {
9
+ return Effect.runPromiseExit(effect).then((exit) => {
10
+ if (Exit.isSuccess(exit))
11
+ return exit.value;
12
+ throw (Option.getOrUndefined(Cause.findErrorOption(exit.cause)) ??
13
+ Cause.squash(exit.cause));
14
+ });
15
+ }
16
+ export function createAsyncRuntime(database, options) {
17
+ const state = openAsyncStore(database, options.now ?? Date.now);
18
+ const renew = (jobId) => state.renew(jobId).pipe(Effect.timeout(heartbeatMs), Effect.mapError(() => new WatchdogError("claim_unavailable")));
19
+ const runtime = bindWatchdogEngine({
20
+ isAlive: (job) => state.isAlive(job.jobId),
21
+ executor: {
22
+ execute: (job) => renew(job.jobId).pipe(Effect.andThen(Effect.raceFirst(Effect.tryPromise({
23
+ try: (signal) => options.execute(job, signal),
24
+ catch: () => new WatchdogError("execution_failed"),
25
+ }), Effect.forever(Effect.sleep(heartbeatMs).pipe(Effect.andThen(renew(job.jobId))))))),
26
+ },
27
+ wake: { recompute: () => attempt(options.wake.recompute) },
28
+ }, state.store);
29
+ return {
30
+ hasPendingWork: () => run(runtime.hasPendingWork),
31
+ ingest: (job) => run(runtime.ingest(job)),
32
+ read: (id) => run(runtime.read(id)),
33
+ readQueueHead: (queue) => run(runtime.readQueueHead(queue)),
34
+ tick: () => run(runtime.tick()),
35
+ cancel: (intent) => run(runtime.cancel(intent)),
36
+ failLane: (input) => run(runtime.failLane(input)),
37
+ purge: (queue) => run(runtime.purge(queue)),
38
+ };
39
+ }
@@ -0,0 +1,15 @@
1
+ import { type WatchdogQueuedJob } from "../effect.js";
2
+ export type SqlValue = string | number | null;
3
+ export type SqlRow = Record<string, SqlValue>;
4
+ export type Statement = Readonly<{
5
+ text: string;
6
+ values: readonly SqlValue[];
7
+ }>;
8
+ export type SqlDatabase = Readonly<{
9
+ clock?: string | undefined;
10
+ query: (statement: Statement) => Promise<readonly SqlRow[]>;
11
+ }>;
12
+ export declare const columns = "sequence, job_id, queue_name, lane, priority, payload_json,\n recovery_max, state, outcome, claim_token, recovery_count, cancellation_id, failure_reason";
13
+ export declare function schemaStatements(postgres: boolean): readonly string[];
14
+ export declare function enqueueSelect(source: Statement): Statement;
15
+ export declare function enqueueStatement(input: WatchdogQueuedJob): Statement;