Skip to content

Commit cf873d5

Browse files
Refactor (rebuilt) top layer and OpenAPI layer code, enable HTTP/2 (#80)
Refactored (rebuilt) the top layer and the OpenAPI-generated layer to eliminate manually modified logic in generated code. This will make it easier to track backend updates and to add new functionality with minimal risk. Ultimately, OpenAPI-generated code should remain untouched, and all customizations should be implemented in the top layers instead. The next step is to regenerate the code for each OpenAPI service (resource) individually. As part of the refactor, environment variables are now automatically prioritized over config variables, without requiring the "useEnvVars: true" config flag. Enabled HTTP/2 support for the Node.js environment (powered by Undici). Undici was chosen because it provides the default built-in fetch in Node.js (since v18) and is expected to make H2 requests by default out of the box starting with Node.js v25. The default fetch remains in use for browser environments (browser fetch APIs already prioritize H2) or if undici fails to initialize. Also added the ability to pass in any custom fetch-like function when creating the client so sdk in theory can work on older versions of node.
1 parent 8bd8587 commit cf873d5

31 files changed

Lines changed: 1014 additions & 1029 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import { OrkesApiConfig, orkesConductorClient } from "@io-orkes/conductor-javasc
4444
const config: Partial<OrkesApiConfig> = {
4545
keyId: "XXX", // optional
4646
keySecret: "XXXX", // optional
47-
refreshTokenInterval: 0, // optional (in milliseconds) defaults to 30 minutes (30 * 60 * 1000). 0 no refresh
47+
refreshTokenInterval: 0, // optional (in milliseconds) | defaults to 30 minutes | 0 = no refresh
4848
serverUrl: "https://play.orkes.io/api",
4949
};
5050

File renamed without changes.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,8 @@
8888
"suiteNameTemplate": "{filepath}",
8989
"classNameTemplate": "{classname}",
9090
"titleTemplate": "{title}"
91+
},
92+
"optionalDependencies": {
93+
"undici": "^7.16.0"
9194
}
92-
}
95+
}

src/__test__/readme.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { TaskType } from "../common";
55
import { TaskRunner } from "../task";
66

