@renderinc/sdk 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +17 -7
- package/biome.json +84 -0
- package/dist/experimental/blob/api.d.ts +11 -0
- package/dist/experimental/blob/api.d.ts.map +1 -0
- package/dist/experimental/blob/api.js +44 -0
- package/dist/experimental/blob/client.d.ts +21 -0
- package/dist/experimental/blob/client.d.ts.map +1 -0
- package/dist/experimental/blob/client.js +127 -0
- package/dist/experimental/blob/index.d.ts +5 -0
- package/dist/experimental/blob/index.d.ts.map +1 -0
- package/dist/experimental/blob/index.js +8 -0
- package/dist/experimental/blob/types.d.ts +49 -0
- package/dist/experimental/blob/types.d.ts.map +1 -0
- package/dist/experimental/experimental.d.ts +12 -0
- package/dist/experimental/experimental.d.ts.map +1 -0
- package/dist/experimental/experimental.js +16 -0
- package/dist/experimental/index.d.ts +3 -0
- package/dist/experimental/index.d.ts.map +1 -0
- package/dist/experimental/index.js +10 -0
- package/dist/experimental/object/api.d.ts +11 -0
- package/dist/experimental/object/api.d.ts.map +1 -0
- package/dist/experimental/object/api.js +44 -0
- package/dist/experimental/object/client.d.ts +21 -0
- package/dist/experimental/object/client.d.ts.map +1 -0
- package/dist/experimental/object/client.js +127 -0
- package/dist/experimental/object/index.d.ts +5 -0
- package/dist/experimental/object/index.d.ts.map +1 -0
- package/dist/experimental/object/index.js +8 -0
- package/dist/experimental/object/types.d.ts +49 -0
- package/dist/experimental/object/types.d.ts.map +1 -0
- package/dist/experimental/object/types.js +2 -0
- package/dist/generated/schema.d.ts +9910 -0
- package/dist/generated/schema.d.ts.map +1 -0
- package/dist/generated/schema.js +2 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/render.d.ts +2 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +4 -2
- package/dist/utils/create-api-client.d.ts +1 -1
- package/dist/utils/create-api-client.d.ts.map +1 -1
- package/dist/utils/create-api-client.js +2 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +23 -0
- package/dist/workflows/client/client.d.ts +1 -1
- package/dist/workflows/client/client.d.ts.map +1 -1
- package/dist/workflows/client/sse.d.ts +2 -2
- package/dist/workflows/client/sse.d.ts.map +1 -1
- package/dist/workflows/client/sse.js +2 -0
- package/dist/workflows/client/types.d.ts +1 -1
- package/dist/workflows/client/types.d.ts.map +1 -1
- package/dist/workflows/executor.d.ts +2 -2
- package/dist/workflows/executor.d.ts.map +1 -1
- package/dist/workflows/registry.d.ts +1 -1
- package/dist/workflows/registry.d.ts.map +1 -1
- package/dist/workflows/registry.js +13 -6
- package/dist/workflows/runner.d.ts.map +1 -1
- package/dist/workflows/runner.js +2 -0
- package/dist/workflows/schema.d.ts +9 -0
- package/dist/workflows/schema.d.ts.map +1 -1
- package/dist/workflows/task.d.ts +1 -0
- package/dist/workflows/task.d.ts.map +1 -1
- package/dist/workflows/task.js +34 -0
- package/dist/workflows/types.d.ts +3 -1
- package/dist/workflows/types.d.ts.map +1 -1
- package/dist/workflows/uds.d.ts +1 -1
- package/dist/workflows/uds.d.ts.map +1 -1
- package/dist/workflows/uds.js +27 -82
- package/examples/client/main.ts +42 -0
- package/examples/client/package-lock.json +601 -0
- package/examples/client/package.json +16 -0
- package/examples/client/tsconfig.json +17 -0
- package/examples/task/main.ts +90 -0
- package/examples/task/package-lock.json +584 -0
- package/examples/task/package.json +16 -0
- package/examples/task/tsconfig.json +17 -0
- package/package.json +19 -27
- package/src/errors.test.ts +75 -0
- package/src/errors.ts +73 -0
- package/src/experimental/experimental.ts +56 -0
- package/src/experimental/index.ts +24 -0
- package/src/experimental/object/api.ts +91 -0
- package/src/experimental/object/client.test.ts +138 -0
- package/src/experimental/object/client.ts +317 -0
- package/src/experimental/object/index.ts +22 -0
- package/src/experimental/object/types.test.ts +87 -0
- package/src/experimental/object/types.ts +131 -0
- package/src/generated/schema.ts +12937 -0
- package/src/index.ts +7 -0
- package/src/render.ts +35 -0
- package/src/utils/create-api-client.ts +13 -0
- package/src/utils/get-base-url.test.ts +58 -0
- package/src/utils/get-base-url.ts +16 -0
- package/src/version.ts +37 -0
- package/src/workflows/client/client.test.ts +68 -0
- package/src/workflows/client/client.ts +142 -0
- package/src/workflows/client/create-client.ts +17 -0
- package/src/workflows/client/index.ts +3 -0
- package/src/workflows/client/sse.ts +95 -0
- package/src/workflows/client/types.ts +56 -0
- package/src/workflows/executor.ts +124 -0
- package/src/workflows/index.ts +7 -0
- package/src/workflows/registry.test.ts +76 -0
- package/src/workflows/registry.ts +88 -0
- package/src/workflows/runner.ts +38 -0
- package/src/workflows/schema.ts +348 -0
- package/src/workflows/task.ts +117 -0
- package/src/workflows/types.test.ts +52 -0
- package/src/workflows/types.ts +89 -0
- package/src/workflows/uds.ts +139 -0
- package/test-types.ts +14 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +23 -0
- package/vitest.config.ts +8 -0
- package/dist/workflows/client/errors.d.ts +0 -25
- package/dist/workflows/client/errors.d.ts.map +0 -1
- package/dist/workflows/client/errors.js +0 -56
- package/dist/workflows/client/schema.d.ts +0 -9322
- package/dist/workflows/client/schema.d.ts.map +0 -1
- package/dist/workflows/client/workflows.d.ts +0 -15
- package/dist/workflows/client/workflows.d.ts.map +0 -1
- package/dist/workflows/client/workflows.js +0 -63
- /package/dist/{workflows/client/schema.js → experimental/blob/types.js} +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { TaskRegistry } from "./registry.js";
|
|
2
|
+
|
|
3
|
+
describe("TaskRegistry", () => {
|
|
4
|
+
let registry: TaskRegistry;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
// Get a fresh registry for each test
|
|
8
|
+
registry = TaskRegistry.getInstance();
|
|
9
|
+
registry.clear();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("register", () => {
|
|
13
|
+
it("should register a task with timeout_seconds", () => {
|
|
14
|
+
const taskFn = () => 42;
|
|
15
|
+
|
|
16
|
+
registry.register(taskFn, {
|
|
17
|
+
name: "timeout_task",
|
|
18
|
+
timeoutSeconds: 120,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const task = registry.get("timeout_task");
|
|
22
|
+
expect(task).toBeDefined();
|
|
23
|
+
expect(task?.options?.timeout_seconds).toBe(120);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should register a task without timeout_seconds", () => {
|
|
27
|
+
const taskFn = () => 42;
|
|
28
|
+
|
|
29
|
+
registry.register(taskFn, {
|
|
30
|
+
name: "no_timeout_task",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const task = registry.get("no_timeout_task");
|
|
34
|
+
expect(task).toBeDefined();
|
|
35
|
+
expect(task?.options).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should register a task with both retry and timeout_seconds", () => {
|
|
39
|
+
const taskFn = () => 42;
|
|
40
|
+
|
|
41
|
+
registry.register(taskFn, {
|
|
42
|
+
name: "both_options_task",
|
|
43
|
+
timeoutSeconds: 300,
|
|
44
|
+
retry: {
|
|
45
|
+
maxRetries: 3,
|
|
46
|
+
waitDurationMs: 1000,
|
|
47
|
+
backoffScaling: 2.0,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const task = registry.get("both_options_task");
|
|
52
|
+
expect(task).toBeDefined();
|
|
53
|
+
expect(task?.options?.timeout_seconds).toBe(300);
|
|
54
|
+
expect(task?.options?.retry?.max_retries).toBe(3);
|
|
55
|
+
expect(task?.options?.retry?.wait_duration_ms).toBe(1000);
|
|
56
|
+
expect(task?.options?.retry?.factor).toBe(2.0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should register a task with only retry options", () => {
|
|
60
|
+
const taskFn = () => 42;
|
|
61
|
+
|
|
62
|
+
registry.register(taskFn, {
|
|
63
|
+
name: "retry_only_task",
|
|
64
|
+
retry: {
|
|
65
|
+
maxRetries: 2,
|
|
66
|
+
waitDurationMs: 500,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const task = registry.get("retry_only_task");
|
|
71
|
+
expect(task).toBeDefined();
|
|
72
|
+
expect(task?.options?.retry?.max_retries).toBe(2);
|
|
73
|
+
expect(task?.options?.timeout_seconds).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { RegisterTaskOptions, TaskFunction, TaskMetadata, TaskOptions } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Global task registry
|
|
5
|
+
*/
|
|
6
|
+
export class TaskRegistry {
|
|
7
|
+
private static instance: TaskRegistry;
|
|
8
|
+
private readonly tasks: Map<string, TaskMetadata> = new Map();
|
|
9
|
+
|
|
10
|
+
private constructor() {}
|
|
11
|
+
|
|
12
|
+
static getInstance(): TaskRegistry {
|
|
13
|
+
if (!TaskRegistry.instance) {
|
|
14
|
+
TaskRegistry.instance = new TaskRegistry();
|
|
15
|
+
}
|
|
16
|
+
return TaskRegistry.instance;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Register a task with optional name and options
|
|
21
|
+
*/
|
|
22
|
+
register(func: TaskFunction, options: RegisterTaskOptions): void {
|
|
23
|
+
const taskName = options.name;
|
|
24
|
+
if (!taskName) {
|
|
25
|
+
throw new Error("Task function must have a name or name must be provided");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let taskOptions: TaskOptions | undefined;
|
|
29
|
+
|
|
30
|
+
if (options.retry || options.timeoutSeconds || options.plan) {
|
|
31
|
+
taskOptions = {};
|
|
32
|
+
|
|
33
|
+
if (options.retry) {
|
|
34
|
+
taskOptions.retry = {
|
|
35
|
+
max_retries: options.retry.maxRetries,
|
|
36
|
+
wait_duration_ms: options.retry.waitDurationMs,
|
|
37
|
+
factor: options.retry.backoffScaling,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (options.timeoutSeconds) {
|
|
42
|
+
taskOptions.timeout_seconds = options.timeoutSeconds;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (options.plan) {
|
|
46
|
+
taskOptions.plan = options.plan;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.tasks.set(taskName, {
|
|
51
|
+
name: taskName,
|
|
52
|
+
func,
|
|
53
|
+
options: taskOptions,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get a task by name
|
|
59
|
+
*/
|
|
60
|
+
get(name: string): TaskMetadata | undefined {
|
|
61
|
+
return this.tasks.get(name);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get all registered task names
|
|
66
|
+
*/
|
|
67
|
+
getAllTaskNames(): string[] {
|
|
68
|
+
return Array.from(this.tasks.keys());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getAllTasks(): TaskMetadata[] {
|
|
72
|
+
return Array.from(this.tasks.values());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if a task is registered
|
|
77
|
+
*/
|
|
78
|
+
has(name: string): boolean {
|
|
79
|
+
return this.tasks.has(name);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Clear all registered tasks (useful for testing)
|
|
84
|
+
*/
|
|
85
|
+
clear(): void {
|
|
86
|
+
this.tasks.clear();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { RenderError } from "../errors.js";
|
|
2
|
+
import { TaskExecutor } from "./executor.js";
|
|
3
|
+
import { markServerStarted } from "./task.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Start the task server and listen for task execution requests
|
|
7
|
+
*/
|
|
8
|
+
export async function startTaskServer(): Promise<void> {
|
|
9
|
+
const mode = process.env.RENDER_SDK_MODE || "run";
|
|
10
|
+
const socketPath = process.env.RENDER_SDK_SOCKET_PATH;
|
|
11
|
+
|
|
12
|
+
if (!socketPath) {
|
|
13
|
+
throw new RenderError("RENDER_SDK_SOCKET_PATH environment variable is required");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const executor = new TaskExecutor(socketPath);
|
|
17
|
+
|
|
18
|
+
// Mark server as started to warn about late task registrations
|
|
19
|
+
markServerStarted();
|
|
20
|
+
|
|
21
|
+
if (mode === "register") {
|
|
22
|
+
// Register tasks mode
|
|
23
|
+
await executor.registerTasks();
|
|
24
|
+
} else if (mode === "run") {
|
|
25
|
+
// Run task mode
|
|
26
|
+
await executor.executeTask();
|
|
27
|
+
} else {
|
|
28
|
+
throw new RenderError(`Unknown SDK mode: ${mode}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run a specific task (for testing or direct execution)
|
|
34
|
+
*/
|
|
35
|
+
export async function run(socketPath: string): Promise<void> {
|
|
36
|
+
const executor = new TaskExecutor(socketPath);
|
|
37
|
+
await executor.executeTask();
|
|
38
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file was auto-generated by openapi-typescript.
|
|
3
|
+
* Do not make direct changes to the file.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface paths {
|
|
7
|
+
"/input": {
|
|
8
|
+
parameters: {
|
|
9
|
+
query?: never;
|
|
10
|
+
header?: never;
|
|
11
|
+
path?: never;
|
|
12
|
+
cookie?: never;
|
|
13
|
+
};
|
|
14
|
+
/** Get the task name and input for a task run */
|
|
15
|
+
get: {
|
|
16
|
+
parameters: {
|
|
17
|
+
query?: never;
|
|
18
|
+
header?: never;
|
|
19
|
+
path?: never;
|
|
20
|
+
cookie?: never;
|
|
21
|
+
};
|
|
22
|
+
requestBody?: never;
|
|
23
|
+
responses: {
|
|
24
|
+
/** @description Input received */
|
|
25
|
+
200: {
|
|
26
|
+
headers: {
|
|
27
|
+
[name: string]: unknown;
|
|
28
|
+
};
|
|
29
|
+
content: {
|
|
30
|
+
"application/json": components["schemas"]["InputResponse"];
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/** @description Internal server error */
|
|
34
|
+
500: {
|
|
35
|
+
headers: {
|
|
36
|
+
[name: string]: unknown;
|
|
37
|
+
};
|
|
38
|
+
content?: never;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
put?: never;
|
|
43
|
+
post?: never;
|
|
44
|
+
delete?: never;
|
|
45
|
+
options?: never;
|
|
46
|
+
head?: never;
|
|
47
|
+
patch?: never;
|
|
48
|
+
trace?: never;
|
|
49
|
+
};
|
|
50
|
+
"/callback": {
|
|
51
|
+
parameters: {
|
|
52
|
+
query?: never;
|
|
53
|
+
header?: never;
|
|
54
|
+
path?: never;
|
|
55
|
+
cookie?: never;
|
|
56
|
+
};
|
|
57
|
+
get?: never;
|
|
58
|
+
put?: never;
|
|
59
|
+
/** Receive a callback from a workflow */
|
|
60
|
+
post: {
|
|
61
|
+
parameters: {
|
|
62
|
+
query?: never;
|
|
63
|
+
header?: never;
|
|
64
|
+
path?: never;
|
|
65
|
+
cookie?: never;
|
|
66
|
+
};
|
|
67
|
+
requestBody?: {
|
|
68
|
+
content: {
|
|
69
|
+
"application/json": components["schemas"]["CallbackRequest"];
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
responses: {
|
|
73
|
+
/** @description Callback received */
|
|
74
|
+
200: {
|
|
75
|
+
headers: {
|
|
76
|
+
[name: string]: unknown;
|
|
77
|
+
};
|
|
78
|
+
content?: never;
|
|
79
|
+
};
|
|
80
|
+
/** @description Bad request */
|
|
81
|
+
400: {
|
|
82
|
+
headers: {
|
|
83
|
+
[name: string]: unknown;
|
|
84
|
+
};
|
|
85
|
+
content?: never;
|
|
86
|
+
};
|
|
87
|
+
/** @description Unauthorized */
|
|
88
|
+
401: {
|
|
89
|
+
headers: {
|
|
90
|
+
[name: string]: unknown;
|
|
91
|
+
};
|
|
92
|
+
content?: never;
|
|
93
|
+
};
|
|
94
|
+
/** @description Forbidden */
|
|
95
|
+
403: {
|
|
96
|
+
headers: {
|
|
97
|
+
[name: string]: unknown;
|
|
98
|
+
};
|
|
99
|
+
content?: never;
|
|
100
|
+
};
|
|
101
|
+
/** @description Internal server error */
|
|
102
|
+
500: {
|
|
103
|
+
headers: {
|
|
104
|
+
[name: string]: unknown;
|
|
105
|
+
};
|
|
106
|
+
content?: never;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
delete?: never;
|
|
111
|
+
options?: never;
|
|
112
|
+
head?: never;
|
|
113
|
+
patch?: never;
|
|
114
|
+
trace?: never;
|
|
115
|
+
};
|
|
116
|
+
"/register-tasks": {
|
|
117
|
+
parameters: {
|
|
118
|
+
query?: never;
|
|
119
|
+
header?: never;
|
|
120
|
+
path?: never;
|
|
121
|
+
cookie?: never;
|
|
122
|
+
};
|
|
123
|
+
get?: never;
|
|
124
|
+
put?: never;
|
|
125
|
+
/** Publish registered tasks */
|
|
126
|
+
post: {
|
|
127
|
+
parameters: {
|
|
128
|
+
query?: never;
|
|
129
|
+
header?: never;
|
|
130
|
+
path?: never;
|
|
131
|
+
cookie?: never;
|
|
132
|
+
};
|
|
133
|
+
requestBody?: {
|
|
134
|
+
content: {
|
|
135
|
+
"application/json": components["schemas"]["Tasks"];
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
responses: {
|
|
139
|
+
/** @description Callback received */
|
|
140
|
+
200: {
|
|
141
|
+
headers: {
|
|
142
|
+
[name: string]: unknown;
|
|
143
|
+
};
|
|
144
|
+
content?: never;
|
|
145
|
+
};
|
|
146
|
+
/** @description Bad request */
|
|
147
|
+
400: {
|
|
148
|
+
headers: {
|
|
149
|
+
[name: string]: unknown;
|
|
150
|
+
};
|
|
151
|
+
content?: never;
|
|
152
|
+
};
|
|
153
|
+
/** @description Internal server error */
|
|
154
|
+
500: {
|
|
155
|
+
headers: {
|
|
156
|
+
[name: string]: unknown;
|
|
157
|
+
};
|
|
158
|
+
content?: never;
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
delete?: never;
|
|
163
|
+
options?: never;
|
|
164
|
+
head?: never;
|
|
165
|
+
patch?: never;
|
|
166
|
+
trace?: never;
|
|
167
|
+
};
|
|
168
|
+
"/run-subtask": {
|
|
169
|
+
parameters: {
|
|
170
|
+
query?: never;
|
|
171
|
+
header?: never;
|
|
172
|
+
path?: never;
|
|
173
|
+
cookie?: never;
|
|
174
|
+
};
|
|
175
|
+
get?: never;
|
|
176
|
+
put?: never;
|
|
177
|
+
/** Trigger a subtask */
|
|
178
|
+
post: {
|
|
179
|
+
parameters: {
|
|
180
|
+
query?: never;
|
|
181
|
+
header?: never;
|
|
182
|
+
path?: never;
|
|
183
|
+
cookie?: never;
|
|
184
|
+
};
|
|
185
|
+
requestBody?: {
|
|
186
|
+
content: {
|
|
187
|
+
"application/json": components["schemas"]["RunSubtaskRequest"];
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
responses: {
|
|
191
|
+
/** @description OK */
|
|
192
|
+
200: {
|
|
193
|
+
headers: {
|
|
194
|
+
[name: string]: unknown;
|
|
195
|
+
};
|
|
196
|
+
content: {
|
|
197
|
+
"application/json": components["schemas"]["RunSubtaskResponse"];
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
/** @description Internal server error */
|
|
201
|
+
500: {
|
|
202
|
+
headers: {
|
|
203
|
+
[name: string]: unknown;
|
|
204
|
+
};
|
|
205
|
+
content?: never;
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
delete?: never;
|
|
210
|
+
options?: never;
|
|
211
|
+
head?: never;
|
|
212
|
+
patch?: never;
|
|
213
|
+
trace?: never;
|
|
214
|
+
};
|
|
215
|
+
"/get-subtask-result": {
|
|
216
|
+
parameters: {
|
|
217
|
+
query?: never;
|
|
218
|
+
header?: never;
|
|
219
|
+
path?: never;
|
|
220
|
+
cookie?: never;
|
|
221
|
+
};
|
|
222
|
+
get?: never;
|
|
223
|
+
put?: never;
|
|
224
|
+
/** Fetch the results for a subtask */
|
|
225
|
+
post: {
|
|
226
|
+
parameters: {
|
|
227
|
+
query?: never;
|
|
228
|
+
header?: never;
|
|
229
|
+
path?: never;
|
|
230
|
+
cookie?: never;
|
|
231
|
+
};
|
|
232
|
+
requestBody?: {
|
|
233
|
+
content: {
|
|
234
|
+
"application/json": components["schemas"]["SubtaskResultRequest"];
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
responses: {
|
|
238
|
+
/** @description OK */
|
|
239
|
+
200: {
|
|
240
|
+
headers: {
|
|
241
|
+
[name: string]: unknown;
|
|
242
|
+
};
|
|
243
|
+
content: {
|
|
244
|
+
"application/json": components["schemas"]["SubtaskResultResponse"];
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
/** @description Internal server error */
|
|
248
|
+
500: {
|
|
249
|
+
headers: {
|
|
250
|
+
[name: string]: unknown;
|
|
251
|
+
};
|
|
252
|
+
content?: never;
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
delete?: never;
|
|
257
|
+
options?: never;
|
|
258
|
+
head?: never;
|
|
259
|
+
patch?: never;
|
|
260
|
+
trace?: never;
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export type webhooks = Record<string, never>;
|
|
264
|
+
export interface components {
|
|
265
|
+
schemas: {
|
|
266
|
+
CallbackRequest: {
|
|
267
|
+
error?: components["schemas"]["TaskError"];
|
|
268
|
+
complete?: components["schemas"]["TaskComplete"];
|
|
269
|
+
};
|
|
270
|
+
TaskError: {
|
|
271
|
+
details: string;
|
|
272
|
+
stack_trace?: string;
|
|
273
|
+
};
|
|
274
|
+
TaskComplete: {
|
|
275
|
+
/** Format: byte */
|
|
276
|
+
output: string;
|
|
277
|
+
};
|
|
278
|
+
RunSubtaskRequest: {
|
|
279
|
+
task_name: string;
|
|
280
|
+
/** Format: byte */
|
|
281
|
+
input?: string;
|
|
282
|
+
};
|
|
283
|
+
RunSubtaskResponse: {
|
|
284
|
+
task_run_id: string;
|
|
285
|
+
};
|
|
286
|
+
SubtaskResultRequest: {
|
|
287
|
+
task_run_id: string;
|
|
288
|
+
};
|
|
289
|
+
SubtaskResultResponse: {
|
|
290
|
+
still_running: boolean;
|
|
291
|
+
error?: components["schemas"]["TaskError"];
|
|
292
|
+
complete?: components["schemas"]["TaskComplete"];
|
|
293
|
+
};
|
|
294
|
+
InputResponse: {
|
|
295
|
+
task_name: string;
|
|
296
|
+
/** Format: byte */
|
|
297
|
+
input: string;
|
|
298
|
+
};
|
|
299
|
+
Tasks: {
|
|
300
|
+
tasks: components["schemas"]["Task"][];
|
|
301
|
+
};
|
|
302
|
+
Task: {
|
|
303
|
+
name: string;
|
|
304
|
+
options?: components["schemas"]["TaskOptions"];
|
|
305
|
+
/** @description Parameter schema extracted from the task function signature */
|
|
306
|
+
parameters?: components["schemas"]["TaskParameter"][];
|
|
307
|
+
};
|
|
308
|
+
/** @description Information about a task parameter extracted from function signature */
|
|
309
|
+
TaskParameter: {
|
|
310
|
+
/** @description Parameter name */
|
|
311
|
+
name: string;
|
|
312
|
+
/** @description String representation of the parameter type hint */
|
|
313
|
+
type?: string;
|
|
314
|
+
/** @description Whether the parameter has a default value */
|
|
315
|
+
has_default: boolean;
|
|
316
|
+
/** @description JSON-encoded default value (if has_default is true) */
|
|
317
|
+
default_value?: string;
|
|
318
|
+
};
|
|
319
|
+
TaskOptions: {
|
|
320
|
+
retry?: components["schemas"]["RetryConfig"];
|
|
321
|
+
/**
|
|
322
|
+
* Format: int64
|
|
323
|
+
* @description Task execution timeout in seconds (30-86400)
|
|
324
|
+
*/
|
|
325
|
+
timeout_seconds?: number;
|
|
326
|
+
/** @description Resource plan for task execution */
|
|
327
|
+
plan?: string;
|
|
328
|
+
};
|
|
329
|
+
RetryConfig: {
|
|
330
|
+
/** @description Maximum number of retry attempts */
|
|
331
|
+
max_retries?: number;
|
|
332
|
+
/**
|
|
333
|
+
* Format: int64
|
|
334
|
+
* @description Initial wait duration between retries (in milliseconds)
|
|
335
|
+
*/
|
|
336
|
+
wait_duration_ms?: number;
|
|
337
|
+
/** @description Backoff factor for exponential retry */
|
|
338
|
+
factor?: number;
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
responses: never;
|
|
342
|
+
parameters: never;
|
|
343
|
+
requestBodies: never;
|
|
344
|
+
headers: never;
|
|
345
|
+
pathItems: never;
|
|
346
|
+
}
|
|
347
|
+
export type $defs = Record<string, never>;
|
|
348
|
+
export type operations = Record<string, never>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { TaskRegistry } from "./registry.js";
|
|
3
|
+
import type { RegisterTaskOptions, TaskContext, TaskFunction } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Storage for the current task execution context
|
|
7
|
+
*/
|
|
8
|
+
const taskContextStorage = new AsyncLocalStorage<TaskContext>();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Flag to track if auto-start has been scheduled
|
|
12
|
+
*/
|
|
13
|
+
let autoStartScheduled = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Flag to track if the server has started (set after startTaskServer completes)
|
|
17
|
+
*/
|
|
18
|
+
let serverStarted = false;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Mark the server as started (called by runner after successful start)
|
|
22
|
+
*/
|
|
23
|
+
export function markServerStarted(): void {
|
|
24
|
+
serverStarted = true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if auto-start should be enabled based on environment
|
|
29
|
+
*/
|
|
30
|
+
function shouldAutoStart(): boolean {
|
|
31
|
+
// Must be in a workflow environment (socket path set)
|
|
32
|
+
if (!process.env.RENDER_SDK_SOCKET_PATH) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check for opt-out via RENDER_SDK_AUTO_START=false
|
|
37
|
+
const autoStartEnv = process.env.RENDER_SDK_AUTO_START;
|
|
38
|
+
if (autoStartEnv !== undefined && autoStartEnv.toLowerCase() === "false") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the current task context (only available during task execution)
|
|
47
|
+
*/
|
|
48
|
+
export function getCurrentContext(): TaskContext | undefined {
|
|
49
|
+
return taskContextStorage.getStore();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Set the current task context (used internally by executor)
|
|
54
|
+
*/
|
|
55
|
+
export function setCurrentContext<T>(context: TaskContext, fn: () => Promise<T>): Promise<T> {
|
|
56
|
+
return taskContextStorage.run(context, fn);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Register a task with the workflow system.
|
|
61
|
+
*
|
|
62
|
+
* When running in a workflow environment (RENDER_SDK_SOCKET_PATH is set),
|
|
63
|
+
* the task server will automatically start after all synchronously-defined
|
|
64
|
+
* tasks are registered. This can be disabled by setting RENDER_SDK_AUTO_START=false.
|
|
65
|
+
*
|
|
66
|
+
* @param func Task function
|
|
67
|
+
* @param options Optional task options
|
|
68
|
+
* @returns The registered function with the same signature
|
|
69
|
+
*/
|
|
70
|
+
export function task<TArgs extends any[], TResult>(
|
|
71
|
+
options: RegisterTaskOptions,
|
|
72
|
+
func: TaskFunction<TArgs, TResult>,
|
|
73
|
+
): TaskFunction<TArgs, TResult> {
|
|
74
|
+
// Warn if task is registered after server has started. This is possible if
|
|
75
|
+
// the task is loaded via dynamic import.
|
|
76
|
+
if (serverStarted) {
|
|
77
|
+
console.warn(
|
|
78
|
+
`Warning: Task '${options.name}' was registered after the task server started. ` +
|
|
79
|
+
`This task will not be available for execution. ` +
|
|
80
|
+
`Ensure all tasks are defined synchronously at module level.`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const registry = TaskRegistry.getInstance();
|
|
85
|
+
registry.register(func, options);
|
|
86
|
+
|
|
87
|
+
// Schedule auto-start on first task registration when in workflow environment
|
|
88
|
+
if (!autoStartScheduled && shouldAutoStart()) {
|
|
89
|
+
autoStartScheduled = true;
|
|
90
|
+
setImmediate(async () => {
|
|
91
|
+
const { startTaskServer } = await import("./runner.js");
|
|
92
|
+
try {
|
|
93
|
+
await startTaskServer();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error("Failed to start task server:", error);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Return a wrapper function that executes the task as a subtask
|
|
102
|
+
return ((...args: TArgs): TResult | Promise<TResult> => {
|
|
103
|
+
const context = getCurrentContext();
|
|
104
|
+
|
|
105
|
+
if (!context) {
|
|
106
|
+
// If we're not in a task execution context, just run the function directly
|
|
107
|
+
// This allows for testing and direct invocation
|
|
108
|
+
return func(...args);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Execute as a subtask through the context
|
|
112
|
+
const result = context.executeTask(func, options.name, ...args);
|
|
113
|
+
|
|
114
|
+
// Return the result wrapped in a promise that awaits the subtask
|
|
115
|
+
return result.get();
|
|
116
|
+
}) as TaskFunction<TArgs, TResult>;
|
|
117
|
+
}
|