@helmr/sdk 0.1.1-rc.4 → 0.1.1-rc.5
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/README.md +20 -12
- package/dist/compile.js +33 -2
- package/dist/idempotency.d.ts +30 -0
- package/dist/index.d.ts +25 -7
- package/dist/index.js +684 -170
- package/dist/internal.d.ts +131 -32
- package/dist/internal.js +264 -25
- package/dist/runtime/client.d.ts +114 -56
- package/dist/runtime/run.d.ts +32 -48
- package/dist/schedules.d.ts +30 -0
- package/dist/schema/payload.d.ts +40 -0
- package/dist/schema/task.d.ts +15 -0
- package/dist/task.d.ts +6 -3
- package/dist/trigger.d.ts +10 -20
- package/package.json +2 -2
package/dist/internal.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { type PayloadSchemaInput, type PayloadSchemaOutput, type PayloadSchema } from "./schema/payload.js";
|
|
2
|
+
import type { IdempotencyKeyInput } from "./idempotency.js";
|
|
3
|
+
import type { RunHandle } from "./runtime/run.js";
|
|
4
|
+
export { parsePayloadWithSchema } from "./schema/payload.js";
|
|
1
5
|
export interface CacheMount {
|
|
2
6
|
readonly id: string;
|
|
3
7
|
}
|
|
@@ -61,13 +65,38 @@ export interface WorkspaceCapabilities {
|
|
|
61
65
|
readonly subpath?: string;
|
|
62
66
|
}): WorkspaceSpec;
|
|
63
67
|
}
|
|
64
|
-
export
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
export type WaitJson = null | boolean | number | string | readonly WaitJson[] | {
|
|
69
|
+
readonly [key: string]: WaitJson;
|
|
70
|
+
};
|
|
71
|
+
export interface WaitOptions {
|
|
72
|
+
readonly timeout?: number;
|
|
73
|
+
readonly policy?: string;
|
|
74
|
+
readonly displayText?: string;
|
|
67
75
|
}
|
|
68
|
-
export
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
export interface WaitManualOptions<TSchema extends PayloadSchema<any, any> = PayloadSchema<any, any>> extends WaitOptions {
|
|
77
|
+
readonly schema?: TSchema;
|
|
78
|
+
}
|
|
79
|
+
export interface WaitResolution<TPayload = unknown> {
|
|
80
|
+
readonly kind: string;
|
|
81
|
+
readonly payload: TPayload;
|
|
82
|
+
readonly at: Date;
|
|
83
|
+
readonly principal?: string;
|
|
84
|
+
}
|
|
85
|
+
export type WaitForInput = string | number | {
|
|
86
|
+
readonly seconds?: number;
|
|
87
|
+
readonly milliseconds?: number;
|
|
88
|
+
readonly duration?: string;
|
|
89
|
+
readonly [key: string]: WaitJson | undefined;
|
|
90
|
+
};
|
|
91
|
+
export type WaitUntilInput = string | Date | {
|
|
92
|
+
readonly date?: string | Date;
|
|
93
|
+
readonly [key: string]: WaitJson | Date | undefined;
|
|
94
|
+
};
|
|
95
|
+
export interface WaitCapabilities {
|
|
96
|
+
for(input: WaitForInput, opts?: Omit<WaitOptions, "timeout" | "policy">): Promise<void>;
|
|
97
|
+
until(input: WaitUntilInput, opts?: Omit<WaitOptions, "timeout" | "policy">): Promise<void>;
|
|
98
|
+
manual<TSchema extends PayloadSchema<any, any>>(opts: WaitManualOptions<TSchema>): Promise<PayloadSchemaOutput<TSchema>>;
|
|
99
|
+
manual<TPayload = unknown>(opts?: WaitManualOptions): Promise<TPayload>;
|
|
71
100
|
}
|
|
72
101
|
export declare class ConcurrentWaitError extends Error {
|
|
73
102
|
constructor(message: string);
|
|
@@ -87,25 +116,7 @@ export type Placement = {
|
|
|
87
116
|
export type SecretDecls = Record<string, Placement>;
|
|
88
117
|
export declare function validateSecretName(name: string, label?: string): void;
|
|
89
118
|
export interface TaskContext {
|
|
90
|
-
readonly wait:
|
|
91
|
-
approval(message: string, opts?: {
|
|
92
|
-
readonly timeout?: number;
|
|
93
|
-
readonly policy?: string;
|
|
94
|
-
}): Promise<{
|
|
95
|
-
readonly approved: boolean;
|
|
96
|
-
readonly approvedBy: string;
|
|
97
|
-
readonly at: Date;
|
|
98
|
-
}>;
|
|
99
|
-
message(prompt?: string, opts?: {
|
|
100
|
-
readonly timeout?: number;
|
|
101
|
-
readonly policy?: string;
|
|
102
|
-
}): Promise<{
|
|
103
|
-
readonly text: string;
|
|
104
|
-
readonly sentBy: string;
|
|
105
|
-
readonly at: Date;
|
|
106
|
-
readonly attachments: readonly unknown[];
|
|
107
|
-
}>;
|
|
108
|
-
};
|
|
119
|
+
readonly wait: WaitCapabilities;
|
|
109
120
|
emit(event: EmitEvent): void;
|
|
110
121
|
readonly log: {
|
|
111
122
|
info(...args: unknown[]): void;
|
|
@@ -146,17 +157,98 @@ export interface EmitEvent {
|
|
|
146
157
|
readonly content: readonly EmitContent[];
|
|
147
158
|
}
|
|
148
159
|
export type MaybePromise<T> = T | Promise<T>;
|
|
149
|
-
|
|
160
|
+
declare const noPayloadBrand: unique symbol;
|
|
161
|
+
export interface NoPayload {
|
|
162
|
+
readonly [noPayloadBrand]: "NoPayload";
|
|
163
|
+
}
|
|
164
|
+
export interface TaskConfigBase<TSecrets extends SecretDecls = Record<never, never>> {
|
|
150
165
|
readonly id: string;
|
|
151
166
|
readonly sandbox: SandboxBuilder;
|
|
152
167
|
readonly maxDuration?: number;
|
|
168
|
+
readonly queue?: TaskQueueConfig;
|
|
169
|
+
readonly ttl?: string;
|
|
153
170
|
readonly secrets?: TSecrets;
|
|
154
|
-
readonly run: (payload: TPayload, ctx: TaskContext) => MaybePromise<TOutput>;
|
|
155
171
|
}
|
|
156
|
-
export
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
export interface TaskQueueConfig {
|
|
173
|
+
readonly name?: string;
|
|
174
|
+
readonly concurrencyLimit?: number | null;
|
|
175
|
+
}
|
|
176
|
+
export interface InternalTaskScheduleConfig {
|
|
177
|
+
readonly cron: string;
|
|
178
|
+
readonly timezone?: string;
|
|
179
|
+
readonly secretBindings?: Record<string, string>;
|
|
180
|
+
readonly workspace: WorkspaceSpec;
|
|
181
|
+
}
|
|
182
|
+
export type TaskRunOptions<TSecrets extends SecretDecls> = {
|
|
183
|
+
readonly workspace: WorkspaceSpec;
|
|
184
|
+
readonly deploymentId?: string;
|
|
185
|
+
readonly version?: string;
|
|
186
|
+
readonly queue?: string;
|
|
187
|
+
readonly concurrencyKey?: string;
|
|
188
|
+
readonly priority?: number;
|
|
189
|
+
readonly ttl?: string;
|
|
190
|
+
readonly idempotencyKey?: IdempotencyKeyInput;
|
|
191
|
+
readonly idempotencyKeyTTL?: string;
|
|
192
|
+
} & ([keyof TSecrets] extends [never] ? {
|
|
193
|
+
readonly secrets?: Record<never, never>;
|
|
194
|
+
} : {
|
|
195
|
+
readonly secrets: {
|
|
196
|
+
readonly [K in keyof TSecrets]: string;
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
export type TaskDirectTrigger<TPayloadInput, TOutput, TSecrets extends SecretDecls> = [TPayloadInput] extends [NoPayload] ? (opts: TaskRunOptions<TSecrets>) => Promise<RunHandle<Awaited<TOutput>>> : (payload: TPayloadInput, opts: TaskRunOptions<TSecrets>) => Promise<RunHandle<Awaited<TOutput>>>;
|
|
200
|
+
export type TaskConfigWithPayload<TPayloadSchema extends PayloadSchema<any, any>, TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>> = TaskConfigBase<TSecrets> & {
|
|
201
|
+
readonly payload: TPayloadSchema;
|
|
202
|
+
readonly run: (payload: PayloadSchemaOutput<TPayloadSchema>, ctx: TaskContext) => MaybePromise<TOutput>;
|
|
203
|
+
};
|
|
204
|
+
export type TaskConfigWithoutPayload<TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>> = TaskConfigBase<TSecrets> & {
|
|
205
|
+
readonly payload?: undefined;
|
|
206
|
+
readonly run: (ctx: TaskContext) => MaybePromise<TOutput>;
|
|
207
|
+
};
|
|
208
|
+
export type TaskConfig<TPayload = NoPayload, TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>, TPayloadSchema extends PayloadSchema<any, any> | undefined = undefined> = TPayloadSchema extends PayloadSchema<any, any> ? TaskConfigWithPayload<TPayloadSchema, TOutput, TSecrets> : TaskConfigWithoutPayload<TOutput, TSecrets>;
|
|
209
|
+
export type Task<TPayload = NoPayload, TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>, TPayloadInput = TPayload> = TaskConfigBase<TSecrets> & {
|
|
210
|
+
readonly "~types"?: {
|
|
211
|
+
readonly payloadInput: TPayloadInput;
|
|
212
|
+
readonly payload: TPayload;
|
|
213
|
+
readonly output: TOutput;
|
|
214
|
+
readonly secrets: TSecrets;
|
|
215
|
+
};
|
|
216
|
+
readonly payload?: PayloadSchema<TPayloadInput, TPayload>;
|
|
217
|
+
readonly run: [TPayloadInput] extends [NoPayload] ? (ctx: TaskContext) => MaybePromise<TOutput> : (payload: TPayload, ctx: TaskContext) => MaybePromise<TOutput>;
|
|
218
|
+
readonly trigger: TaskDirectTrigger<TPayloadInput, TOutput, TSecrets>;
|
|
219
|
+
};
|
|
220
|
+
export type AnyTask = TaskConfigBase<SecretDecls> & {
|
|
221
|
+
readonly schedule?: InternalTaskScheduleConfig;
|
|
222
|
+
readonly "~types"?: {
|
|
223
|
+
readonly payloadInput: any;
|
|
224
|
+
readonly payload: any;
|
|
225
|
+
readonly output: any;
|
|
226
|
+
readonly secrets: SecretDecls;
|
|
227
|
+
};
|
|
228
|
+
readonly payload?: PayloadSchema<any, any>;
|
|
229
|
+
readonly run: (...args: any[]) => MaybePromise<any>;
|
|
230
|
+
readonly trigger: (...args: any[]) => Promise<RunHandle<any>>;
|
|
231
|
+
};
|
|
232
|
+
export type TaskPayload<TTask> = TTask extends {
|
|
233
|
+
readonly "~types"?: {
|
|
234
|
+
readonly payload: infer TPayload;
|
|
235
|
+
};
|
|
236
|
+
} ? [TPayload] extends [NoPayload] ? never : TPayload : never;
|
|
237
|
+
export type TaskTriggerPayload<TTask> = TTask extends {
|
|
238
|
+
readonly "~types"?: {
|
|
239
|
+
readonly payloadInput: infer TPayloadInput;
|
|
240
|
+
};
|
|
241
|
+
} ? TPayloadInput : never;
|
|
242
|
+
export type TaskOutput<TTask> = TTask extends {
|
|
243
|
+
readonly "~types"?: {
|
|
244
|
+
readonly output: infer TOutput;
|
|
245
|
+
};
|
|
246
|
+
} ? Awaited<TOutput> : never;
|
|
247
|
+
export type TaskSecrets<TTask> = TTask extends {
|
|
248
|
+
readonly "~types"?: {
|
|
249
|
+
readonly secrets: infer TSecrets;
|
|
250
|
+
};
|
|
251
|
+
} ? TSecrets : never;
|
|
160
252
|
export declare const taskBrand: unique symbol;
|
|
161
253
|
export declare const taskOriginBrand: unique symbol;
|
|
162
254
|
export declare const configBrand: unique symbol;
|
|
@@ -217,7 +309,14 @@ export interface SandboxResources {
|
|
|
217
309
|
readonly cpu?: number;
|
|
218
310
|
readonly memory?: string;
|
|
219
311
|
}
|
|
220
|
-
export
|
|
312
|
+
export type MarkedTask<TPayload, TOutput, TSecrets extends SecretDecls, TPayloadSchema extends PayloadSchema<any, any> | undefined> = TPayloadSchema extends PayloadSchema<any, any> ? Task<PayloadSchemaOutput<TPayloadSchema>, Awaited<TOutput>, TSecrets, PayloadSchemaInput<TPayloadSchema>> : Task<NoPayload, Awaited<TOutput>, TSecrets, NoPayload>;
|
|
313
|
+
export declare function markTask<TPayload, TOutput, TSecrets extends SecretDecls, TPayloadSchema extends PayloadSchema<any, any> | undefined>(config: TaskConfig<TPayload, TOutput, TSecrets, TPayloadSchema>): MarkedTask<TPayload, TOutput, TSecrets, TPayloadSchema>;
|
|
314
|
+
export declare function markScheduledTask<TPayload, TOutput, TSecrets extends SecretDecls, TPayloadSchema extends PayloadSchema<any, any> | undefined>(config: TaskConfig<TPayload, TOutput, TSecrets, TPayloadSchema>, schedule: InternalTaskScheduleConfig | undefined): MarkedTask<TPayload, TOutput, TSecrets, TPayloadSchema>;
|
|
315
|
+
export declare function validateTaskSchedule(taskId: string, value: InternalTaskScheduleConfig | undefined): void;
|
|
316
|
+
export declare function validateTaskQueue(taskId: string, value: TaskQueueConfig | undefined): void;
|
|
317
|
+
export declare function defaultTaskQueueName(taskId: string): string;
|
|
318
|
+
export declare function validateOptionalTTL(value: unknown, label?: string): void;
|
|
319
|
+
export declare function parseTaskPayload<TTask extends AnyTask>(task: TTask, payload: unknown): Promise<TaskPayload<TTask>>;
|
|
221
320
|
export declare function isTaskDefinition(value: unknown): value is AnyTask & {
|
|
222
321
|
readonly [taskBrand]: true;
|
|
223
322
|
};
|
package/dist/internal.js
CHANGED
|
@@ -1,9 +1,92 @@
|
|
|
1
|
+
// sdk/typescript/src/schema/payload.ts
|
|
2
|
+
var payloadSchemaValidationErrorBrand = Symbol.for("helmr.sdk.PayloadSchemaValidationError");
|
|
3
|
+
|
|
4
|
+
class PayloadSchemaValidationError extends Error {
|
|
5
|
+
issues;
|
|
6
|
+
constructor(label, issues) {
|
|
7
|
+
super(formatPayloadSchemaValidationMessage(label, issues));
|
|
8
|
+
this.name = "PayloadSchemaValidationError";
|
|
9
|
+
this.issues = issues;
|
|
10
|
+
Object.defineProperty(this, payloadSchemaValidationErrorBrand, { value: true });
|
|
11
|
+
}
|
|
12
|
+
static [Symbol.hasInstance](value) {
|
|
13
|
+
return this === PayloadSchemaValidationError && typeof value === "object" && value !== null && payloadSchemaValidationErrorBrand in value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function assertPayloadSchema(value, label = "payload") {
|
|
17
|
+
if (value === undefined) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
assertStandardSchema(value, label);
|
|
21
|
+
}
|
|
22
|
+
function assertStandardSchema(value, label = "schema") {
|
|
23
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") {
|
|
24
|
+
throw new Error(`${label} must implement the Standard Schema v1 interface`);
|
|
25
|
+
}
|
|
26
|
+
const standard = value["~standard"];
|
|
27
|
+
if (standard === null || typeof standard !== "object") {
|
|
28
|
+
throw new Error(`${label} must implement the Standard Schema v1 interface`);
|
|
29
|
+
}
|
|
30
|
+
const record = standard;
|
|
31
|
+
if (record["version"] !== 1 || typeof record["validate"] !== "function") {
|
|
32
|
+
throw new Error(`${label} must implement the Standard Schema v1 interface`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function parsePayloadWithSchema(schema, payload, label) {
|
|
36
|
+
assertStandardSchema(schema, label);
|
|
37
|
+
const result = await schema["~standard"].validate(payload);
|
|
38
|
+
if ("issues" in result && result.issues !== undefined) {
|
|
39
|
+
throw new PayloadSchemaValidationError(label, result.issues);
|
|
40
|
+
}
|
|
41
|
+
return result.value;
|
|
42
|
+
}
|
|
43
|
+
function formatPayloadSchemaValidationMessage(label, issues) {
|
|
44
|
+
if (issues.length === 0) {
|
|
45
|
+
return `${label} failed validation`;
|
|
46
|
+
}
|
|
47
|
+
const formattedIssues = issues.slice(0, 5).map(formatPayloadSchemaIssue);
|
|
48
|
+
const suffix = issues.length > formattedIssues.length ? `; and ${issues.length - formattedIssues.length} more` : "";
|
|
49
|
+
return `${label} failed validation: ${formattedIssues.join("; ")}${suffix}`;
|
|
50
|
+
}
|
|
51
|
+
function formatPayloadSchemaIssue(issue) {
|
|
52
|
+
const path = formatPayloadSchemaIssuePath(issue.path);
|
|
53
|
+
return path === "" ? issue.message : `${path}: ${issue.message}`;
|
|
54
|
+
}
|
|
55
|
+
function formatPayloadSchemaIssuePath(path) {
|
|
56
|
+
if (path === undefined || path.length === 0) {
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
let formatted = "payload";
|
|
60
|
+
for (const segment of path) {
|
|
61
|
+
const key = typeof segment === "object" && segment !== null && "key" in segment ? segment.key : segment;
|
|
62
|
+
if (typeof key === "string" && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
|
|
63
|
+
formatted += `.${key}`;
|
|
64
|
+
} else if (typeof key === "string" && isArrayIndexKey(key)) {
|
|
65
|
+
formatted += `[${key}]`;
|
|
66
|
+
} else if (typeof key === "number") {
|
|
67
|
+
formatted += `[${key}]`;
|
|
68
|
+
} else {
|
|
69
|
+
formatted += `[${JSON.stringify(String(key))}]`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return formatted;
|
|
73
|
+
}
|
|
74
|
+
function isArrayIndexKey(value) {
|
|
75
|
+
if (!/^(0|[1-9]\d*)$/.test(value)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const parsed = Number(value);
|
|
79
|
+
return Number.isSafeInteger(parsed);
|
|
80
|
+
}
|
|
81
|
+
|
|
1
82
|
// sdk/typescript/src/schema/task.ts
|
|
2
83
|
var TASK_ID_PATTERN = "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$";
|
|
3
84
|
var TASK_ID_MAX_LENGTH = 128;
|
|
4
85
|
var DEFAULT_MAX_DURATION_SECONDS = 900;
|
|
5
86
|
var MIN_MAX_DURATION_SECONDS = 5;
|
|
6
87
|
var MAX_DURATION_SECONDS = 86400;
|
|
88
|
+
var QUEUE_NAME_PATTERN = "^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$";
|
|
89
|
+
var QUEUE_NAME_MAX_LENGTH = 256;
|
|
7
90
|
|
|
8
91
|
class TaskIdError extends Error {
|
|
9
92
|
name = "TaskIdError";
|
|
@@ -57,36 +140,60 @@ function readOptionalMaxDurationSeconds(value, label = "task maxDuration") {
|
|
|
57
140
|
function validateOptionalMaxDurationSeconds(value, label = "task maxDuration") {
|
|
58
141
|
readOptionalMaxDurationSeconds(value, label);
|
|
59
142
|
}
|
|
60
|
-
function isAsciiAlnum(code) {
|
|
61
|
-
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
|
|
62
|
-
}
|
|
63
143
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
144
|
+
class TaskQueueNameError extends Error {
|
|
145
|
+
name = "TaskQueueNameError";
|
|
146
|
+
value;
|
|
147
|
+
constructor(value) {
|
|
148
|
+
super(`queue name must match ${QUEUE_NAME_PATTERN}: ${JSON.stringify(value)}`);
|
|
149
|
+
this.value = value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
68
152
|
|
|
69
|
-
class
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
153
|
+
class TaskQueueConcurrencyLimitError extends Error {
|
|
154
|
+
name = "TaskQueueConcurrencyLimitError";
|
|
155
|
+
value;
|
|
156
|
+
constructor(value) {
|
|
157
|
+
super("queue concurrencyLimit must be a positive integer");
|
|
158
|
+
this.value = value;
|
|
74
159
|
}
|
|
75
|
-
|
|
76
|
-
|
|
160
|
+
}
|
|
161
|
+
function validateQueueName(value) {
|
|
162
|
+
if (!isValidQueueName(value)) {
|
|
163
|
+
throw new TaskQueueNameError(value);
|
|
77
164
|
}
|
|
78
165
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
super(message);
|
|
83
|
-
this.name = "MessageTimeoutError";
|
|
84
|
-
Object.defineProperty(this, messageTimeoutErrorBrand, { value: true });
|
|
166
|
+
function isValidQueueName(value) {
|
|
167
|
+
if (value.length === 0 || value.length > QUEUE_NAME_MAX_LENGTH) {
|
|
168
|
+
return false;
|
|
85
169
|
}
|
|
86
|
-
|
|
87
|
-
|
|
170
|
+
const first = value.charCodeAt(0);
|
|
171
|
+
if (!isAsciiAlnum(first)) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
for (let index = 1;index < value.length; index += 1) {
|
|
175
|
+
const code = value.charCodeAt(index);
|
|
176
|
+
if (!(isAsciiAlnum(code) || code === 46 || code === 95 || code === 45 || code === 47)) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
88
179
|
}
|
|
180
|
+
return true;
|
|
89
181
|
}
|
|
182
|
+
function validateOptionalQueueConcurrencyLimit(value) {
|
|
183
|
+
if (value === undefined || value === null) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value > 0) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
throw new TaskQueueConcurrencyLimitError(value);
|
|
190
|
+
}
|
|
191
|
+
function isAsciiAlnum(code) {
|
|
192
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// sdk/typescript/src/internal.ts
|
|
196
|
+
var concurrentWaitErrorBrand = Symbol.for("helmr.sdk.ConcurrentWaitError");
|
|
90
197
|
|
|
91
198
|
class ConcurrentWaitError extends Error {
|
|
92
199
|
constructor(message) {
|
|
@@ -162,12 +269,139 @@ var sandboxBuilderBrand = Symbol.for("helmr.sdk.SandboxBuilder");
|
|
|
162
269
|
var sourceFileRefBrand = Symbol.for("helmr.sdk.SourceFileRef");
|
|
163
270
|
var sourceDirRefBrand = Symbol.for("helmr.sdk.SourceDirRef");
|
|
164
271
|
function markTask(config) {
|
|
272
|
+
if ("schedule" in config) {
|
|
273
|
+
throw new Error(`task ${JSON.stringify(config.id)} must use schedules.task(...) for declarative schedules`);
|
|
274
|
+
}
|
|
275
|
+
return markTaskInternal(config, undefined);
|
|
276
|
+
}
|
|
277
|
+
function markScheduledTask(config, schedule) {
|
|
278
|
+
return markTaskInternal(config, schedule);
|
|
279
|
+
}
|
|
280
|
+
function markTaskInternal(config, schedule) {
|
|
165
281
|
validateTaskId(config.id);
|
|
166
282
|
validateOptionalMaxDurationSeconds(config.maxDuration);
|
|
283
|
+
validateTaskQueue(config.id, config.queue);
|
|
284
|
+
validateTaskSchedule(config.id, schedule);
|
|
285
|
+
validateOptionalTTL(config.ttl, `task ${JSON.stringify(config.id)} ttl`);
|
|
286
|
+
assertPayloadSchema(config.payload, `task ${JSON.stringify(config.id)} payload`);
|
|
287
|
+
if (schedule !== undefined) {
|
|
288
|
+
Object.defineProperty(config, "schedule", {
|
|
289
|
+
value: Object.freeze({ ...schedule }),
|
|
290
|
+
enumerable: true
|
|
291
|
+
});
|
|
292
|
+
}
|
|
167
293
|
Object.defineProperty(config, taskBrand, { value: true });
|
|
168
294
|
Object.defineProperty(config, taskOriginBrand, { value: captureTaskOrigin() });
|
|
169
295
|
return config;
|
|
170
296
|
}
|
|
297
|
+
function validateTaskSchedule(taskId, value) {
|
|
298
|
+
if (value === undefined) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (value.cron.trim() === "") {
|
|
302
|
+
throw new Error(`task ${JSON.stringify(taskId)} schedule cron is required`);
|
|
303
|
+
}
|
|
304
|
+
if (value.timezone !== undefined && value.timezone.trim() === "") {
|
|
305
|
+
throw new Error(`task ${JSON.stringify(taskId)} schedule timezone must not be empty`);
|
|
306
|
+
}
|
|
307
|
+
if (value.workspace.kind !== "github") {
|
|
308
|
+
throw new Error(`task ${JSON.stringify(taskId)} schedule workspace must be workspace.github(...)`);
|
|
309
|
+
}
|
|
310
|
+
if (value.secretBindings !== undefined) {
|
|
311
|
+
if (value.secretBindings === null || typeof value.secretBindings !== "object" || Array.isArray(value.secretBindings)) {
|
|
312
|
+
throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings must be an object`);
|
|
313
|
+
}
|
|
314
|
+
for (const [name, binding] of Object.entries(value.secretBindings)) {
|
|
315
|
+
validateSecretName(name, `task ${JSON.stringify(taskId)} schedule secretBindings.${name}`);
|
|
316
|
+
if (typeof binding !== "string" || binding.trim() === "") {
|
|
317
|
+
throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings.${name} must be a non-empty string`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function validateTaskQueue(taskId, value) {
|
|
323
|
+
if (value === undefined) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (value.name !== undefined) {
|
|
327
|
+
validateQueueName(value.name);
|
|
328
|
+
}
|
|
329
|
+
validateOptionalQueueConcurrencyLimit(value.concurrencyLimit);
|
|
330
|
+
if (value.name === undefined && value.concurrencyLimit === undefined) {
|
|
331
|
+
throw new Error(`task ${JSON.stringify(taskId)} queue must include name or concurrencyLimit`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function defaultTaskQueueName(taskId) {
|
|
335
|
+
return `task/${taskId}`;
|
|
336
|
+
}
|
|
337
|
+
function validateOptionalTTL(value, label = "ttl") {
|
|
338
|
+
if (value === undefined) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (typeof value === "string" && isPositiveDurationString(value)) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
throw new Error(`${label} must be a positive duration string`);
|
|
345
|
+
}
|
|
346
|
+
function isPositiveDurationString(value) {
|
|
347
|
+
const raw = value.trim();
|
|
348
|
+
if (raw === "") {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
if (/^[1-9][0-9]*d$/.test(raw)) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
const sign = raw.startsWith("+") || raw.startsWith("-") ? raw.slice(0, 1) : "";
|
|
355
|
+
if (sign === "-") {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
const body = sign === "" ? raw : raw.slice(1);
|
|
359
|
+
const tokenPattern = /([0-9]+(?:\.[0-9]*)?|\.[0-9]+)(ns|us|µs|μs|ms|s|m|h)/gy;
|
|
360
|
+
let totalNanoseconds = 0;
|
|
361
|
+
let offset = 0;
|
|
362
|
+
for (;; ) {
|
|
363
|
+
tokenPattern.lastIndex = offset;
|
|
364
|
+
const match = tokenPattern.exec(body);
|
|
365
|
+
if (match === null) {
|
|
366
|
+
return offset === body.length && totalNanoseconds >= 1;
|
|
367
|
+
}
|
|
368
|
+
if (match.index !== offset) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
const amount = Number(match[1]);
|
|
372
|
+
if (!Number.isFinite(amount) || amount < 0) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
totalNanoseconds += amount * durationUnitNanoseconds(match[2] ?? "");
|
|
376
|
+
offset = tokenPattern.lastIndex;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function durationUnitNanoseconds(unit) {
|
|
380
|
+
switch (unit) {
|
|
381
|
+
case "ns":
|
|
382
|
+
return 1;
|
|
383
|
+
case "us":
|
|
384
|
+
case "µs":
|
|
385
|
+
case "μs":
|
|
386
|
+
return 1000;
|
|
387
|
+
case "ms":
|
|
388
|
+
return 1e6;
|
|
389
|
+
case "s":
|
|
390
|
+
return 1e9;
|
|
391
|
+
case "m":
|
|
392
|
+
return 60000000000;
|
|
393
|
+
case "h":
|
|
394
|
+
return 3600000000000;
|
|
395
|
+
default:
|
|
396
|
+
return 0;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async function parseTaskPayload(task, payload) {
|
|
400
|
+
if (task.payload === undefined) {
|
|
401
|
+
throw new Error(`task ${JSON.stringify(task.id)} does not accept payload`);
|
|
402
|
+
}
|
|
403
|
+
return await parsePayloadWithSchema(task.payload, payload, `task ${JSON.stringify(task.id)} payload`);
|
|
404
|
+
}
|
|
171
405
|
function isTaskDefinition(value) {
|
|
172
406
|
return hasBrand(value, taskBrand);
|
|
173
407
|
}
|
|
@@ -322,11 +556,17 @@ function hasBrand(value, brand) {
|
|
|
322
556
|
return value !== null && typeof value === "object" && value[brand] === true;
|
|
323
557
|
}
|
|
324
558
|
export {
|
|
559
|
+
validateTaskSchedule,
|
|
560
|
+
validateTaskQueue,
|
|
325
561
|
validateSecretName,
|
|
562
|
+
validateOptionalTTL,
|
|
326
563
|
taskOriginFile,
|
|
327
564
|
taskOriginBrand,
|
|
328
565
|
taskBrand,
|
|
566
|
+
parseTaskPayload,
|
|
567
|
+
parsePayloadWithSchema,
|
|
329
568
|
markTask,
|
|
569
|
+
markScheduledTask,
|
|
330
570
|
markConfig,
|
|
331
571
|
isTaskDefinition,
|
|
332
572
|
isSourceFileRef,
|
|
@@ -334,12 +574,11 @@ export {
|
|
|
334
574
|
isSandboxBuilder,
|
|
335
575
|
isImageBuilder,
|
|
336
576
|
isConfigDefinition,
|
|
577
|
+
defaultTaskQueueName,
|
|
337
578
|
configBrand,
|
|
338
579
|
SourceFileRefImpl,
|
|
339
580
|
SourceDirRefImpl,
|
|
340
581
|
SandboxBuilderImpl,
|
|
341
|
-
MessageTimeoutError,
|
|
342
582
|
ImageBuilderImpl,
|
|
343
|
-
ConcurrentWaitError
|
|
344
|
-
ApprovalTimeoutError
|
|
583
|
+
ConcurrentWaitError
|
|
345
584
|
};
|