77
describe("TaskManager", () => {
8-
const clientPromise = orkesConductorClient({ useEnvVars: true });
8+
const clientPromise = orkesConductorClient();
99

1010
jest.setTimeout(20000);
1111
test("worker example ", async () => {

src/common/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export {
66
ConductorClient,
77
ApiRequestOptions,
88
ApiResult,
9-
ConductorClientAPIConfig,
109
OpenAPIConfig,
1110
OnCancel,
1211
ApiError,
Lines changed: 11 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
/* generated using openapi-typescript-codegen -- do not edit */
12
/* istanbul ignore file */
23
/* tslint:disable */
34
/* eslint-disable */
45
import type { BaseHttpRequest } from "./core/BaseHttpRequest";
5-
import type { OpenAPIConfig, Resolver } from "./core/OpenAPI";
6-
6+
import type { OpenAPIConfig } from "./core/OpenAPI";
77
import { EventResourceService } from "./services/EventResourceService";
88
import { HealthCheckResourceService } from "./services/HealthCheckResourceService";
99
import { MetadataResourceService } from "./services/MetadataResourceService";
@@ -12,36 +12,12 @@ import { TaskResourceService } from "./services/TaskResourceService";
1212
import { TokenResourceService } from "./services/TokenResourceService";
1313
import { WorkflowBulkResourceService } from "./services/WorkflowBulkResourceService";
1414
import { WorkflowResourceService } from "./services/WorkflowResourceService";
15-
import { request as baseRequest } from "./core/request";
16-
import { ConductorHttpRequest } from "../RequestCustomizer";
1715
import { HumanTaskService } from "./services/HumanTaskService";
1816
import { HumanTaskResourceService } from "./services/HumanTaskResourceService";
19-
import {ServiceRegistryResourceService} from "./services/ServiceRegistryResourceService";
20-
21-
export const defaultRequestHandler: ConductorHttpRequest = (
22-
request,
23-
config,
24-
options
25-
) => request(config, options);
26-
27-
export interface ConductorClientAPIConfig extends Omit<OpenAPIConfig, "BASE"> {
28-
serverUrl: string;
29-
useEnvVars: boolean;
30-
}
31-
32-
const getServerBaseURL = (config?: Partial<ConductorClientAPIConfig>) => {
33-
if (config?.useEnvVars) {
34-
if(!process.env.CONDUCTOR_SERVER_URL) {
35-
throw new Error(
36-
"Environment variable CONDUCTOR_SERVER_URL is not defined."
37-
);
38-
}
17+
import { ServiceRegistryResourceService } from "./services/ServiceRegistryResourceService";
18+
import { NodeHttpRequest } from "./core/NodeHttpRequest";
3919

40-
return process.env.CONDUCTOR_SERVER_URL;
41-
}
42-
43-
return config?.serverUrl ?? "http://localhost:8080";
44-
};
20+
type HttpRequestConstructor = new (config: OpenAPIConfig) => BaseHttpRequest;
4521

4622
export class ConductorClient {
4723
public readonly eventResource: EventResourceService;
@@ -53,47 +29,25 @@ export class ConductorClient {
5329
public readonly workflowBulkResource: WorkflowBulkResourceService;
5430
public readonly workflowResource: WorkflowResourceService;
5531
public readonly serviceRegistryResource: ServiceRegistryResourceService;
56-
5732
public readonly humanTask: HumanTaskService;
5833
public readonly humanTaskResource: HumanTaskResourceService;
5934
public readonly request: BaseHttpRequest;
6035

61-
public token?: string | Resolver<string>;
62-
6336
constructor(
64-
config?: Partial<ConductorClientAPIConfig>,
65-
requestHandler: ConductorHttpRequest = defaultRequestHandler
37+
config?: Partial<OpenAPIConfig>,
38+
HttpRequest: HttpRequestConstructor = NodeHttpRequest
6639
) {
67-
const resolvedConfig = {
68-
BASE: getServerBaseURL(config),
69-
VERSION: config?.VERSION ?? "0",
40+
this.request = new HttpRequest({
41+
BASE: config?.BASE ?? "http://localhost:8080",
42+
VERSION: config?.VERSION ?? "2",
7043
WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false,
7144
CREDENTIALS: config?.CREDENTIALS ?? "include",
7245
TOKEN: config?.TOKEN,
7346
USERNAME: config?.USERNAME,
7447
PASSWORD: config?.PASSWORD,
7548
HEADERS: config?.HEADERS,
7649
ENCODE_PATH: config?.ENCODE_PATH,
77-
};
78-
79-
// START conductor-client-modification
80-
/* The generated models are all based on the concept of an instantiated base http
81-
class. To avoid making edits there, we just create an object that satisfies the same
82-
interface. Yay typescript!
83-
*/
84-
this.request = {
85-
config: resolvedConfig,
86-
request: (apiConfig) => {
87-
return requestHandler(
88-
baseRequest,
89-
{ ...resolvedConfig, TOKEN: this.token },
90-
apiConfig
91-
);
92-
},
93-
};
94-
this.token = config?.TOKEN;
95-
// END conductor-client-modification
96-
50+
});
9751
this.eventResource = new EventResourceService(this.request);
9852
this.healthCheckResource = new HealthCheckResourceService(this.request);
9953
this.metadataResource = new MetadataResourceService(this.request);
@@ -106,5 +60,4 @@ export class ConductorClient {
10660
this.humanTask = new HumanTaskService(this.request);
10761
this.humanTaskResource = new HumanTaskResourceService(this.request);
10862
}
109-
stop() {}
11063
}

src/common/open-api/__test__/EventResourceService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { orkesConductorClient } from "../../../orkes";
33

44
describe("EventResourceService", () => {
55
test("Should create an event handler with description and tags and then delete it", async () => {
6-
const orkesClient = await orkesConductorClient({ useEnvVars: true });
6+
const orkesClient = await orkesConductorClient();
77
const eventApi = orkesClient.eventResource;
88

99
const [eventName, event, eventDescription, eventTagKey, eventTagValue] = [

src/common/open-api/__test__/WorkflowResourceService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describe("WorkflowResourceService", () => {
88
jest.setTimeout(120000);
99

1010
test("Should test a workflow", async () => {
11-
const client = await orkesConductorClient({ useEnvVars: true });
11+
const client = await orkesConductorClient();
1212
const metadataClient = new MetadataClient(client);
1313
const tasks: TaskDefTypes[] = [
1414
simpleTask("simple_ref", "le_simple_task", {}),
Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* generated using openapi-typescript-codegen -- do not edit */
12
/* istanbul ignore file */
23
/* tslint:disable */
34
/* eslint-disable */
@@ -22,15 +23,13 @@ export interface OnCancel {
2223
}
2324

2425
export class CancelablePromise<T> implements Promise<T> {
25-
readonly [Symbol.toStringTag]!: string;
26-
27-
private _isResolved: boolean;
28-
private _isRejected: boolean;
29-
private _isCancelled: boolean;
30-
private readonly _cancelHandlers: (() => void)[];
31-
private readonly _promise: Promise<T>;
32-
private _resolve?: (value: T | PromiseLike<T>) => void;
33-
private _reject?: (reason?: any) => void;
26+
#isResolved: boolean;
27+
#isRejected: boolean;
28+
#isCancelled: boolean;
29+
readonly #cancelHandlers: (() => void)[];
30+
readonly #promise: Promise<T>;
31+
#resolve?: (value: T | PromiseLike<T>) => void;
32+
#reject?: (reason?: any) => void;
3433

3534
constructor(
3635
executor: (
@@ -39,90 +38,94 @@ export class CancelablePromise<T> implements Promise<T> {
3938
onCancel: OnCancel
4039
) => void
4140
) {
42-
this._isResolved = false;
43-
this._isRejected = false;
44-
this._isCancelled = false;
45-
this._cancelHandlers = [];
46-
this._promise = new Promise<T>((resolve, reject) => {
47-
this._resolve = resolve;
48-
this._reject = reject;
41+
this.#isResolved = false;
42+
this.#isRejected = false;
43+
this.#isCancelled = false;
44+
this.#cancelHandlers = [];
45+
this.#promise = new Promise<T>((resolve, reject) => {
46+
this.#resolve = resolve;
47+
this.#reject = reject;
4948

5049
const onResolve = (value: T | PromiseLike<T>): void => {
51-
if (this._isResolved || this._isRejected || this._isCancelled) {
50+
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
5251
return;
5352
}
54-
this._isResolved = true;
55-
this._resolve?.(value);
53+
this.#isResolved = true;
54+
if (this.#resolve) this.#resolve(value);
5655
};
5756

5857
const onReject = (reason?: any): void => {
59-
if (this._isResolved || this._isRejected || this._isCancelled) {
58+
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
6059
return;
6160
}
62-
this._isRejected = true;
63-
this._reject?.(reason);
61+
this.#isRejected = true;
62+
if (this.#reject) this.#reject(reason);
6463
};
6564

6665
const onCancel = (cancelHandler: () => void): void => {
67-
if (this._isResolved || this._isRejected || this._isCancelled) {
66+
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
6867
return;
6968
}
70-
this._cancelHandlers.push(cancelHandler);
69+
this.#cancelHandlers.push(cancelHandler);
7170
};
7271

7372
Object.defineProperty(onCancel, 'isResolved', {
74-
get: (): boolean => this._isResolved,
73+
get: (): boolean => this.#isResolved,
7574
});
7675

7776
Object.defineProperty(onCancel, 'isRejected', {
78-
get: (): boolean => this._isRejected,
77+
get: (): boolean => this.#isRejected,
7978
});
8079

8180
Object.defineProperty(onCancel, 'isCancelled', {
82-
get: (): boolean => this._isCancelled,
81+
get: (): boolean => this.#isCancelled,
8382
});
8483

8584
return executor(onResolve, onReject, onCancel as OnCancel);
8685
});
8786
}
8887

88+
get [Symbol.toStringTag]() {
89+
return "Cancellable Promise";
90+
}
91+
8992
public then<TResult1 = T, TResult2 = never>(
9093
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
9194
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
9295
): Promise<TResult1 | TResult2> {
93-
return this._promise.then(onFulfilled, onRejected);
96+
return this.#promise.then(onFulfilled, onRejected);
9497
}
9598

9699
public catch<TResult = never>(
97100
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
98101
): Promise<T | TResult> {
99-
return this._promise.catch(onRejected);
102+
return this.#promise.catch(onRejected);
100103
}
101104

102105
public finally(onFinally?: (() => void) | null): Promise<T> {
103-
return this._promise.finally(onFinally);
106+
return this.#promise.finally(onFinally);
104107
}
105108

106109
public cancel(): void {
107-
if (this._isResolved || this._isRejected || this._isCancelled) {
110+
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
108111
return;
109112
}
110-
this._isCancelled = true;
111-
if (this._cancelHandlers.length) {
113+
this.#isCancelled = true;
114+
if (this.#cancelHandlers.length) {
112115
try {
113-
for (const cancelHandler of this._cancelHandlers) {
116+
for (const cancelHandler of this.#cancelHandlers) {
114117
cancelHandler();
115118
}
116119
} catch (error) {
117120
console.warn('Cancellation threw an error', error);
118121
return;
119122
}
120123
}
121-
this._cancelHandlers.length = 0;
122-
this._reject?.(new CancelError('Request aborted'));
124+
this.#cancelHandlers.length = 0;
125+
if (this.#reject) this.#reject(new CancelError('Request aborted'));
123126
}
124127

125128
public get isCancelled(): boolean {
126-
return this._isCancelled;
129+
return this.#isCancelled;
127130
}
128131
}

src/common/open-api/core/OpenAPI.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
1+
/* generated using openapi-typescript-codegen -- do not edit */
12
/* istanbul ignore file */
23
/* tslint:disable */
34
/* eslint-disable */
45
import type { ApiRequestOptions } from './ApiRequestOptions';
56

6-
export type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
7+
type Resolver<T> = (options: ApiRequestOptions) => Promise<T>;
78
type Headers = Record<string, string>;
89

910
export type OpenAPIConfig = {
1011
BASE: string;
1112
VERSION: string;
1213
WITH_CREDENTIALS: boolean;
1314
CREDENTIALS: 'include' | 'omit' | 'same-origin';
14-
TOKEN?: string | Resolver<string>;
15-
USERNAME?: string | Resolver<string>;
16-
PASSWORD?: string | Resolver<string>;
17-
HEADERS?: Headers | Resolver<Headers>;
18-
ENCODE_PATH?: (path: string) => string;
15+
TOKEN?: string | Resolver<string> | undefined;
16+
USERNAME?: string | Resolver<string> | undefined;
17+
PASSWORD?: string | Resolver<string> | undefined;
18+
HEADERS?: Headers | Resolver<Headers> | undefined;
19+
ENCODE_PATH?: ((path: string) => string) | undefined;
1920
};
2021

2122
export const OpenAPI: OpenAPIConfig = {
2223
BASE: 'http://localhost:8080',
23-
VERSION: '0',
24+
VERSION: '2',
2425
WITH_CREDENTIALS: false,
2526
CREDENTIALS: 'include',
2627
TOKEN: undefined,

0 commit comments

Comments
 (0)