@superdurable/dex 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +145 -0
  3. package/dist/src/blob-cache.d.ts +14 -0
  4. package/dist/src/blob-cache.js +22 -0
  5. package/dist/src/blob-cache.js.map +1 -0
  6. package/dist/src/client.d.ts +36 -0
  7. package/dist/src/client.js +441 -0
  8. package/dist/src/client.js.map +1 -0
  9. package/dist/src/codec.d.ts +38 -0
  10. package/dist/src/codec.js +86 -0
  11. package/dist/src/codec.js.map +1 -0
  12. package/dist/src/context.d.ts +23 -0
  13. package/dist/src/context.js +9 -0
  14. package/dist/src/context.js.map +1 -0
  15. package/dist/src/errors.d.ts +44 -0
  16. package/dist/src/errors.js +73 -0
  17. package/dist/src/errors.js.map +1 -0
  18. package/dist/src/flow.d.ts +31 -0
  19. package/dist/src/flow.js +155 -0
  20. package/dist/src/flow.js.map +1 -0
  21. package/dist/src/gen/dex.d.ts +1529 -0
  22. package/dist/src/gen/dex.js +10661 -0
  23. package/dist/src/gen/dex.js.map +1 -0
  24. package/dist/src/gen/google/protobuf/duration.d.ts +103 -0
  25. package/dist/src/gen/google/protobuf/duration.js +64 -0
  26. package/dist/src/gen/google/protobuf/duration.js.map +1 -0
  27. package/dist/src/gen/google/protobuf/empty.d.ts +37 -0
  28. package/dist/src/gen/google/protobuf/empty.js +39 -0
  29. package/dist/src/gen/google/protobuf/empty.js.map +1 -0
  30. package/dist/src/gen/google/protobuf/struct.d.ts +140 -0
  31. package/dist/src/gen/google/protobuf/struct.js +359 -0
  32. package/dist/src/gen/google/protobuf/struct.js.map +1 -0
  33. package/dist/src/gen/google/protobuf/timestamp.d.ts +133 -0
  34. package/dist/src/gen/google/protobuf/timestamp.js +64 -0
  35. package/dist/src/gen/google/protobuf/timestamp.js.map +1 -0
  36. package/dist/src/grpc-status.d.ts +4 -0
  37. package/dist/src/grpc-status.js +105 -0
  38. package/dist/src/grpc-status.js.map +1 -0
  39. package/dist/src/index.d.ts +14 -0
  40. package/dist/src/index.js +20 -0
  41. package/dist/src/index.js.map +1 -0
  42. package/dist/src/invocation-context.d.ts +44 -0
  43. package/dist/src/invocation-context.js +203 -0
  44. package/dist/src/invocation-context.js.map +1 -0
  45. package/dist/src/options.d.ts +106 -0
  46. package/dist/src/options.js +55 -0
  47. package/dist/src/options.js.map +1 -0
  48. package/dist/src/persistence.d.ts +45 -0
  49. package/dist/src/persistence.js +65 -0
  50. package/dist/src/persistence.js.map +1 -0
  51. package/dist/src/rpc.d.ts +39 -0
  52. package/dist/src/rpc.js +50 -0
  53. package/dist/src/rpc.js.map +1 -0
  54. package/dist/src/step.d.ts +92 -0
  55. package/dist/src/step.js +62 -0
  56. package/dist/src/step.js.map +1 -0
  57. package/dist/src/validation.d.ts +3 -0
  58. package/dist/src/validation.js +33 -0
  59. package/dist/src/validation.js.map +1 -0
  60. package/dist/src/value-mapper.d.ts +14 -0
  61. package/dist/src/value-mapper.js +199 -0
  62. package/dist/src/value-mapper.js.map +1 -0
  63. package/dist/src/wait.d.ts +57 -0
  64. package/dist/src/wait.js +124 -0
  65. package/dist/src/wait.js.map +1 -0
  66. package/dist/src/worker-dispatcher.d.ts +14 -0
  67. package/dist/src/worker-dispatcher.js +374 -0
  68. package/dist/src/worker-dispatcher.js.map +1 -0
  69. package/dist/src/worker.d.ts +18 -0
  70. package/dist/src/worker.js +139 -0
  71. package/dist/src/worker.js.map +1 -0
  72. package/package.json +53 -0
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2026 Super Durable, Inc.
2
+
3
+ Licensed under the Super Durable Source License 1.0.
4
+ You may not use this file except in compliance with the License.
5
+ See the LICENSE file in the repository root.
6
+
7
+ SPDX-License-Identifier: LicenseRef-Super-Durable-1.0
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # Dex SDK for TypeScript
2
+
3
+ This package targets Node.js 22 and 24. It provides strongly typed workflow
4
+ contracts and a Promise-based gRPC Client. The Client and Worker runtime use
5
+ `@grpc/grpc-js`. The native BlobCache binding remains a separate runtime phase.
6
+
7
+ Application values use `Codec<T>`. Flow, Step, RPC, Attribute, and Channel
8
+ definitions retain their input and output types. Client methods return Promise
9
+ because Node network I/O is asynchronous.
10
+
11
+ The Client uses `@grpc/grpc-js` directly. Rust is only the implementation
12
+ boundary for the shared BlobCache; TypeScript callbacks and network transport
13
+ stay in Node.
14
+
15
+ Step input codecs and RPC input/output codecs remain explicit because
16
+ TypeScript erases generic types at runtime. They are serialization metadata,
17
+ not builder arguments.
18
+
19
+ ```typescript
20
+ class ApproveOrder implements Step<string> {
21
+ readonly inputCodec = stringCodec;
22
+
23
+ getStepType(): string {
24
+ return "ApproveOrder";
25
+ }
26
+
27
+ waitFor(_context: Context, _orderId: string): Wait {
28
+ return Wait.allOf(Timer.byDuration(1_000));
29
+ }
30
+
31
+ execute(_context: Context, orderId: string): StepDecision {
32
+ return gracefulComplete(orderId);
33
+ }
34
+ }
35
+
36
+ class Orders implements Flow<string> {
37
+ readonly approve = new ApproveOrder();
38
+
39
+ getFlowType(): string {
40
+ return "Orders";
41
+ }
42
+
43
+ getSteps() {
44
+ return StepList.startStep(this.approve);
45
+ }
46
+ }
47
+
48
+ const orders = new Orders();
49
+ const registry = new Registry([orders]);
50
+ ```
51
+
52
+ Flows return all Steps once. Start with `StepList.startStep(step)` and append
53
+ heterogeneous Steps with `.otherSteps(...)`. Use
54
+ `StepList.withoutStartStep<void>(...)` for RPC-triggered Steps, or
55
+ `StepList.empty()` when the Flow has no Steps.
56
+ `Flow<StartInput>` only types the starting Step and `Client.startFlow()` input;
57
+ `StepList<StartInput>` enforces that relationship during type checking.
58
+ Non-starting Steps may use unrelated input types. `Flow` defaults to `void` for
59
+ Flows without a start input.
60
+
61
+ `StepOptions.waitForMethodTimeoutMs` and `executeMethodTimeoutMs` bound the two
62
+ handler calls. Timer and channel conditions determine how long a Step waits.
63
+
64
+ Every TypeScript Flow and Step must return an explicit durable name from
65
+ `getFlowType()` or `getStepType()`. Class names are never used as fallbacks
66
+ because bundlers and minifiers may rename them.
67
+
68
+ ## Source layout
69
+
70
+ Public contracts are grouped by domain under `src/`. The root `src/index.ts`
71
+ is a barrel that re-exports the supported package API; applications should
72
+ continue importing only from `@superdurable/dex`.
73
+
74
+ - `codec.ts`: wire values and codecs
75
+ - `persistence.ts`: attributes, indexes, locks, and schemas
76
+ - `wait.ts`: channels, timers, conditions, and waits
77
+ - `step.ts`: Steps, movements, options, and decisions
78
+ - `rpc.ts`: typed RPC contracts and decorators
79
+ - `flow.ts`: Flows, registration, and validation
80
+ - `client.ts`: Promise-based FlowService Client
81
+ - `worker.ts`: Worker gRPC service and lifecycle
82
+ - `worker-dispatcher.ts`: typed callback dispatch and response mapping
83
+ - `invocation-context.ts`: invocation-scoped persistence and condition state
84
+ - `blob-cache.ts`: injectable cache contract and future N-API binding
85
+ - `gen/`: checked-in protobuf and grpc-js bindings
86
+
87
+ Run `npm test` for runtime contracts and `npm run typecheck` for strict static
88
+ contracts. Run `./run-integration-tests.sh` for all 58 IWF compatibility
89
+ scenarios against an isolated `dexcli dev` environment. Run
90
+ `npm run generate:proto` after changing `protos/dex.proto`; `protoc` and its
91
+ standard protobuf includes must be installed.
92
+
93
+ ## Integration coverage
94
+
95
+ Run the complete integration suite with TypeScript source coverage:
96
+
97
+ ```shell
98
+ npm run coverage:integration
99
+ ```
100
+
101
+ The terminal report lists coverage per SDK source file and every uncovered
102
+ line. Open `coverage/index.html` for annotated source, or inspect
103
+ `coverage/coverage-summary.json` programmatically. `coverage/lcov.info` is the
104
+ report uploaded by CI. Generated protobuf code under `src/gen/` is excluded.
105
+
106
+ CI uploads the LCOV report to Codecov with GitHub OIDC, so no upload secret is
107
+ stored in this repository. The report uses the `sdk-typescript-integration`
108
+ flag and contributes to the TypeScript SDK component defined in the root
109
+ `codecov.yml`. After the first successful `main` upload, Codecov displays
110
+ project and patch coverage in its dashboard, GitHub checks, and PR comments.
111
+ The Actions run also publishes the complete HTML report as
112
+ `sdk-typescript-integration-coverage`.
113
+
114
+ The complete legacy IWF integration inventory lives under
115
+ [`test/integ`](test/integ/README.md). Its Flow fixtures retain the Java
116
+ suite's workflow behavior and its 58 assertions run against a real Dex server.
117
+
118
+ ## Releases
119
+
120
+ The npm package is published as [`@superdurable/dex`](https://www.npmjs.com/package/@superdurable/dex).
121
+ Update `package.json` and `package-lock.json` to the same version, merge the
122
+ change, then publish a GitHub Release tagged `sdk-typescript/vX.Y.Z`. The
123
+ release workflow verifies that the tag matches `package.json`, runs type checks
124
+ and tests, inspects the tarball, and publishes through npm Trusted Publishing.
125
+ Prerelease versions use the `next` npm dist-tag; stable versions use `latest`.
126
+
127
+ Trusted Publishing can only be configured after the package exists. Bootstrap
128
+ the first version from a maintainer workstation with 2FA:
129
+
130
+ ```shell
131
+ cd sdk-typescript
132
+ npm ci
133
+ npm run typecheck
134
+ npm test
135
+ npm pack --dry-run
136
+ npm login
137
+ npm publish --access public
138
+ ```
139
+
140
+ Then open the package settings on npmjs.com and add a GitHub Actions trusted
141
+ publisher with organization `superdurable`, repository `dex`, workflow
142
+ `sdk-typescript-publish.yml`, no environment, and `npm publish` permission.
143
+ Future releases use short-lived OIDC credentials and require no `NPM_TOKEN`.
144
+ After verifying the first OIDC release, configure npm publishing access to
145
+ require 2FA and disallow token-based publication.
@@ -0,0 +1,14 @@
1
+ export interface BlobCacheConfig {
2
+ readonly directory: string;
3
+ readonly maxBytes: number;
4
+ readonly frequencyCounters?: number;
5
+ }
6
+ export interface BlobCache {
7
+ readonly config: BlobCacheConfig;
8
+ get(blobId: string): Uint8Array | undefined;
9
+ put(blobId: string, payload: Uint8Array): boolean;
10
+ delete(blobId: string): void;
11
+ deleteAll(): void;
12
+ close(): void;
13
+ }
14
+ export declare function openBlobCache(config: BlobCacheConfig): BlobCache;
@@ -0,0 +1,22 @@
1
+ // Copyright (c) 2026 Super Durable, Inc.
2
+ //
3
+ // Licensed under the Super Durable Source License 1.0.
4
+ // You may not use this file except in compliance with the License.
5
+ // See the LICENSE file in the repository root.
6
+ //
7
+ // SPDX-License-Identifier: LicenseRef-Super-Durable-1.0
8
+ import { laterPhase } from "./errors.js";
9
+ export function openBlobCache(config) {
10
+ if (config.directory.length === 0) {
11
+ throw new TypeError("blob cache directory is required");
12
+ }
13
+ if (!Number.isSafeInteger(config.maxBytes) || config.maxBytes <= 0) {
14
+ throw new RangeError("blob cache maxBytes must be a positive safe integer");
15
+ }
16
+ if (config.frequencyCounters !== undefined &&
17
+ (!Number.isSafeInteger(config.frequencyCounters) || config.frequencyCounters < 0)) {
18
+ throw new RangeError("blob cache frequencyCounters must be a non-negative safe integer");
19
+ }
20
+ throw laterPhase("BlobCache bridge");
21
+ }
22
+ //# sourceMappingURL=blob-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blob-cache.js","sourceRoot":"","sources":["../../src/blob-cache.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,uDAAuD;AACvD,mEAAmE;AACnE,+CAA+C;AAC/C,EAAE;AACF,wDAAwD;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAiBzC,MAAM,UAAU,aAAa,CAAC,MAAuB;IACnD,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,SAAS,CAAC,kCAAkC,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,UAAU,CAAC,qDAAqD,CAAC,CAAC;IAC9E,CAAC;IACD,IACE,MAAM,CAAC,iBAAiB,KAAK,SAAS;QACtC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,iBAAiB,CAAC,IAAI,MAAM,CAAC,iBAAiB,GAAG,CAAC,CAAC,EACjF,CAAC;QACD,MAAM,IAAI,UAAU,CAAC,kEAAkE,CAAC,CAAC;IAC3F,CAAC;IACD,MAAM,UAAU,CAAC,kBAAkB,CAAC,CAAC;AACvC,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { BlobCache } from "./blob-cache.js";
2
+ import type { Codec } from "./codec.js";
3
+ import type { Context } from "./context.js";
4
+ import { type Flow, type Registry } from "./flow.js";
5
+ import { type ClientOptions, type FlowConfig, type FlowInfo, type ResetFlowOptions, type StartFlowOptions, type StepExecutionId, type StopFlowOptions, type TimerId } from "./options.js";
6
+ import { AttributeMap, type Attribute } from "./persistence.js";
7
+ import type { RPCResult } from "./rpc.js";
8
+ import { ChannelMap, type Channel } from "./wait.js";
9
+ export declare class Client {
10
+ readonly registry: Registry;
11
+ readonly blobCache: BlobCache;
12
+ readonly options: ClientOptions;
13
+ private readonly service;
14
+ private readonly hydrator;
15
+ constructor(registry: Registry, blobCache: BlobCache, options?: ClientOptions);
16
+ startFlow<StartInput>(flow: Flow<StartInput>, flowId: string, input: StartInput, options?: StartFlowOptions): Promise<string>;
17
+ invokeRPC<Input, Output>(rpcMethod: (context: Context, input: Input) => RPCResult<Output>, flowId: string, input: Input, runId?: string): Promise<Output>;
18
+ invokeRPC<Output>(rpcMethod: (context: Context) => RPCResult<Output>, flowId: string, runId?: string): Promise<Output>;
19
+ invokeRPC<Input>(rpcMethod: (context: Context, input: Input) => void, flowId: string, input: Input, runId?: string): Promise<void>;
20
+ invokeRPC(rpcMethod: (context: Context) => void, flowId: string, runId?: string): Promise<void>;
21
+ getAttribute<T>(flowId: string, attribute: Attribute<T>, runId?: string): Promise<T | undefined>;
22
+ getAttribute<T>(flowId: string, attribute: AttributeMap<T>, instance: string, runId?: string): Promise<T | undefined>;
23
+ setAttribute<T>(flowId: string, attribute: Attribute<T>, value: T, runId?: string): Promise<void>;
24
+ setAttribute<T>(flowId: string, attribute: AttributeMap<T>, instance: string, value: T, runId?: string): Promise<void>;
25
+ publish<T>(flowId: string, channel: Channel<T>, ...values: readonly T[]): Promise<void>;
26
+ publish<T>(flowId: string, channel: ChannelMap<T>, instance: string, ...values: readonly T[]): Promise<void>;
27
+ waitForFlow(flowId: string): Promise<void>;
28
+ waitForFlow<Output>(flowId: string, outputCodec: Codec<Output>, timeoutMs?: number): Promise<Output>;
29
+ stopFlow(flowId: string, options?: StopFlowOptions): Promise<void>;
30
+ describeFlow(flowId: string): Promise<FlowInfo>;
31
+ resetFlow(flowId: string, options: ResetFlowOptions): Promise<string>;
32
+ skipTimer(flowId: string, stepExecutionId: StepExecutionId, timerId: TimerId): Promise<void>;
33
+ waitForStepCompletion(flowId: string, stepExecutionId: StepExecutionId, timeoutMs: number): Promise<void>;
34
+ updateFlowConfig(flowId: string, config: FlowConfig): Promise<void>;
35
+ close(): Promise<void>;
36
+ }
@@ -0,0 +1,441 @@
1
+ // Copyright (c) 2026 Super Durable, Inc.
2
+ //
3
+ // Licensed under the Super Durable Source License 1.0.
4
+ // You may not use this file except in compliance with the License.
5
+ // See the LICENSE file in the repository root.
6
+ //
7
+ // SPDX-License-Identifier: LicenseRef-Super-Durable-1.0
8
+ import { credentials, status as GrpcStatus } from "@grpc/grpc-js";
9
+ import { ActiveStepSearchMode as ProtoActiveStepSearchMode, FlowErrorType as ProtoFlowErrorType, FlowResetType as ProtoFlowResetType, FlowServiceClient, FlowStatus as ProtoFlowStatus, IdReusePolicy as ProtoIdReusePolicy, IndexType as ProtoIndexType, ExecuteMethodFailurePolicy, StartFlowRequest, StepDurability as ProtoStepDurability, StepOptions as ProtoStepOptions, StopType as ProtoStopType, WaitForMethodFailurePolicy, } from "./gen/dex.js";
10
+ import { DexError, ErrorSubStatus, FlowErrorType, FlowUncompletedError, LongPollTimeoutError, } from "./errors.js";
11
+ import { registeredFlow, registeredRPC, } from "./flow.js";
12
+ import { translateServiceError } from "./grpc-status.js";
13
+ import { ActiveStepSearchMode, IdReusePolicy, ResetType, StopType, } from "./options.js";
14
+ import { AttributeMap, IndexType } from "./persistence.js";
15
+ import { requireName } from "./validation.js";
16
+ import { decodeValue, encodeValue, ValueHydrator } from "./value-mapper.js";
17
+ import { ChannelMap } from "./wait.js";
18
+ const defaultServerAddress = "localhost:8801";
19
+ export class Client {
20
+ registry;
21
+ blobCache;
22
+ options;
23
+ service;
24
+ hydrator;
25
+ constructor(registry, blobCache, options = {}) {
26
+ this.registry = registry;
27
+ this.blobCache = blobCache;
28
+ this.options = options;
29
+ this.service = new FlowServiceClient(options.serverAddress ?? defaultServerAddress, credentials.createInsecure());
30
+ this.hydrator = new ValueHydrator(this.service, blobCache);
31
+ }
32
+ async startFlow(flow, flowId, input, options = {}) {
33
+ const registered = registeredFlow(this.registry, flow);
34
+ const request = StartFlowRequest.create({
35
+ flowId: requireName(flowId),
36
+ flowType: registered.name,
37
+ flowTimeoutSeconds: seconds(options.timeoutMs),
38
+ requestId: options.requestId ?? crypto.randomUUID(),
39
+ flowStartOptions: {
40
+ idReusePolicy: mapIdReusePolicy(options.idReusePolicy),
41
+ cronSchedule: options.cronSchedule ?? "",
42
+ flowStartDelaySeconds: seconds(options.startDelayMs),
43
+ retryPolicy: mapFlowRetryPolicy(options.retryPolicy),
44
+ attributes: (options.attributes ?? []).map((initial) => ({
45
+ key: physicalName(initial.attribute.name, initial.instance),
46
+ value: encodeValue(initial.attribute.codec, initial.value),
47
+ indexConfig: mapIndex(initial.attribute.index),
48
+ })),
49
+ flowConfigOverride: mapFlowConfig(options.configOverride, this.options),
50
+ flowAlreadyStartedOptions: {
51
+ ignoreAlreadyStartedError: options.ignoreAlreadyStarted ?? false,
52
+ },
53
+ },
54
+ });
55
+ if (registered.startStep === undefined) {
56
+ if (input !== undefined) {
57
+ throw new TypeError("Flow without a start Step requires undefined input");
58
+ }
59
+ }
60
+ else {
61
+ request.startStepType = registered.startStep.name;
62
+ request.stepInput = encodeValue(registered.startStep.step.inputCodec, input);
63
+ request.stepOptions = mapStepOptions(registered.startStep.step.getStepOptions?.(), registered.startStep.step.waitFor === undefined, registered);
64
+ }
65
+ return (await unary((callback) => this.service.startFlow(request, callback)))
66
+ .runId;
67
+ }
68
+ async invokeRPC(rpcMethod, flowId, inputOrRunId, runId = "") {
69
+ const rpc = registeredRPC(this.registry, rpcMethod);
70
+ const hasInput = rpc.options.inputCodec !== undefined;
71
+ const response = await unary((callback) => this.service.invokeRpc({
72
+ flowId: requireName(flowId),
73
+ runId: hasInput ? runId : inputOrRunId ?? "",
74
+ rpcName: rpc.name,
75
+ input: hasInput
76
+ ? encodeValue(rpc.options.inputCodec, inputOrRunId)
77
+ : undefined,
78
+ timeoutSeconds: seconds(rpc.options.timeoutMs),
79
+ lockAttributeKeys: (rpc.options.lockAttributes ?? []).map((lock) => physicalName(lock.attribute.name, lock.instance)),
80
+ requestId: crypto.randomUUID(),
81
+ }, callback));
82
+ if (rpc.options.outputCodec === undefined) {
83
+ return undefined;
84
+ }
85
+ return decodeValue(rpc.options.outputCodec, await this.hydrator.hydrate(response.output));
86
+ }
87
+ async getAttribute(flowId, attribute, instanceOrRunId = "", runId = "") {
88
+ const isMap = attribute instanceof AttributeMap;
89
+ const response = await unary((callback) => this.service.getAttributes({
90
+ flowId: requireName(flowId),
91
+ runId: isMap ? runId : instanceOrRunId,
92
+ keys: [physicalName(attribute.name, isMap ? instanceOrRunId : undefined)],
93
+ allKeys: false,
94
+ }, callback));
95
+ const value = response.attributes[0]?.value;
96
+ if (value === undefined) {
97
+ return undefined;
98
+ }
99
+ return decodeValue(attribute.codec, await this.hydrator.hydrate(value));
100
+ }
101
+ async setAttribute(flowId, attribute, instanceOrValue, valueOrRunId, runId = "") {
102
+ const isMap = attribute instanceof AttributeMap;
103
+ const instance = isMap ? String(instanceOrValue) : undefined;
104
+ const value = isMap ? valueOrRunId : instanceOrValue;
105
+ await unary((callback) => this.service.setAttributes({
106
+ flowId: requireName(flowId),
107
+ runId: isMap ? runId : typeof valueOrRunId === "string" ? valueOrRunId : "",
108
+ attributes: [
109
+ {
110
+ key: physicalName(attribute.name, instance),
111
+ value: encodeValue(attribute.codec, value),
112
+ indexConfig: mapIndex(attribute.index),
113
+ },
114
+ ],
115
+ requestId: crypto.randomUUID(),
116
+ }, callback));
117
+ }
118
+ async publish(flowId, channel, ...instanceAndValues) {
119
+ const isMap = channel instanceof ChannelMap;
120
+ const instance = isMap ? String(instanceAndValues[0]) : undefined;
121
+ const values = isMap ? instanceAndValues.slice(1) : instanceAndValues;
122
+ await unary((callback) => this.service.publishToChannel({
123
+ flowId: requireName(flowId),
124
+ runId: "",
125
+ messages: values.map((value) => ({
126
+ channelName: physicalName(channel.name, instance),
127
+ value: encodeValue(channel.codec, value),
128
+ })),
129
+ }, callback));
130
+ }
131
+ async waitForFlow(flowId, outputCodec, timeoutMs) {
132
+ let response;
133
+ try {
134
+ response = await unary((callback) => this.service.waitForFlow({
135
+ flowId: requireName(flowId),
136
+ runId: "",
137
+ needsResults: outputCodec !== undefined,
138
+ waitTimeSeconds: seconds(timeoutMs),
139
+ }, callback));
140
+ }
141
+ catch (failure) {
142
+ if (failure instanceof DexError &&
143
+ (failure.code === GrpcStatus.DEADLINE_EXCEEDED ||
144
+ failure.subStatus === ErrorSubStatus.LONG_POLL_TIMEOUT)) {
145
+ throw new LongPollTimeoutError(flowId, { cause: failure });
146
+ }
147
+ throw failure;
148
+ }
149
+ if (response.flowStatus !== ProtoFlowStatus.FLOW_STATUS_COMPLETED) {
150
+ const summary = await this.describeFlow(flowId);
151
+ const results = await this.hydrator.hydrateAll(response.results.map((result) => result.completedStepOutput));
152
+ throw new FlowUncompletedError(summary.runId, mapFlowStatus(response.flowStatus), mapFlowErrorType(response.errorType), response.errorMessage || undefined, results);
153
+ }
154
+ if (outputCodec === undefined) {
155
+ return undefined;
156
+ }
157
+ for (let index = response.results.length - 1; index >= 0; index -= 1) {
158
+ const value = response.results[index]?.completedStepOutput;
159
+ if (value !== undefined) {
160
+ return decodeValue(outputCodec, await this.hydrator.hydrate(value));
161
+ }
162
+ }
163
+ throw new TypeError(`Flow ${flowId} completed without an output`);
164
+ }
165
+ async stopFlow(flowId, options = {}) {
166
+ await unary((callback) => this.service.stopFlow({
167
+ flowId: requireName(flowId),
168
+ runId: "",
169
+ reason: options.reason ?? "",
170
+ stopType: mapStopType(options.type),
171
+ }, callback));
172
+ }
173
+ async describeFlow(flowId) {
174
+ const response = await unary((callback) => this.service.getFlowSummary({ flowId: requireName(flowId), runId: "" }, callback));
175
+ if (response.flowExecutionId === undefined || response.startTime === undefined) {
176
+ throw new TypeError(`Dex returned an incomplete summary for Flow ${flowId}`);
177
+ }
178
+ return {
179
+ flowId: response.flowExecutionId.flowId,
180
+ runId: response.flowExecutionId.runId,
181
+ flowType: response.flowType,
182
+ status: mapFlowStatus(response.flowStatus),
183
+ startedAt: response.startTime,
184
+ };
185
+ }
186
+ async resetFlow(flowId, options) {
187
+ const response = await unary((callback) => this.service.resetFlow({
188
+ flowId: requireName(flowId),
189
+ runId: "",
190
+ resetType: mapResetType(options.type),
191
+ historyEventId: number64(options.historyEventId),
192
+ reason: options.reason ?? "",
193
+ historyEventTime: options.historyEventTime?.toISOString() ?? "",
194
+ stepType: options.stepType ?? "",
195
+ stepExecutionId: options.stepExecutionId ?? "",
196
+ skipChannelMessagesReapply: options.skipChannelMessagesReapply ?? false,
197
+ skipLockingRpcReapply: options.skipLockingRpcReapply ?? false,
198
+ }, callback));
199
+ return response.runId;
200
+ }
201
+ async skipTimer(flowId, stepExecutionId, timerId) {
202
+ await unary((callback) => this.service.skipTimer({
203
+ flowId: requireName(flowId),
204
+ runId: "",
205
+ stepExecutionId: `${stepExecutionId.stepType}-${stepExecutionId.number ?? 1}`,
206
+ timerConditionId: timerId.conditionId ?? "",
207
+ timerConditionIndex: timerId.conditionIndex,
208
+ }, callback));
209
+ }
210
+ async waitForStepCompletion(flowId, stepExecutionId, timeoutMs) {
211
+ await unary((callback) => this.service.waitForStepCompletion({
212
+ flowId: requireName(flowId),
213
+ stepType: stepExecutionId.stepType,
214
+ stepExecutionNumber: String(stepExecutionId.number ?? 1),
215
+ waitTimeSeconds: seconds(timeoutMs),
216
+ requestId: crypto.randomUUID(),
217
+ }, callback));
218
+ }
219
+ async updateFlowConfig(flowId, config) {
220
+ await unary((callback) => this.service.updateFlowConfig({
221
+ flowId: requireName(flowId),
222
+ runId: "",
223
+ flowConfig: mapFlowConfig(config, this.options),
224
+ }, callback));
225
+ }
226
+ async close() {
227
+ this.service.close();
228
+ }
229
+ }
230
+ function mapStepOptions(options, skipWaitFor, flow) {
231
+ const executeFailureStep = options?.executeFailure?.step;
232
+ const executeFailureDefinition = executeFailureStep === undefined
233
+ ? undefined
234
+ : flow.steps.find((definition) => definition.step === executeFailureStep);
235
+ if (executeFailureStep !== undefined && executeFailureDefinition === undefined) {
236
+ throw new TypeError("execute failure Step must belong to the Flow");
237
+ }
238
+ const executeFailureOptions = options?.executeFailure?.options ?? executeFailureDefinition?.step.getStepOptions?.();
239
+ return ProtoStepOptions.create({
240
+ waitForTimeoutSeconds: seconds(options?.waitForMethodTimeoutMs),
241
+ executeTimeoutSeconds: seconds(options?.executeMethodTimeoutMs),
242
+ waitForRetryPolicy: mapRetryPolicy(options?.waitForRetry),
243
+ executeRetryPolicy: mapRetryPolicy(options?.executeRetry),
244
+ waitForFailurePolicy: options?.waitForFailure === "proceed"
245
+ ? WaitForMethodFailurePolicy.WAIT_FOR_METHOD_FAILURE_POLICY_PROCEED_ON_FAILURE
246
+ : options?.waitForFailure === "failFlow"
247
+ ? WaitForMethodFailurePolicy.WAIT_FOR_METHOD_FAILURE_POLICY_FAIL_FLOW_ON_FAILURE
248
+ : WaitForMethodFailurePolicy.WAIT_FOR_METHOD_FAILURE_POLICY_UNSPECIFIED,
249
+ executeFailurePolicy: executeFailureDefinition === undefined
250
+ ? ExecuteMethodFailurePolicy.EXECUTE_METHOD_FAILURE_POLICY_UNSPECIFIED
251
+ : ExecuteMethodFailurePolicy.EXECUTE_METHOD_FAILURE_POLICY_PROCEED_TO_CONFIGURED_STEP,
252
+ executeFailureProceedStepType: executeFailureDefinition?.name ?? "",
253
+ executeFailureProceedStepOptions: executeFailureDefinition === undefined
254
+ ? undefined
255
+ : mapStepOptions(executeFailureOptions, executeFailureDefinition.step.waitFor === undefined, flow),
256
+ skipWaitFor,
257
+ waitForDurabilityOverride: mapDurability(options?.waitForDurability),
258
+ executeDurabilityOverride: mapDurability(options?.executeDurability),
259
+ waitForLockAttributeKeys: (options?.waitForLockAttributes ?? []).map((lock) => physicalName(lock.attribute.name, lock.instance)),
260
+ executeLockAttributeKeys: (options?.executeLockAttributes ?? []).map((lock) => physicalName(lock.attribute.name, lock.instance)),
261
+ });
262
+ }
263
+ function mapRetryPolicy(retry) {
264
+ if (retry === undefined) {
265
+ return undefined;
266
+ }
267
+ return {
268
+ initialIntervalSeconds: seconds(retry.initialIntervalMs),
269
+ backoffCoefficient: retry.backoffCoefficient ?? 0,
270
+ maximumIntervalSeconds: seconds(retry.maximumIntervalMs),
271
+ maximumAttempts: retry.maximumAttempts ?? 0,
272
+ totalDurationSeconds: seconds(retry.totalDurationMs),
273
+ };
274
+ }
275
+ function mapFlowRetryPolicy(retry) {
276
+ if (retry === undefined) {
277
+ return undefined;
278
+ }
279
+ return {
280
+ initialIntervalSeconds: seconds(retry.initialIntervalMs),
281
+ backoffCoefficient: retry.backoffCoefficient ?? 0,
282
+ maximumIntervalSeconds: seconds(retry.maximumIntervalMs),
283
+ maximumAttempts: retry.maximumAttempts ?? 0,
284
+ };
285
+ }
286
+ function mapFlowConfig(config, clientOptions) {
287
+ const workerTarget = config?.workerTarget ?? clientOptions.workerTarget;
288
+ if (config === undefined && workerTarget === undefined) {
289
+ return undefined;
290
+ }
291
+ return {
292
+ activeStepSearchMode: config?.activeStepSearchMode === undefined
293
+ ? undefined
294
+ : config.activeStepSearchMode === ActiveStepSearchMode.ALL
295
+ ? ProtoActiveStepSearchMode.ACTIVE_STEP_SEARCH_MODE_ENABLED_FOR_ALL
296
+ : ProtoActiveStepSearchMode.ACTIVE_STEP_SEARCH_MODE_UNSPECIFIED,
297
+ continueAsNewThreshold: config?.continueAsNewThreshold,
298
+ continueAsNewPageSizeInBytes: config?.continueAsNewPageSizeBytes,
299
+ stepDurability: config?.stepDurability === undefined ? undefined : mapDurability(config.stepDurability),
300
+ workerTarget: workerTarget === undefined
301
+ ? undefined
302
+ : { address: workerTarget.address, isHeadlessAddress: workerTarget.headless ?? false },
303
+ };
304
+ }
305
+ function mapIndex(index) {
306
+ if (index === undefined) {
307
+ return undefined;
308
+ }
309
+ const types = {
310
+ [IndexType.KEYWORD]: ProtoIndexType.INDEX_TYPE_KEYWORD,
311
+ [IndexType.FULL_TEXT]: ProtoIndexType.INDEX_TYPE_TEXT,
312
+ [IndexType.KEYWORD_ARRAY]: ProtoIndexType.INDEX_TYPE_KEYWORD_ARRAY,
313
+ [IndexType.INT]: ProtoIndexType.INDEX_TYPE_INT,
314
+ [IndexType.DOUBLE]: ProtoIndexType.INDEX_TYPE_DOUBLE,
315
+ [IndexType.BOOL]: ProtoIndexType.INDEX_TYPE_BOOL,
316
+ [IndexType.DATETIME]: ProtoIndexType.INDEX_TYPE_DATETIME,
317
+ };
318
+ return {
319
+ enable: true,
320
+ type: types[index.type],
321
+ indexKey: index.indexKey ?? "",
322
+ };
323
+ }
324
+ function mapIdReusePolicy(policy) {
325
+ switch (policy) {
326
+ case IdReusePolicy.ALLOW_IF_PREVIOUS_FAILED:
327
+ return ProtoIdReusePolicy.ID_REUSE_POLICY_ALLOW_IF_PREVIOUS_EXISTS_ABNORMALLY;
328
+ case IdReusePolicy.ALLOW_IF_NOT_RUNNING:
329
+ return ProtoIdReusePolicy.ID_REUSE_POLICY_ALLOW_IF_NO_RUNNING;
330
+ case IdReusePolicy.ALLOW_TERMINATE_IF_RUNNING:
331
+ return ProtoIdReusePolicy.ID_REUSE_POLICY_ALLOW_TERMINATE_IF_RUNNING;
332
+ case IdReusePolicy.DISALLOW:
333
+ return ProtoIdReusePolicy.ID_REUSE_POLICY_DISALLOW_REUSE;
334
+ default:
335
+ return ProtoIdReusePolicy.ID_REUSE_POLICY_UNSPECIFIED;
336
+ }
337
+ }
338
+ function mapStopType(type) {
339
+ switch (type) {
340
+ case StopType.TERMINATE:
341
+ return ProtoStopType.STOP_TYPE_TERMINATE;
342
+ case StopType.FAIL:
343
+ return ProtoStopType.STOP_TYPE_FAIL;
344
+ default:
345
+ return ProtoStopType.STOP_TYPE_CANCEL;
346
+ }
347
+ }
348
+ function mapResetType(type) {
349
+ const types = {
350
+ [ResetType.BEGINNING]: ProtoFlowResetType.FLOW_RESET_TYPE_BEGINNING,
351
+ [ResetType.HISTORY_EVENT_ID]: ProtoFlowResetType.FLOW_RESET_TYPE_HISTORY_EVENT_ID,
352
+ [ResetType.HISTORY_EVENT_TIME]: ProtoFlowResetType.FLOW_RESET_TYPE_HISTORY_EVENT_TIME,
353
+ [ResetType.STEP_TYPE]: ProtoFlowResetType.FLOW_RESET_TYPE_STEP_TYPE,
354
+ [ResetType.STEP_EXECUTION_ID]: ProtoFlowResetType.FLOW_RESET_TYPE_STEP_EXECUTION_ID,
355
+ };
356
+ return types[type];
357
+ }
358
+ function mapDurability(value) {
359
+ if (value === "sync") {
360
+ return ProtoStepDurability.STEP_DURABILITY_SYNC;
361
+ }
362
+ if (value === "async") {
363
+ return ProtoStepDurability.STEP_DURABILITY_ASYNC;
364
+ }
365
+ return ProtoStepDurability.STEP_DURABILITY_UNSPECIFIED;
366
+ }
367
+ function mapFlowStatus(status) {
368
+ switch (status) {
369
+ case ProtoFlowStatus.FLOW_STATUS_RUNNING:
370
+ return "running";
371
+ case ProtoFlowStatus.FLOW_STATUS_COMPLETED:
372
+ return "completed";
373
+ case ProtoFlowStatus.FLOW_STATUS_FAILED:
374
+ return "failed";
375
+ case ProtoFlowStatus.FLOW_STATUS_TIMEOUT:
376
+ return "timedOut";
377
+ case ProtoFlowStatus.FLOW_STATUS_TERMINATED:
378
+ return "terminated";
379
+ case ProtoFlowStatus.FLOW_STATUS_CANCELED:
380
+ return "cancelled";
381
+ case ProtoFlowStatus.FLOW_STATUS_CONTINUED_AS_NEW:
382
+ return "continuedAsNew";
383
+ default:
384
+ throw new TypeError(`unsupported Flow status ${status}`);
385
+ }
386
+ }
387
+ function mapFlowErrorType(type) {
388
+ switch (type) {
389
+ case ProtoFlowErrorType.FLOW_ERROR_TYPE_STEP_DECISION_FAILING_FLOW:
390
+ return FlowErrorType.STEP_DECISION_FAILED;
391
+ case ProtoFlowErrorType.FLOW_ERROR_TYPE_CLIENT_API_FAILING_FLOW:
392
+ return FlowErrorType.CLIENT_API_FAILED;
393
+ case ProtoFlowErrorType.FLOW_ERROR_TYPE_WORKER_API_FAIL:
394
+ return FlowErrorType.WORKER_API_FAILED;
395
+ case ProtoFlowErrorType.FLOW_ERROR_TYPE_INVALID_USER_FLOW_CODE:
396
+ return FlowErrorType.INVALID_USER_FLOW_CODE;
397
+ case ProtoFlowErrorType.FLOW_ERROR_TYPE_INTERNAL:
398
+ return FlowErrorType.INTERNAL;
399
+ default:
400
+ return undefined;
401
+ }
402
+ }
403
+ function physicalName(name, instance) {
404
+ if (instance === undefined) {
405
+ return name;
406
+ }
407
+ requireName(instance);
408
+ const encoded = encodeURIComponent(instance).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
409
+ return `${name}/${encoded}`;
410
+ }
411
+ function seconds(milliseconds) {
412
+ if (milliseconds === undefined) {
413
+ return 0;
414
+ }
415
+ if (!Number.isSafeInteger(milliseconds) || milliseconds < 0 || milliseconds % 1_000 !== 0) {
416
+ throw new RangeError("duration must be a non-negative whole number of seconds");
417
+ }
418
+ return milliseconds / 1_000;
419
+ }
420
+ function number64(value) {
421
+ if (value === undefined) {
422
+ return 0;
423
+ }
424
+ const number = Number(value);
425
+ if (!Number.isSafeInteger(number)) {
426
+ throw new RangeError("history event ID exceeds JavaScript's safe integer range");
427
+ }
428
+ return number;
429
+ }
430
+ function unary(invoke) {
431
+ return new Promise((resolve, reject) => {
432
+ invoke((error, response) => {
433
+ if (error !== null) {
434
+ reject(translateServiceError(error));
435
+ return;
436
+ }
437
+ resolve(response);
438
+ });
439
+ });
440
+ }
441
+ //# sourceMappingURL=client.js.map