@apisurf/ngn-core 0.2.7 → 0.2.8

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 (3) hide show
  1. package/dist/core.d.ts +459 -11
  2. package/dist/core.js +1244 -238
  3. package/package.json +8 -7
package/dist/core.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { FsNodeFileDescriptor } from '@apisurf/ngn-os';
2
- import { Client, InArgs, ResultSet, InStatement } from '@apisurf/ngn-persistence';
2
+ import { Client, InArgs, ResultSet, InStatement, InValue } from '@apisurf/ngn-persistence';
3
3
  export { initDbFileIfNotExists } from '@apisurf/ngn-persistence';
4
+ import { Cron } from 'croner';
4
5
 
5
6
  type CompilerOptions = {
6
7
  /** Minify output */
@@ -120,6 +121,349 @@ interface TaskSqlite {
120
121
  */
121
122
  declare function closeTaskDatabases(): void;
122
123
 
124
+ /**
125
+ * A length of time as tasks write it: milliseconds, or a string such as
126
+ * `"500ms"`, `"30s"`, `"4m"`, `"2h"` or `"1d"`.
127
+ */
128
+ type Duration = number | `${number}${"ms" | "s" | "m" | "h" | "d"}`;
129
+
130
+ /**
131
+ * Named leases in `locks`.
132
+ *
133
+ * Taking one is a single upsert that only overwrites a row whose lease has
134
+ * run out, so two processes on one file cannot both win, and a lease left by
135
+ * a process that died is free once it expires. Every acquisition gets its own
136
+ * holder token: two runs of one task in one process are two holders.
137
+ */
138
+ declare class LockService {
139
+ private db;
140
+ constructor(db: Client);
141
+ /** The holder token on success, null when someone else holds an unexpired lease. */
142
+ tryAcquire(name: string, ttlMs: number, taskRunId: number | null): Promise<string | null>;
143
+ /** Push the lease out; false when it was lost (it expired and someone took it). */
144
+ renew(name: string, holder: string, ttlMs: number): Promise<boolean>;
145
+ release(name: string, holder: string): Promise<void>;
146
+ }
147
+
148
+ interface LockOptions {
149
+ /**
150
+ * How long the lease lasts if this process stops renewing it (it dies, or
151
+ * the event loop is stuck). Renewed while held. Default one minute.
152
+ */
153
+ ttl?: Duration;
154
+ /**
155
+ * How long to wait for a lock someone else holds: `true` (default) waits
156
+ * until the run's deadline, or for as long as it takes when there is none;
157
+ * `false` gives up at once; a duration waits that long.
158
+ */
159
+ wait?: boolean | Duration;
160
+ }
161
+ /**
162
+ * Thrown by `ctx.lock` when the lock could not be had in time. Tasks can tell
163
+ * it apart by `name`, which survives a second copy of ngn in node_modules where
164
+ * `instanceof` would not.
165
+ */
166
+ declare class LockBusyError extends Error {
167
+ readonly lockName: string;
168
+ name: string;
169
+ constructor(lockName: string);
170
+ }
171
+ interface Held {
172
+ release(): Promise<void>;
173
+ }
174
+ /** The locks one run takes, all recorded against its `task_runs` row. */
175
+ declare function createRunLocks(service: LockService, taskRunId: number): {
176
+ /** `ctx.lock`: run `fn` holding `name`, released however `fn` ends. */
177
+ lock<T>(name: string, fn: () => Promise<T> | T, options: LockOptions, deadline: number | null): Promise<T>;
178
+ /**
179
+ * Take every lock in `names` without waiting, for `exclusive`. Names are
180
+ * taken in sorted order so two runs asking for overlapping sets cannot
181
+ * each hold half. If any is busy, the ones already taken are given back.
182
+ */
183
+ holdAll(names: readonly string[]): Promise<{
184
+ busy: string;
185
+ } | Held>;
186
+ };
187
+ type RunLocks = ReturnType<typeof createRunLocks>;
188
+
189
+ /** Where a job is in its life. `dead` and `done` are final until `reopen`. */
190
+ type JobStatus = "ready" | "leased" | "done" | "dead" | "parked";
191
+
192
+ interface WorkerHandle<W> {
193
+ /** Which worker this is, from 0. */
194
+ index: number;
195
+ /** What `worker.setup` returned for this worker, e.g. a browser tab. */
196
+ resource: W;
197
+ /** The `until` signal: aborts when the run should stop taking on work. */
198
+ signal: AbortSignal;
199
+ /**
200
+ * Hand this job back unworked instead of completing it, by default without
201
+ * spending its attempt.
202
+ */
203
+ release(options?: {
204
+ delay?: Duration;
205
+ refund?: boolean;
206
+ }): void;
207
+ /**
208
+ * Stop this job's group for the rest of the call: this job and any of the
209
+ * group's jobs already claimed are handed back, and no more are claimed.
210
+ * For a site that answered with a bot wall, where every other page would
211
+ * get the same answer.
212
+ */
213
+ stopGroup(options?: {
214
+ delay?: Duration;
215
+ refund?: boolean;
216
+ }): void;
217
+ }
218
+ interface WorkOptions<W> {
219
+ /** Jobs worked at once. Default 1. */
220
+ concurrency?: number;
221
+ /** Most jobs this call takes. Default no limit. */
222
+ max?: number;
223
+ /**
224
+ * Most jobs one group contributes to this call, or a function giving each
225
+ * group its own cap (one site is slower, or touchier, than another). A cap of
226
+ * 0 leaves the group alone. Default no limit.
227
+ */
228
+ perGroup?: number | ((group: string) => number);
229
+ /**
230
+ * When to stop taking on work: a signal, or an epoch-ms time. Jobs already
231
+ * started are finished. Default `ctx.run.signal`.
232
+ */
233
+ until?: AbortSignal | number;
234
+ /** Per-worker resources, made when a worker takes its first job. */
235
+ worker?: {
236
+ setup?: (index: number) => Promise<W> | W;
237
+ teardown?: (resource: W, index: number) => Promise<void> | void;
238
+ };
239
+ }
240
+ type WorkHandler<T, W> = (job: Job<T>, worker: WorkerHandle<W>) => Promise<unknown> | unknown;
241
+ interface WorkSummary {
242
+ done: number;
243
+ /** Attempts that failed, the ones that left their job dead included. */
244
+ failed: number;
245
+ /** Of those, how many jobs had no attempts left. */
246
+ dead: number;
247
+ /** Claimed and handed back unworked. */
248
+ released: number;
249
+ /** Jobs due that this call left for the next one. */
250
+ remaining: number;
251
+ /** How long the oldest ready job has waited, or null when there is none. */
252
+ oldestAgeMs: number | null;
253
+ /** Groups stopped with `stopGroup`. */
254
+ stoppedGroups: string[];
255
+ /** Why the call ended. */
256
+ stoppedBy: "empty" | "deadline" | "max";
257
+ }
258
+
259
+ /** What a queue knows about the run that opened it. */
260
+ interface QueueRunContext {
261
+ /** The task's next tick, for `"next-tick"` backoff. */
262
+ nextAt: number | null;
263
+ /** The run's deadline signal, `work()`'s default `until`. */
264
+ signal?: AbortSignal;
265
+ }
266
+ /**
267
+ * When a failed job may be tried again:
268
+ *
269
+ * - `"next-tick"` (default): not before the task's next scheduled run, so a
270
+ * retry never competes with the run that is failing. Without a schedule it
271
+ * is straight away.
272
+ * - a duration: that long after the failure.
273
+ * - `{ base, max }`: `base`, doubling with each attempt, never above `max`.
274
+ */
275
+ type Backoff = "next-tick" | Duration | {
276
+ base: Duration;
277
+ max?: Duration;
278
+ };
279
+ interface QueueOptions {
280
+ /**
281
+ * The database the queue lives in: any `ctx.sqlite.initDB(...)` instance,
282
+ * or `ctx.sqlite` itself (the default). Keeping it next to your own tables is
283
+ * what lets `addStatement` go in the same batch as the row a job is about.
284
+ */
285
+ db?: {
286
+ client: Client;
287
+ };
288
+ /** Attempts a job gets before it is `dead`. Default 3. */
289
+ maxAttempts?: number;
290
+ backoff?: Backoff;
291
+ /** How long a claim lasts before the job is claimable again. Default 10 minutes. */
292
+ lease?: Duration;
293
+ }
294
+ interface AddJob<T> {
295
+ /** De-duplicates: a key already in this queue, in any status, is not added again. */
296
+ key?: string;
297
+ /** Groups are served fairly and never two at once by `work()`; e.g. a host. */
298
+ group?: string;
299
+ payload?: T;
300
+ /** Higher first, within a group. Default 0. */
301
+ priority?: number;
302
+ /** Not before this long from now. */
303
+ delay?: Duration;
304
+ /** Overrides the queue's `maxAttempts` for this job. */
305
+ maxAttempts?: number;
306
+ }
307
+ interface Job<T> {
308
+ id: number;
309
+ key: string | null;
310
+ group: string | null;
311
+ payload: T;
312
+ priority: number;
313
+ /** Attempts so far, this one included. */
314
+ attempts: number;
315
+ maxAttempts: number;
316
+ /** The error the previous attempt failed with. */
317
+ lastError: string | null;
318
+ createdAt: number;
319
+ }
320
+ interface ClaimOptions {
321
+ limit: number;
322
+ /** At most this many jobs per group. */
323
+ perGroup?: number;
324
+ /** Leave these groups' jobs where they are. */
325
+ skipGroups?: readonly string[];
326
+ /** Leave these jobs where they are, e.g. ones this run already tried. */
327
+ skipIds?: readonly number[];
328
+ }
329
+ /** Which jobs `park`, `reopen` and `prune` act on. Fields combine with AND. */
330
+ interface JobFilter {
331
+ group?: string | readonly string[];
332
+ /** Every group except these. Jobs with no group match. */
333
+ groupNotIn?: readonly string[];
334
+ key?: string | readonly string[];
335
+ }
336
+ interface QueueStats {
337
+ ready: number;
338
+ /** Ready jobs whose `run_after` has passed: what a claim could take now. */
339
+ due: number;
340
+ leased: number;
341
+ done: number;
342
+ dead: number;
343
+ parked: number;
344
+ /** How long the oldest ready job has waited since it was added, or null. */
345
+ oldestReadyAgeMs: number | null;
346
+ }
347
+ /**
348
+ * A named queue over `_ngn_jobs` in one database.
349
+ *
350
+ * Every state change is a single statement guarded by what the caller saw, so
351
+ * two runs (or two processes) working one queue cannot both take a job, and a
352
+ * run whose lease ran out cannot settle a job someone else has since claimed.
353
+ */
354
+ declare class Queue<T = unknown> {
355
+ private readonly client;
356
+ readonly name: string;
357
+ private readonly options;
358
+ private readonly run;
359
+ private readonly maxAttempts;
360
+ private readonly leaseMs;
361
+ constructor(client: Client, name: string, options: QueueOptions, run: QueueRunContext);
362
+ /**
363
+ * The INSERT for one job, to run in your own `batch` beside the row the job
364
+ * is about. A key already in the queue makes it a no-op.
365
+ */
366
+ addStatement(job: AddJob<T>): {
367
+ sql: string;
368
+ args: InValue[];
369
+ };
370
+ /** Add one job or several. Returns how many were new; the rest had keys already queued. */
371
+ add(jobs: AddJob<T> | readonly AddJob<T>[]): Promise<number>;
372
+ /**
373
+ * Take up to `limit` jobs that are due, counting an attempt on each and
374
+ * leasing them for the queue's `lease`.
375
+ *
376
+ * With groups, the jobs are drawn a group at a time: every group's first job
377
+ * before any group's second, groups in a fresh random order each claim. So
378
+ * a claim spreads over as many groups as it can, and when `limit` cuts a
379
+ * round short it is not always the same groups left out.
380
+ */
381
+ claim({ limit, perGroup, skipGroups, skipIds }: ClaimOptions): Promise<Job<T>[]>;
382
+ /**
383
+ * Jobs whose lease ran out on their last attempt: no claim will take them
384
+ * again, so they are marked `dead` rather than left looking leased forever.
385
+ */
386
+ private retireStatement;
387
+ /**
388
+ * Settle a claimed job, if the claim is still ours: same job, same attempt,
389
+ * still leased. False when the lease expired and the job moved on.
390
+ */
391
+ private settle;
392
+ /** The job is finished. */
393
+ complete(job: Job<T>): Promise<boolean>;
394
+ /**
395
+ * The attempt failed. The job backs off and is tried again, or is `dead`
396
+ * once it has had all its attempts. `delay` overrides the backoff.
397
+ */
398
+ fail(job: Job<T>, error: unknown, { delay }?: {
399
+ delay?: Duration;
400
+ }): Promise<boolean>;
401
+ /**
402
+ * Hand a claimed job back unworked. By default the attempt is refunded: for
403
+ * a job that never got a fair try, such as a site that walled the run.
404
+ */
405
+ release(job: Job<T>, { delay, refund }?: {
406
+ delay?: Duration;
407
+ refund?: boolean;
408
+ }): Promise<boolean>;
409
+ /** How long a job that just failed its `attempts`th attempt waits. */
410
+ private backoffMs;
411
+ /**
412
+ * Set ready jobs aside, e.g. ones no handler can read yet. Parked jobs are
413
+ * never claimed until `reopen`. Returns how many were parked.
414
+ */
415
+ park(filter: JobFilter, reason?: string): Promise<number>;
416
+ /**
417
+ * Make jobs ready again with their attempts reset: parked ones by default,
418
+ * or any of `from`, e.g. `["dead"]` to retry what gave up.
419
+ */
420
+ reopen(filter?: JobFilter, { from }?: {
421
+ from?: readonly Exclude<JobStatus, "leased" | "ready">[];
422
+ }): Promise<number>;
423
+ /**
424
+ * Delete finished jobs last touched more than `olderThan` ago, `done` ones by
425
+ * default. A pruned key can be added again.
426
+ */
427
+ prune({ olderThan, status, }: {
428
+ olderThan: Duration;
429
+ status?: readonly Exclude<JobStatus, "leased" | "ready">[];
430
+ }): Promise<number>;
431
+ /**
432
+ * Work through the queue until it is empty, `max` is reached or the run's
433
+ * deadline passes, `concurrency` jobs at a time and never two of one group
434
+ * at once. A handler that returns completes its job; one that throws fails
435
+ * it. Whatever is left waits for the next run.
436
+ */
437
+ work<W = undefined>(handler: WorkHandler<T, NoInfer<W>>, options?: WorkOptions<W>): Promise<WorkSummary>;
438
+ /** The job with `key`, in whatever state it is in. */
439
+ get(key: string): Promise<(Job<T> & {
440
+ status: JobStatus;
441
+ }) | null>;
442
+ stats(): Promise<QueueStats>;
443
+ }
444
+
445
+ /**
446
+ * The run this context belongs to, and how long it has. Times are epoch
447
+ * milliseconds, as everywhere in ngn's database.
448
+ */
449
+ interface TaskRun {
450
+ /** This run's `task_runs.id`. */
451
+ id: number;
452
+ /** When the tick that started it fired; null for a run with no schedule. */
453
+ scheduledAt: number | null;
454
+ /** When the next tick is due; null for a run with no schedule. */
455
+ nextAt: number | null;
456
+ /**
457
+ * When to stop starting new work: shortly before `nextAt`, or sooner with a
458
+ * `budget` export. Null when neither applies.
459
+ */
460
+ deadline: number | null;
461
+ /**
462
+ * Aborts at `deadline`. It is a request: ngn does not stop a run that keeps
463
+ * going past it.
464
+ */
465
+ signal: AbortSignal;
466
+ }
123
467
  type LiveSourceParams = {
124
468
  livePath: `live:${string}`;
125
469
  code: string;
@@ -154,12 +498,30 @@ interface TaskContext {
154
498
  timing: {
155
499
  start(label: string): () => Promise<void>;
156
500
  };
501
+ run: TaskRun;
502
+ /**
503
+ * Run `fn` holding the named lock, shared with every task and process using
504
+ * the same ngn database. Waits for it by default; throws a `LockBusyError`
505
+ * when it cannot be had in time.
506
+ */
507
+ lock<T>(name: string, fn: () => Promise<T> | T, options?: LockOptions): Promise<T>;
508
+ /**
509
+ * Open a named job queue, by default in this task's own database. Any task
510
+ * that opens the same name on the same database shares it.
511
+ */
512
+ queue<T = unknown>(name: string, options?: QueueOptions): Promise<Queue<T>>;
157
513
  /**
158
514
  * SQLite scoped to the folder this task file lives in. Always present — no
159
515
  * configuration required.
160
516
  */
161
517
  sqlite: TaskSqlite;
162
518
  }
519
+ /**
520
+ * What a scheduled tick does when the task's previous run is still going:
521
+ * `"skip"` records the tick as a skipped run, `"allow"` starts another run
522
+ * beside it.
523
+ */
524
+ type OverlapPolicy = "skip" | "allow";
163
525
 
164
526
  interface CompiledTaskCallbacks {
165
527
  onTaskLoadComplete?: ({ paths, md5Hash, compiledCode, }: {
@@ -175,7 +537,6 @@ interface CompiledTaskCallbacks {
175
537
  fileTaskId: number;
176
538
  fileTaskVersionId: number;
177
539
  }>;
178
- onTaskExecutionStart?: () => Promise<void>;
179
540
  onTaskCodeLoaded?: () => Promise<void>;
180
541
  onTaskExportsLoaded?: () => Promise<void>;
181
542
  }
@@ -183,45 +544,81 @@ interface TaskRuntimeCallbacks {
183
544
  onNotFound?: (meta: {
184
545
  path: string;
185
546
  }) => Promise<void>;
547
+ /** `reason` is set when ngn skipped the run, rather than the task's `shouldSkip`. */
186
548
  onSkip?: (meta: {
187
549
  path: string;
550
+ reason?: string;
188
551
  }) => Promise<void>;
189
552
  onStart?: (meta: {
190
553
  path: string;
191
554
  }) => Promise<void>;
192
555
  onSuccess?: (meta: {
193
556
  path: string;
557
+ result?: unknown;
194
558
  }) => Promise<void>;
195
559
  onFailure?: (meta: {
196
560
  path: string;
197
561
  }) => Promise<void>;
198
562
  }
563
+ /**
564
+ * One run's bookkeeping: its row in `task_runs`, and everything that writes
565
+ * against that row. Made fresh for every execution, so two runs of one task
566
+ * that overlap never write to each other's row.
567
+ */
568
+ interface RunControls {
569
+ id: number;
570
+ log: TaskContext["log"];
571
+ timing: TaskContext["timing"];
572
+ locks: RunLocks;
573
+ callbacks: TaskRuntimeCallbacks;
574
+ }
199
575
  type BuildCompiledTaskConfigFn = ({ sourcePath, compiledPath, }: {
200
576
  sourcePath: string;
201
577
  compiledPath: string;
202
- }) => {
578
+ }) => TaskConfig;
579
+ /** Everything one task file gets from ngn, built once per `Task`. */
580
+ type TaskConfig = {
203
581
  compiledPath: string;
204
582
  sourcePath: string;
205
583
  taskCallbacks: CompiledTaskCallbacks;
206
- runtimeCallbacks: TaskRuntimeCallbacks;
584
+ /**
585
+ * Open a run: insert its `task_runs` row and hand back what writes to it.
586
+ * `retryOf` is the first run of the tick a retry belongs to.
587
+ */
588
+ startRun: (options?: {
589
+ retryOf?: number | null;
590
+ }) => Promise<RunControls>;
591
+ /** The task's run history, for the circuit breaker. */
592
+ history: {
593
+ recentTickOutcomes(limit: number): Promise<Array<{
594
+ id: number;
595
+ outcome: "success" | "failure";
596
+ }>>;
597
+ countSkipsSince(reason: string, afterId: number): Promise<number>;
598
+ };
207
599
  env: TaskContext["env"];
208
600
  kv: TaskContext["kv"];
209
- log: TaskContext["log"];
210
- timing: TaskContext["timing"];
211
601
  sqlite: TaskContext["sqlite"];
212
602
  };
213
603
 
604
+ interface ExecuteSchedule {
605
+ /** When the tick fired, epoch ms. */
606
+ scheduledAt?: number | null;
607
+ /** When the next tick is due, epoch ms. */
608
+ nextAt?: number | null;
609
+ }
214
610
  declare class Task {
215
611
  private taskCallbacks;
216
- private runtimeCallbacks;
612
+ private startRun;
613
+ private history;
217
614
  private env;
218
615
  private kv;
219
- private log;
220
- private timing;
221
616
  private sqlite;
222
617
  private meta;
223
618
  private entry;
224
619
  private entryExports;
620
+ /** Executions started and not yet settled. */
621
+ private inFlight;
225
622
  constructor({ entryParams, buildConfigFn, }: {
226
623
  entryParams: EntryParams;
227
624
  buildConfigFn: BuildCompiledTaskConfigFn;
@@ -234,12 +631,56 @@ declare class Task {
234
631
  get taskPath(): string;
235
632
  get codeHash(): string | null;
236
633
  get timingPattern(): string | undefined;
634
+ /**
635
+ * The task's `overlap` export, `"skip"` when it has none. Throws on anything
636
+ * else, naming the task, so a typo fails at schedule time.
637
+ */
638
+ get overlapPolicy(): OverlapPolicy;
639
+ /**
640
+ * Check the exports that shape how the task is scheduled, so a bad value
641
+ * fails when the task is scheduled rather than on its first tick.
642
+ */
643
+ validateExports(): void;
644
+ /** Whether an execution of this task is in progress. */
645
+ get isRunning(): boolean;
646
+ /**
647
+ * Record a run that ngn decided not to start, without loading or calling any
648
+ * of the task's code. Needs the task to have been registered by an earlier
649
+ * execution, which is always so for a run skipped because one is in progress.
650
+ */
651
+ skip(reason: string): Promise<void>;
237
652
  hasTaskExport(): boolean;
238
653
  hasTimingExport(): boolean;
239
654
  loadEntry(): Promise<void>;
240
655
  private loadAndRegister;
241
656
  private runTask;
242
- execute(): Promise<void>;
657
+ /**
658
+ * Run the task for one tick, retrying as its `retry` export allows. A
659
+ * scheduled tick passes when it fired and when the next one is due, which
660
+ * `ctx.run.deadline` and the retry window are worked out from.
661
+ */
662
+ execute(schedule?: ExecuteSchedule): Promise<void>;
663
+ private executeWithRetries;
664
+ /**
665
+ * Retry only a run that got as far as running: a module that will not load
666
+ * fails the same way every time. Then it has to fit before the next tick,
667
+ * and the task's `shouldRetry` gets the last word.
668
+ */
669
+ private shouldRetry;
670
+ /**
671
+ * Whether the breaker holds this tick back: after `after` failed ticks in a
672
+ * row it skips 1, 2, 4… ticks (counted since the latest failure), letting
673
+ * one through in between to see whether things have recovered.
674
+ */
675
+ private breakerOpen;
676
+ /**
677
+ * Why ngn will not start this run, or null. Checked in order: the breaker
678
+ * (scheduled first attempts only), then the `exclusive` locks, which are
679
+ * left held on success.
680
+ */
681
+ private holdBack;
682
+ private buildContext;
683
+ private executeOnce;
243
684
  }
244
685
 
245
686
  declare class TaskLibrary {
@@ -283,6 +724,13 @@ declare class TaskOnceRunner {
283
724
  run(): Promise<void>;
284
725
  }
285
726
 
727
+ declare function isCronPatternValid(pattern: string): boolean;
728
+ /**
729
+ * Schedule a loaded task on `pattern`. The task's exports are checked here, so
730
+ * a bad `overlap` or `budget` fails now rather than on the first tick.
731
+ */
732
+ declare function scheduleTask(task: Task, pattern: string, onError: (error: unknown) => void): Cron<undefined>;
733
+
286
734
  declare function setupDbClient(dbPath: string): Promise<Client>;
287
735
  declare function getDbClient(): Client;
288
736
 
@@ -291,4 +739,4 @@ declare function createControlsGenerator({ dbClient, env, }: {
291
739
  env: TaskContext["env"];
292
740
  }): BuildCompiledTaskConfigFn;
293
741
 
294
- export { Compiler, type DBInstance, type InitDBOptions, type Migration, Task, type TaskContext, TaskLibrary, TaskLibraryScheduler, TaskOnceRunner, type TaskSqlite, closeTaskDatabases, createControlsGenerator, executeEsm, getDbClient, setupDbClient };
742
+ export { type AddJob, type Backoff, type ClaimOptions, Compiler, type DBInstance, type Duration, type InitDBOptions, type Job, type JobFilter, type JobStatus, LockBusyError, type LockOptions, type Migration, type OverlapPolicy, Queue, type QueueOptions, type QueueStats, Task, type TaskContext, TaskLibrary, TaskLibraryScheduler, TaskOnceRunner, type TaskRun, type TaskSqlite, type WorkHandler, type WorkOptions, type WorkSummary, type WorkerHandle, closeTaskDatabases, createControlsGenerator, executeEsm, getDbClient, isCronPatternValid, scheduleTask, setupDbClient };