@bluelibs/runner 5.2.0 → 5.3.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 (44) hide show
  1. package/README.md +73 -10
  2. package/dist/browser/index.cjs +149 -59
  3. package/dist/browser/index.cjs.map +1 -1
  4. package/dist/browser/index.mjs +149 -60
  5. package/dist/browser/index.mjs.map +1 -1
  6. package/dist/edge/index.cjs +149 -59
  7. package/dist/edge/index.cjs.map +1 -1
  8. package/dist/edge/index.mjs +149 -60
  9. package/dist/edge/index.mjs.map +1 -1
  10. package/dist/node/node.cjs +384 -201
  11. package/dist/node/node.cjs.map +1 -1
  12. package/dist/node/node.mjs +382 -202
  13. package/dist/node/node.mjs.map +1 -1
  14. package/dist/types/definers/builders/error/fluent-builder.interface.d.ts +6 -0
  15. package/dist/types/definers/builders/error/index.d.ts +10 -1
  16. package/dist/types/definers/builders/error/types.d.ts +2 -0
  17. package/dist/types/definers/defineError.d.ts +5 -3
  18. package/dist/types/defs.d.ts +2 -0
  19. package/dist/types/globals/resources/tunnel/protocol.d.ts +3 -0
  20. package/dist/types/models/Store.d.ts +5 -3
  21. package/dist/types/models/StoreRegistry.d.ts +5 -3
  22. package/dist/types/node/durable/core/DurableResource.d.ts +23 -9
  23. package/dist/types/node/durable/core/DurableService.d.ts +15 -9
  24. package/dist/types/node/durable/core/interfaces/service.d.ts +27 -19
  25. package/dist/types/node/durable/core/interfaces/store.d.ts +1 -1
  26. package/dist/types/node/durable/core/managers/ExecutionManager.d.ts +34 -4
  27. package/dist/types/node/durable/core/managers/ScheduleManager.d.ts +5 -3
  28. package/dist/types/node/durable/core/managers/TaskRegistry.d.ts +5 -5
  29. package/dist/types/node/durable/core/managers/WaitManager.d.ts +1 -1
  30. package/dist/types/node/durable/index.d.ts +1 -0
  31. package/dist/types/node/durable/tags/durableWorkflow.tag.d.ts +14 -0
  32. package/dist/types/node/node.d.ts +2 -1
  33. package/dist/types/public.d.ts +4 -1
  34. package/dist/types/testing.d.ts +0 -1
  35. package/dist/types/types/error.d.ts +22 -1
  36. package/dist/types/types/resource.d.ts +2 -4
  37. package/dist/types/types/symbols.d.ts +2 -2
  38. package/dist/types/types/tagged.d.ts +18 -0
  39. package/dist/universal/index.cjs +149 -59
  40. package/dist/universal/index.cjs.map +1 -1
  41. package/dist/universal/index.mjs +149 -60
  42. package/dist/universal/index.mjs.map +1 -1
  43. package/package.json +2 -2
  44. package/readmes/AI.md +25 -9
@@ -1,10 +1,16 @@
1
1
  import type { DefaultErrorType, IErrorMeta, IValidationSchema, IErrorHelper } from "../../../defs";
2
2
  export interface ErrorFluentBuilder<TData extends DefaultErrorType = DefaultErrorType> {
3
3
  id: string;
4
+ httpCode(code: number): ErrorFluentBuilder<TData>;
4
5
  serialize(fn: (data: TData) => string): ErrorFluentBuilder<TData>;
5
6
  parse(fn: (raw: string) => TData): ErrorFluentBuilder<TData>;
6
7
  dataSchema(schema: IValidationSchema<TData>): ErrorFluentBuilder<TData>;
7
8
  build(): IErrorHelper<TData>;
8
9
  format(fn: (data: TData) => string): ErrorFluentBuilder<TData>;
10
+ /**
11
+ * Attach remediation advice that explains how to fix this error.
12
+ * Appears in the stringified error after the main message.
13
+ */
14
+ remediation(advice: string | ((data: TData) => string)): ErrorFluentBuilder<TData>;
9
15
  meta<TNewMeta extends IErrorMeta>(m: TNewMeta): ErrorFluentBuilder<TData>;
10
16
  }
@@ -1,5 +1,6 @@
1
1
  import type { DefaultErrorType } from "../../../defs";
2
2
  import type { ErrorFluentBuilder } from "./fluent-builder.interface";
3
+ import { RunnerError } from "../../defineError";
3
4
  export * from "./fluent-builder.interface";
4
5
  export * from "./fluent-builder";
5
6
  export * from "./types";
@@ -8,4 +9,12 @@ export * from "./utils";
8
9
  * Entry point for creating an error builder.
9
10
  */
10
11
  export declare function errorBuilder<TData extends DefaultErrorType = DefaultErrorType>(id: string): ErrorFluentBuilder<TData>;
11
- export declare const error: typeof errorBuilder;
12
+ /**
13
+ * Check if an error is any Runner error (not just a specific one).
14
+ * @param error - The error to check
15
+ * @returns true if the error is a RunnerError instance
16
+ */
17
+ declare function isRunnerError(error: unknown): error is RunnerError;
18
+ export declare const error: typeof errorBuilder & {
19
+ is: typeof isRunnerError;
20
+ };
@@ -6,7 +6,9 @@ import type { DefaultErrorType, IErrorMeta, IValidationSchema } from "../../../d
6
6
  export type BuilderState<TData extends DefaultErrorType> = Readonly<{
7
7
  id: string;
8
8
  filePath: string;
9
+ httpCode?: number;
9
10
  format?: (data: TData) => string;
11
+ remediation?: string | ((data: TData) => string);
10
12
  serialize?: (data: TData) => string;
11
13
  parse?: (raw: string) => TData;
12
14
  dataSchema?: IValidationSchema<TData>;
@@ -1,9 +1,11 @@
1
1
  import { DefaultErrorType, IErrorDefinition, IErrorHelper, IErrorDefinitionFinal } from "../types/error";
2
2
  import { symbolError, symbolFilePath, symbolOptionalDependency } from "../types/symbols";
3
- declare class RunnerError<TData extends DefaultErrorType = DefaultErrorType> extends Error {
3
+ export declare class RunnerError<TData extends DefaultErrorType = DefaultErrorType> extends Error {
4
4
  readonly id: string;
5
5
  readonly data: TData;
6
- constructor(id: string, message: string, data: TData);
6
+ readonly httpCode?: number;
7
+ readonly remediation?: string;
8
+ constructor(id: string, message: string, data: TData, httpCode?: number, remediation?: string);
7
9
  }
8
10
  export declare class ErrorHelper<TData extends DefaultErrorType = DefaultErrorType> implements IErrorHelper<TData> {
9
11
  private readonly definition;
@@ -11,6 +13,7 @@ export declare class ErrorHelper<TData extends DefaultErrorType = DefaultErrorTy
11
13
  [symbolFilePath]: string;
12
14
  constructor(definition: IErrorDefinitionFinal<TData>, filePath: string);
13
15
  get id(): string;
16
+ get httpCode(): number | undefined;
14
17
  throw(data: TData): never;
15
18
  is(error: unknown): error is RunnerError<TData>;
16
19
  optional(): {
@@ -24,4 +27,3 @@ export declare class ErrorHelper<TData extends DefaultErrorType = DefaultErrorTy
24
27
  * @returns
25
28
  */
26
29
  export declare function defineError<TData extends DefaultErrorType = DefaultErrorType>(definition: IErrorDefinition<TData>, filePath?: string): ErrorHelper<TData>;
27
- export {};
@@ -28,5 +28,7 @@ export * from "./types/runner";
28
28
  export * from "./types/asyncContext";
29
29
  export * from "./types/error";
30
30
  export * from "./types/contracts";
31
+ export * from "./types/tagged";
32
+ export * from "./types/inputFile";
31
33
  export type { ICacheInstance } from "./globals/middleware/cache.middleware";
32
34
  export * from "./types/storeTypes";
@@ -3,6 +3,7 @@ export interface ProtocolErrorShape {
3
3
  code: string;
4
4
  message: string;
5
5
  details?: unknown;
6
+ httpCode?: number;
6
7
  id?: string;
7
8
  data?: unknown;
8
9
  }
@@ -33,11 +34,13 @@ export interface EventRequest {
33
34
  export declare class TunnelError extends Error {
34
35
  readonly code: string;
35
36
  readonly details?: unknown;
37
+ readonly httpCode?: number;
36
38
  readonly id?: string;
37
39
  readonly data?: unknown;
38
40
  constructor(code: string, message: string, details?: unknown, extras?: {
39
41
  id?: string;
40
42
  data?: unknown;
43
+ httpCode?: number;
41
44
  });
42
45
  }
43
46
  export declare function toTunnelError(input: unknown, fallbackMessage?: string): TunnelError;
@@ -1,4 +1,4 @@
1
- import { IResource, RegisterableItems, ITag, ITaskMiddleware, IResourceMiddleware } from "../defs";
1
+ import { IResource, RegisterableItems, ITag, AnyTask, ITaskMiddleware, IResourceMiddleware, TaggedTask, TaggedResource, AnyResource } from "../defs";
2
2
  import { EventManager } from "./EventManager";
3
3
  import { Logger } from "./Logger";
4
4
  import { ResourceStoreElementType, TaskStoreElementType, EventStoreElementType } from "../types/storeTypes";
@@ -66,11 +66,13 @@ export declare class Store {
66
66
  * @param tag - The tag to filter by.
67
67
  * @returns The tasks with the given tag.
68
68
  */
69
- getTasksWithTag(tag: string | ITag<any, any, any>): import("..").ITask<any, any, any, any, import("..").TagType[], import("..").TaskMiddlewareAttachmentType[]>[];
69
+ getTasksWithTag<TTag extends ITag<any, any, any>>(tag: TTag): TaggedTask<TTag>[];
70
+ getTasksWithTag(tag: string): AnyTask[];
70
71
  /**
71
72
  * Returns all resources with the given tag.
72
73
  * @param tag - The tag to filter by.
73
74
  * @returns The resources with the given tag.
74
75
  */
75
- getResourcesWithTag(tag: string | ITag<any, any, any>): IResource<any, any, {}, any, any, import("..").TagType[], import("..").ResourceMiddlewareAttachmentType[]>[];
76
+ getResourcesWithTag<TTag extends ITag<any, any, any>>(tag: TTag): TaggedResource<TTag>[];
77
+ getResourcesWithTag(tag: string): AnyResource[];
76
78
  }
@@ -1,4 +1,4 @@
1
- import { IResource, ITask, IResourceWithConfig, RegisterableItems, ITaskMiddleware, IResourceMiddleware, IEvent, ITag, IHook } from "../defs";
1
+ import { IResource, ITask, AnyTask, IResourceWithConfig, RegisterableItems, ITaskMiddleware, IResourceMiddleware, IEvent, ITag, IHook, TaggedTask, TaggedResource, AnyResource } from "../defs";
2
2
  import { TaskStoreElementType, TaskMiddlewareStoreElementType, ResourceMiddlewareStoreElementType, ResourceStoreElementType, EventStoreElementType, HookStoreElementType } from "../defs";
3
3
  import { StoreValidator } from "./StoreValidator";
4
4
  import { Store } from "./Store";
@@ -39,8 +39,10 @@ export declare class StoreRegistry {
39
39
  */
40
40
  buildEventEmissionGraph(): IDependentNode[];
41
41
  private setupBlankNodes;
42
- getTasksWithTag(tag: string | ITag): ITask<any, any, any, any, import("..").TagType[], import("..").TaskMiddlewareAttachmentType[]>[];
43
- getResourcesWithTag(tag: string | ITag): IResource<any, any, {}, any, any, import("..").TagType[], import("..").ResourceMiddlewareAttachmentType[]>[];
42
+ getTasksWithTag<TTag extends ITag<any, any, any>>(tag: TTag): TaggedTask<TTag>[];
43
+ getTasksWithTag(tag: string): AnyTask[];
44
+ getResourcesWithTag<TTag extends ITag<any, any, any>>(tag: TTag): TaggedResource<TTag>[];
45
+ getResourcesWithTag(tag: string): AnyResource[];
44
46
  /**
45
47
  * Used to fetch the value cloned, and if we're dealing with an override, we need to extend the previous value.
46
48
  */
@@ -1,9 +1,9 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import type { Store } from "../../../models/Store";
3
3
  import type { IEventDefinition } from "../../../types/event";
4
- import type { ITask } from "../../../types/task";
4
+ import type { AnyTask, ITask } from "../../../types/task";
5
5
  import type { IDurableContext } from "./interfaces/context";
6
- import type { DurableTask, ExecuteOptions, IDurableService, ScheduleOptions } from "./interfaces/service";
6
+ import type { DurableStartAndWaitResult, ExecuteOptions, IDurableService, ScheduleOptions } from "./interfaces/service";
7
7
  import type { Schedule } from "./types";
8
8
  import type { IDurableStore } from "./interfaces/store";
9
9
  import { DurableOperator } from "./DurableOperator";
@@ -11,7 +11,11 @@ import { type DurableFlowShape } from "./flowShape";
11
11
  export interface DurableResourceConfig {
12
12
  worker?: boolean;
13
13
  }
14
- export interface IDurableResource extends Pick<IDurableService, "startExecution" | "cancelExecution" | "wait" | "execute" | "executeStrict" | "schedule" | "ensureSchedule" | "pauseSchedule" | "resumeSchedule" | "getSchedule" | "listSchedules" | "updateSchedule" | "removeSchedule" | "recover" | "signal"> {
14
+ export interface IDurableResource extends Pick<IDurableService, "cancelExecution" | "wait" | "schedule" | "ensureSchedule" | "pauseSchedule" | "resumeSchedule" | "getSchedule" | "listSchedules" | "updateSchedule" | "removeSchedule" | "recover" | "signal"> {
15
+ start<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input?: TInput, options?: ExecuteOptions): Promise<string>;
16
+ start(task: string, input?: unknown, options?: ExecuteOptions): Promise<string>;
17
+ startAndWait<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input?: TInput, options?: ExecuteOptions): Promise<DurableStartAndWaitResult<TResult>>;
18
+ startAndWait<TResult = unknown>(task: string, input?: unknown, options?: ExecuteOptions): Promise<DurableStartAndWaitResult<TResult>>;
15
19
  /**
16
20
  * Reads the durable context for the currently running workflow execution.
17
21
  * Throws if called outside of a durable execution.
@@ -34,10 +38,14 @@ export interface IDurableResource extends Pick<IDurableService, "startExecution"
34
38
  * (steps/audit/history and operator actions where supported by the store).
35
39
  */
36
40
  readonly operator: DurableOperator;
41
+ /**
42
+ * Returns all tasks tagged as durable workflows in the current runtime.
43
+ */
44
+ getWorkflows(): AnyTask[];
37
45
  }
38
46
  /**
39
47
  * A Runner-facing wrapper around `DurableService` that exposes a per-instance
40
- * context store and the public durable API (`execute`, `signal`, `wait`, etc.).
48
+ * context store and the public durable API (`start`, `startAndWait`, `signal`, `wait`, etc.).
41
49
  *
42
50
  * This enables tasks to depend on a specific durable instance and call
43
51
  * `durable.use()` to access the per-execution durable context.
@@ -52,17 +60,23 @@ export declare class DurableResource implements IDurableResource {
52
60
  get operator(): DurableOperator;
53
61
  use(): IDurableContext;
54
62
  describe<TInput>(task: ITask<TInput, any, any, any, any, any>, input?: TInput): Promise<DurableFlowShape>;
63
+ getWorkflows(): AnyTask[];
55
64
  private injectRecorderIntoDurableDeps;
56
- startExecution<TInput>(task: DurableTask<TInput, unknown>, input?: TInput, options?: ExecuteOptions): Promise<string>;
65
+ start<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input?: TInput, options?: ExecuteOptions): Promise<string>;
66
+ start(task: string, input?: unknown, options?: ExecuteOptions): Promise<string>;
57
67
  cancelExecution(executionId: string, reason?: string): Promise<void>;
58
68
  wait<TResult>(executionId: string, options?: {
59
69
  timeout?: number;
60
70
  waitPollIntervalMs?: number;
61
71
  }): Promise<TResult>;
62
- execute<TInput, TResult>(task: DurableTask<TInput, TResult>, input?: TInput, options?: ExecuteOptions): Promise<TResult>;
63
- executeStrict<TInput, TResult>(task: undefined extends TResult ? never : DurableTask<TInput, TResult>, input?: TInput, options?: ExecuteOptions): Promise<TResult>;
64
- schedule<TInput>(task: DurableTask<TInput, unknown>, input: TInput | undefined, options: ScheduleOptions): Promise<string>;
65
- ensureSchedule<TInput>(task: DurableTask<TInput, unknown>, input: TInput | undefined, options: ScheduleOptions & {
72
+ startAndWait<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input?: TInput, options?: ExecuteOptions): Promise<DurableStartAndWaitResult<TResult>>;
73
+ startAndWait<TResult = unknown>(task: string, input?: unknown, options?: ExecuteOptions): Promise<DurableStartAndWaitResult<TResult>>;
74
+ schedule<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input: TInput | undefined, options: ScheduleOptions): Promise<string>;
75
+ schedule(task: string, input: unknown, options: ScheduleOptions): Promise<string>;
76
+ ensureSchedule<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input: TInput | undefined, options: ScheduleOptions & {
77
+ id: string;
78
+ }): Promise<string>;
79
+ ensureSchedule(task: string, input: unknown, options: ScheduleOptions & {
66
80
  id: string;
67
81
  }): Promise<string>;
68
82
  pauseSchedule(scheduleId: string): Promise<void>;
@@ -1,7 +1,8 @@
1
1
  import { NoopEventBus } from "../bus/NoopEventBus";
2
- import type { DurableServiceConfig, DurableTask, ExecuteOptions, IDurableService, ScheduleOptions } from "./interfaces/service";
2
+ import type { DurableStartAndWaitResult, DurableServiceConfig, ExecuteOptions, IDurableService, ScheduleOptions } from "./interfaces/service";
3
3
  import { type Schedule } from "./types";
4
4
  import type { IEventDefinition } from "../../../types/event";
5
+ import type { ITask } from "../../../types/task";
5
6
  import { ExecutionManager, PollingManager } from "./managers";
6
7
  export { DurableExecutionError } from "./utils";
7
8
  /**
@@ -32,22 +33,27 @@ export declare class DurableService implements IDurableService {
32
33
  /** Unique worker ID for distributed timer coordination */
33
34
  private readonly workerId;
34
35
  constructor(config: DurableServiceConfig);
35
- registerTask<TInput, TResult>(task: DurableTask<TInput, TResult>): void;
36
- findTask(taskId: string): DurableTask<any, any> | undefined;
37
- startExecution<TInput>(task: DurableTask<TInput, unknown>, input?: TInput, options?: ExecuteOptions): Promise<string>;
36
+ registerTask<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>): void;
37
+ findTask(taskId: string): ITask<any, Promise<any>, any, any, any, any> | undefined;
38
+ start<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input?: TInput, options?: ExecuteOptions): Promise<string>;
39
+ start(task: string, input?: unknown, options?: ExecuteOptions): Promise<string>;
40
+ start(): void;
38
41
  cancelExecution(executionId: string, reason?: string): Promise<void>;
39
- execute<TInput, TResult>(task: DurableTask<TInput, TResult>, input?: TInput, options?: ExecuteOptions): Promise<TResult>;
40
- executeStrict<TInput, TResult>(task: undefined extends TResult ? never : DurableTask<TInput, TResult>, input?: TInput, options?: ExecuteOptions): Promise<TResult>;
42
+ startAndWait<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input?: TInput, options?: ExecuteOptions): Promise<DurableStartAndWaitResult<TResult>>;
43
+ startAndWait<TResult = unknown>(task: string, input?: unknown, options?: ExecuteOptions): Promise<DurableStartAndWaitResult<TResult>>;
41
44
  wait<TResult>(executionId: string, options?: {
42
45
  timeout?: number;
43
46
  waitPollIntervalMs?: number;
44
47
  }): Promise<TResult>;
45
- schedule<TInput>(task: DurableTask<TInput, unknown>, input: TInput | undefined, options: ScheduleOptions): Promise<string>;
46
- ensureSchedule<TInput>(task: DurableTask<TInput, unknown>, input: TInput | undefined, options: ScheduleOptions & {
48
+ schedule<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input: TInput | undefined, options: ScheduleOptions): Promise<string>;
49
+ schedule(task: string, input: unknown, options: ScheduleOptions): Promise<string>;
50
+ ensureSchedule<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input: TInput | undefined, options: ScheduleOptions & {
51
+ id: string;
52
+ }): Promise<string>;
53
+ ensureSchedule(task: string, input: unknown, options: ScheduleOptions & {
47
54
  id: string;
48
55
  }): Promise<string>;
49
56
  recover(): Promise<void>;
50
- start(): void;
51
57
  stop(): Promise<void>;
52
58
  pauseSchedule(id: string): Promise<void>;
53
59
  resumeSchedule(id: string): Promise<void>;
@@ -1,20 +1,17 @@
1
1
  import type { IEventDefinition } from "../../../../types/event";
2
+ import type { ITask } from "../../../../types/task";
2
3
  import type { IDurableStore } from "./store";
3
4
  import type { IDurableQueue } from "./queue";
4
5
  import type { IEventBus } from "./bus";
5
6
  import type { IDurableContext } from "./context";
6
7
  import type { Schedule } from "../types";
7
8
  import type { DurableAuditEmitter } from "../audit";
8
- export interface DurableTask<TInput = unknown, TResult = unknown> {
9
- id: string;
10
- run(input: TInput, ...args: any[]): Promise<TResult>;
11
- }
12
9
  export interface ITaskExecutor {
13
- run<TInput, TResult>(task: DurableTask<TInput, TResult>, input?: TInput): Promise<TResult>;
10
+ run<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input?: TInput): Promise<TResult>;
14
11
  }
15
12
  export interface ScheduleConfig<TInput = unknown> {
16
13
  id: string;
17
- task: DurableTask<TInput, unknown>;
14
+ task: ITask<TInput, Promise<any>, any, any, any, any> | string;
18
15
  cron?: string;
19
16
  interval?: number;
20
17
  input: TInput;
@@ -51,7 +48,7 @@ export interface DurableServiceConfig {
51
48
  * Resolves tasks by id for resuming/recovering executions.
52
49
  * Useful in Runner environments where tasks are registered in the Store registry.
53
50
  */
54
- taskResolver?: (taskId: string) => DurableTask<any, any> | undefined;
51
+ taskResolver?: (taskId: string) => ITask<any, Promise<any>, any, any, any, any> | undefined;
55
52
  audit?: {
56
53
  enabled?: boolean;
57
54
  emitter?: DurableAuditEmitter;
@@ -66,7 +63,7 @@ export interface DurableServiceConfig {
66
63
  maxAttempts?: number;
67
64
  timeout?: number;
68
65
  /**
69
- * When a queue is configured, `startExecution()` persists the execution and then enqueues it.
66
+ * When a queue is configured, `start()` persists the execution and then enqueues it.
70
67
  * If enqueue fails (eg. broker outage), the execution would otherwise remain "pending" forever.
71
68
  *
72
69
  * This delay arms a small store-backed timer as a failsafe so workers can retry resuming it
@@ -75,7 +72,7 @@ export interface DurableServiceConfig {
75
72
  kickoffFailsafeDelayMs?: number;
76
73
  };
77
74
  schedules?: ScheduleConfig[];
78
- tasks?: Array<DurableTask<any, any>>;
75
+ tasks?: Array<ITask<any, Promise<any>, any, any, any, any>>;
79
76
  }
80
77
  export interface ExecuteOptions {
81
78
  timeout?: number;
@@ -94,8 +91,15 @@ export interface ScheduleOptions {
94
91
  cron?: string;
95
92
  interval?: number;
96
93
  }
94
+ export interface DurableStartAndWaitResult<TResult = unknown> {
95
+ durable: {
96
+ executionId: string;
97
+ };
98
+ data: TResult;
99
+ }
97
100
  export interface IDurableService {
98
- startExecution<TInput>(task: DurableTask<TInput, unknown>, input?: TInput, options?: ExecuteOptions): Promise<string>;
101
+ start<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input?: TInput, options?: ExecuteOptions): Promise<string>;
102
+ start(task: string, input?: unknown, options?: ExecuteOptions): Promise<string>;
99
103
  /**
100
104
  * Request cancellation for an execution.
101
105
  * Cancellation is cooperative: it marks the execution as cancelled and unblocks waiters,
@@ -106,24 +110,28 @@ export interface IDurableService {
106
110
  timeout?: number;
107
111
  waitPollIntervalMs?: number;
108
112
  }): Promise<TResult>;
109
- execute<TInput, TResult>(task: DurableTask<TInput, TResult>, input?: TInput, options?: ExecuteOptions): Promise<TResult>;
110
113
  /**
111
- * A stricter alternative to `execute()` that rejects tasks whose result type
112
- * includes `undefined` (including `void`, `unknown`, and `any`).
113
- *
114
- * This mirrors the runtime contract where `wait()`/`execute()` treat
115
- * "completed without result" as an error.
114
+ * Starts a workflow and waits for completion.
115
+ * Returns the started execution id together with the workflow result payload.
116
116
  */
117
- executeStrict<TInput, TResult>(task: undefined extends TResult ? never : DurableTask<TInput, TResult>, input?: TInput, options?: ExecuteOptions): Promise<TResult>;
118
- schedule<TInput>(task: DurableTask<TInput, unknown>, input: TInput | undefined, options: ScheduleOptions): Promise<string>;
117
+ startAndWait<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input?: TInput, options?: ExecuteOptions): Promise<DurableStartAndWaitResult<TResult>>;
118
+ startAndWait<TResult = unknown>(task: string, input?: unknown, options?: ExecuteOptions): Promise<DurableStartAndWaitResult<TResult>>;
119
+ schedule<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input: TInput | undefined, options: ScheduleOptions): Promise<string>;
120
+ schedule(task: string, input: unknown, options: ScheduleOptions): Promise<string>;
119
121
  /**
120
122
  * Idempotently create (or update) a recurring schedule (cron/interval) with a stable id.
121
123
  * Safe to call concurrently from multiple processes.
122
124
  */
123
- ensureSchedule<TInput>(task: DurableTask<TInput, unknown>, input: TInput | undefined, options: ScheduleOptions & {
125
+ ensureSchedule<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>, input: TInput | undefined, options: ScheduleOptions & {
126
+ id: string;
127
+ }): Promise<string>;
128
+ ensureSchedule(task: string, input: unknown, options: ScheduleOptions & {
124
129
  id: string;
125
130
  }): Promise<string>;
126
131
  recover(): Promise<void>;
132
+ /**
133
+ * Starts the durable polling loop (timers/schedules processing).
134
+ */
127
135
  start(): void;
128
136
  stop(): Promise<void>;
129
137
  pauseSchedule(scheduleId: string): Promise<void>;
@@ -13,7 +13,7 @@ export interface IDurableStore {
13
13
  listIncompleteExecutions(): Promise<Execution[]>;
14
14
  /**
15
15
  * Optional execution-level idempotency mapping.
16
- * If supported, allows `startExecution(..., { idempotencyKey })` to dedupe workflow starts.
16
+ * If supported, allows `start(..., { idempotencyKey })` to dedupe workflow starts.
17
17
  */
18
18
  getExecutionIdByIdempotencyKey?(params: {
19
19
  taskId: string;
@@ -1,7 +1,8 @@
1
1
  import type { IDurableStore } from "../interfaces/store";
2
2
  import type { IDurableQueue } from "../interfaces/queue";
3
3
  import type { IEventBus } from "../interfaces/bus";
4
- import type { DurableServiceConfig, DurableTask, ExecuteOptions, ITaskExecutor } from "../interfaces/service";
4
+ import type { DurableStartAndWaitResult, DurableServiceConfig, ExecuteOptions, ITaskExecutor } from "../interfaces/service";
5
+ import type { ITask } from "../../../../types/task";
5
6
  import { type Execution } from "../types";
6
7
  import type { TaskRegistry } from "./TaskRegistry";
7
8
  import type { AuditLogger } from "./AuditLogger";
@@ -37,12 +38,41 @@ export declare class ExecutionManager {
37
38
  private readonly auditLogger;
38
39
  private readonly waitManager;
39
40
  constructor(config: ExecutionManagerConfig, taskRegistry: TaskRegistry, auditLogger: AuditLogger, waitManager: WaitManager);
40
- startExecution<TInput>(task: DurableTask<TInput, unknown>, input?: TInput, options?: ExecuteOptions): Promise<string>;
41
+ start(taskRef: string | ITask<any, Promise<any>, any, any, any, any>, input?: unknown, options?: ExecuteOptions): Promise<string>;
42
+ /**
43
+ * Start a workflow with deduplication: if the same (taskId, idempotencyKey)
44
+ * was already started, returns the existing executionId instead of creating
45
+ * a duplicate. Uses a distributed lock to prevent concurrent races.
46
+ */
47
+ private startWithIdempotencyKey;
48
+ private assertCanExecute;
49
+ private assertStoreSupportsIdempotency;
50
+ /**
51
+ * Acquires a distributed lock around the idempotency check-and-set,
52
+ * falling back to lock-free operation when the store has no locking support.
53
+ */
54
+ private withIdempotencyLock;
55
+ /**
56
+ * Recovery path: `setExecutionIdByIdempotencyKey` returned false (another
57
+ * writer won the race), so we re-read the mapping to get their executionId.
58
+ */
59
+ private resolveRacedIdempotencyKey;
60
+ /**
61
+ * Creates a new execution record, persists it to the store, and logs an
62
+ * audit entry. Returns the executionId.
63
+ */
64
+ private persistNewExecution;
65
+ /**
66
+ * Kicks off an execution with a failsafe timer for queue mode.
67
+ * If the queue enqueue succeeds, the timer is cleaned up immediately.
68
+ * If enqueue fails, the timer remains so the polling loop can retry later.
69
+ */
70
+ private kickoffWithFailsafe;
41
71
  cancelExecution(executionId: string, reason?: string): Promise<void>;
42
- execute<TInput, TResult>(task: DurableTask<TInput, TResult>, input?: TInput, options?: ExecuteOptions): Promise<TResult>;
43
- executeStrict<TInput, TResult>(task: undefined extends TResult ? never : DurableTask<TInput, TResult>, input?: TInput, options?: ExecuteOptions): Promise<TResult>;
72
+ startAndWait(taskRef: string | ITask<any, Promise<any>, any, any, any, any>, input?: unknown, options?: ExecuteOptions): Promise<DurableStartAndWaitResult<unknown>>;
44
73
  processExecution(executionId: string): Promise<void>;
45
74
  kickoffExecution(executionId: string): Promise<void>;
46
75
  notifyExecutionFinished(execution: Execution): Promise<void>;
47
76
  private runExecutionAttempt;
77
+ private resolveTaskReference;
48
78
  }
@@ -1,7 +1,8 @@
1
1
  import type { IDurableStore } from "../interfaces/store";
2
- import type { DurableTask, ScheduleOptions } from "../interfaces/service";
2
+ import type { ScheduleOptions } from "../interfaces/service";
3
3
  import { type Schedule } from "../types";
4
4
  import type { TaskRegistry } from "./TaskRegistry";
5
+ import type { ITask } from "../../../../types/task";
5
6
  /**
6
7
  * Creates and maintains durable schedules.
7
8
  *
@@ -13,10 +14,10 @@ export declare class ScheduleManager {
13
14
  private readonly store;
14
15
  private readonly taskRegistry;
15
16
  constructor(store: IDurableStore, taskRegistry: TaskRegistry);
16
- ensureSchedule<TInput>(task: DurableTask<TInput, unknown>, input: TInput | undefined, options: ScheduleOptions & {
17
+ ensureSchedule(taskRef: string | ITask<any, Promise<any>, any, any, any, any>, input: unknown, options: ScheduleOptions & {
17
18
  id: string;
18
19
  }): Promise<string>;
19
- schedule<TInput>(task: DurableTask<TInput, unknown>, input: TInput | undefined, options: ScheduleOptions): Promise<string>;
20
+ schedule(taskRef: string | ITask<any, Promise<any>, any, any, any, any>, input: unknown, options: ScheduleOptions): Promise<string>;
20
21
  reschedule(schedule: Schedule, options?: {
21
22
  lastRunAt?: Date;
22
23
  }): Promise<void>;
@@ -30,4 +31,5 @@ export declare class ScheduleManager {
30
31
  input?: unknown;
31
32
  }): Promise<void>;
32
33
  remove(id: string): Promise<void>;
34
+ private resolveTaskReference;
33
35
  }
@@ -1,16 +1,16 @@
1
- import type { DurableTask } from "../interfaces/service";
1
+ import type { ITask } from "../../../../types/task";
2
2
  /**
3
3
  * In-memory durable task registry.
4
4
  *
5
5
  * Durable executions persist only a `taskId` in the store, so the runtime needs a way
6
- * to resolve `taskId -> DurableTask` when resuming. This registry holds tasks that
6
+ * to resolve `taskId -> ITask` when resuming. This registry holds tasks that
7
7
  * were registered on the current process and optionally delegates to an external
8
8
  * resolver for tasks defined elsewhere (useful for modular apps).
9
9
  */
10
10
  export declare class TaskRegistry {
11
11
  private readonly externalResolver?;
12
12
  private readonly tasks;
13
- constructor(externalResolver?: ((taskId: string) => DurableTask<any, any> | undefined) | undefined);
14
- register<TInput, TResult>(task: DurableTask<TInput, TResult>): void;
15
- find(taskId: string): DurableTask<any, any> | undefined;
13
+ constructor(externalResolver?: ((taskId: string) => ITask<any, Promise<any>, any, any, any, any> | undefined) | undefined);
14
+ register<TInput, TResult>(task: ITask<TInput, Promise<TResult>, any, any, any, any>): void;
15
+ find(taskId: string): ITask<any, Promise<any>, any, any, any, any> | undefined;
16
16
  }
@@ -12,7 +12,7 @@ export interface WaitConfig {
12
12
  * - otherwise (or on bus issues) fall back to polling the store
13
13
  *
14
14
  * The durable store remains the source of truth; this manager is purely a convenience layer
15
- * for callers that want `await durable.wait(...)` / `await durable.execute(...)`.
15
+ * for callers that want `await durable.wait(...)` / `await durable.startAndWait(...)`.
16
16
  */
17
17
  export declare class WaitManager {
18
18
  private readonly store;
@@ -28,6 +28,7 @@ export { NoopEventBus } from "./bus/NoopEventBus";
28
28
  export { RedisEventBus } from "./bus/RedisEventBus";
29
29
  export { createDurableTestSetup, waitUntil } from "./test-utils";
30
30
  export type { DurableTestSetup, DurableTestSetupOptions } from "./test-utils";
31
+ export { durableWorkflowTag, type DurableWorkflowTagConfig, } from "./tags/durableWorkflow.tag";
31
32
  export { memoryDurableResource } from "./resources/memoryDurableResource";
32
33
  export type { MemoryDurableResourceConfig } from "./resources/memoryDurableResource";
33
34
  export { redisDurableResource } from "./resources/redisDurableResource";
@@ -0,0 +1,14 @@
1
+ export interface DurableWorkflowTagConfig {
2
+ /**
3
+ * Optional domain/category to group workflows (eg. "orders", "billing").
4
+ */
5
+ category?: string;
6
+ /**
7
+ * Optional metadata for dashboards/tooling.
8
+ */
9
+ metadata?: Record<string, unknown>;
10
+ }
11
+ /**
12
+ * Marks a task as a durable workflow for runtime discovery.
13
+ */
14
+ export declare const durableWorkflowTag: import("../..").ITag<DurableWorkflowTagConfig, void, void>;
@@ -4,7 +4,8 @@ import type { SerializerLike } from "../serializer";
4
4
  export { nodeExposure } from "./exposure";
5
5
  export { hasExposureContext, useExposureContext, } from "./exposure/requestContext";
6
6
  export type * from "./exposure/resourceTypes";
7
- export { createNodeFile } from "./files";
7
+ export { createNodeFile, NodeInputFile } from "./files";
8
+ export type { NodeReadable } from "./files";
8
9
  export { readInputFileToBuffer, writeInputFileToPath } from "./files";
9
10
  export { createHttpSmartClient, createHttpMixedClient } from "./http";
10
11
  export type { HttpSmartClient, HttpSmartClientAuthConfig, HttpSmartClientConfig, MixedHttpClient, MixedHttpClientAuthConfig, MixedHttpClientConfig, Readable, } from "./http";
@@ -193,7 +193,9 @@ export declare const r: Readonly<{
193
193
  tag: typeof import("./definers/builders/tag").tagBuilder;
194
194
  override: typeof overrideBuilder;
195
195
  asyncContext: typeof import("./definers/builders/asyncContext").asyncContextBuilder;
196
- error: typeof import("./definers/builders/error").errorBuilder;
196
+ error: typeof import("./definers/builders/error").errorBuilder & {
197
+ is: (error: unknown) => error is import("./public").RunnerError;
198
+ };
197
199
  middleware: Readonly<{
198
200
  task: typeof import("./definers/builders/middleware").taskMiddlewareBuilder;
199
201
  resource: typeof import("./definers/builders/middleware").resourceMiddlewareBuilder;
@@ -204,6 +206,7 @@ export * from "./models";
204
206
  export * from "./globals/types";
205
207
  export * as Errors from "./errors";
206
208
  export { PlatformAdapter, setPlatform } from "./platform";
209
+ export { RunnerError } from "./definers/defineError";
207
210
  export * from "./http-client";
208
211
  export * from "./http-fetch-tunnel.resource";
209
212
  export { Serializer, SymbolPolicy, SymbolPolicyErrorMessage, } from "./serializer";
@@ -12,7 +12,6 @@ export interface TestFacade {
12
12
  * Helper to create a minimal test harness resource that wraps a root app (or any registerable)
13
13
  * and exposes convenient testing utilities while running the full ecosystem
14
14
  * (registration, overrides, middleware, events) without modifying the core API.
15
- * @deprecated Use `run` instead with your testResource, as it provides the necessary toolkit.
16
15
  */
17
16
  export declare function createTestResource(root: RegisterableItems, options?: {
18
17
  overrides?: Array<IResource | ITask | ITaskMiddleware | IResourceMiddleware | IResourceWithConfig>;
@@ -6,9 +6,16 @@ export type ErrorReference = string | IErrorHelper<any>;
6
6
  export type ThrowsList = ReadonlyArray<ErrorReference>;
7
7
  export interface IErrorDefinition<TData extends DefaultErrorType = DefaultErrorType> {
8
8
  id: string;
9
+ httpCode?: number;
9
10
  serialize?: (data: TData) => string;
10
11
  parse?: (data: string) => TData;
11
12
  format?: (data: TData) => string;
13
+ /**
14
+ * Optional advice on how to fix the error. Appears in the stringified
15
+ * error message after the main message. Can be a static string or a
16
+ * function that receives the error data and returns a string.
17
+ */
18
+ remediation?: string | ((data: TData) => string);
12
19
  /**
13
20
  * Validate error data on throw(). If provided, data is parsed first.
14
21
  */
@@ -17,8 +24,20 @@ export interface IErrorDefinition<TData extends DefaultErrorType = DefaultErrorT
17
24
  }
18
25
  export interface IErrorDefinitionFinal<TData extends DefaultErrorType> extends IErrorDefinition<TData> {
19
26
  format: (data: TData) => string;
27
+ httpCode?: number;
28
+ remediation?: string | ((data: TData) => string);
20
29
  }
21
30
  export type DefaultErrorType = Record<string, unknown>;
31
+ /**
32
+ * Runtime error shape thrown by r.error()/defineError() helpers.
33
+ * Consumers can use helper.is(error) to narrow unknown values to this type.
34
+ */
35
+ export interface IRunnerError<TData extends DefaultErrorType = DefaultErrorType> extends Error {
36
+ id: string;
37
+ httpCode?: number;
38
+ data: TData;
39
+ remediation?: string;
40
+ }
22
41
  /**
23
42
  * Runtime helper returned by defineError()/r.error().
24
43
  * Contains helpers to throw typed errors and perform type-safe checks.
@@ -26,10 +45,12 @@ export type DefaultErrorType = Record<string, unknown>;
26
45
  export interface IErrorHelper<TData extends DefaultErrorType = DefaultErrorType> {
27
46
  /** Unique id for registration and DI */
28
47
  id: string;
48
+ /** Optional HTTP status code associated with this error helper */
49
+ httpCode?: number;
29
50
  /** Throw a typed error with the given data */
30
51
  throw(data: TData): never;
31
52
  /** Type guard for checking if an unknown error is this error */
32
- is(error: unknown): boolean;
53
+ is(error: unknown): error is IRunnerError<TData>;
33
54
  /** Brand symbol for runtime detection */
34
55
  [symbolError]: true;
35
56
  /** Return an optional dependency wrapper for this error */