@helmr/sdk 0.1.1-rc.3 → 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.
@@ -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
  }
@@ -19,6 +23,31 @@ export interface WorkspaceSpec {
19
23
  readonly ref: string;
20
24
  readonly subpath?: string;
21
25
  }
26
+ export type GitHubRefKind = "branch" | "tag" | "sha" | "pull_request" | "unknown";
27
+ export interface GitHubPullRequestMetadata {
28
+ readonly number: number;
29
+ readonly baseRef: string;
30
+ readonly baseSha: string;
31
+ readonly headRef: string;
32
+ readonly headSha: string;
33
+ }
34
+ export interface GitHubTaskSource {
35
+ readonly kind: "github";
36
+ readonly repository: string;
37
+ readonly requestedRef: string;
38
+ readonly resolvedSha: string;
39
+ readonly refKind?: GitHubRefKind;
40
+ readonly refName?: string;
41
+ readonly fullRef?: string;
42
+ readonly subpath?: string;
43
+ readonly defaultBranch?: string;
44
+ readonly pullRequest?: GitHubPullRequestMetadata;
45
+ }
46
+ export type TaskSource = GitHubTaskSource;
47
+ export interface TaskWorkspace {
48
+ readonly path: string;
49
+ readonly projectPath: string;
50
+ }
22
51
  export interface SourceFileRef {
23
52
  readonly path: string;
24
53
  }
@@ -36,13 +65,38 @@ export interface WorkspaceCapabilities {
36
65
  readonly subpath?: string;
37
66
  }): WorkspaceSpec;
38
67
  }
39
- export declare class ApprovalTimeoutError extends Error {
40
- constructor(message: string);
41
- static [Symbol.hasInstance](value: unknown): boolean;
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;
42
75
  }
43
- export declare class MessageTimeoutError extends Error {
44
- constructor(message: string);
45
- static [Symbol.hasInstance](value: unknown): boolean;
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>;
46
100
  }
47
101
  export declare class ConcurrentWaitError extends Error {
48
102
  constructor(message: string);
@@ -62,25 +116,7 @@ export type Placement = {
62
116
  export type SecretDecls = Record<string, Placement>;
63
117
  export declare function validateSecretName(name: string, label?: string): void;
64
118
  export interface TaskContext {
65
- readonly wait: {
66
- approval(message: string, opts?: {
67
- readonly timeout?: number;
68
- readonly policy?: string;
69
- }): Promise<{
70
- readonly approved: boolean;
71
- readonly approvedBy: string;
72
- readonly at: Date;
73
- }>;
74
- message(prompt?: string, opts?: {
75
- readonly timeout?: number;
76
- readonly policy?: string;
77
- }): Promise<{
78
- readonly text: string;
79
- readonly sentBy: string;
80
- readonly at: Date;
81
- readonly attachments: readonly unknown[];
82
- }>;
83
- };
119
+ readonly wait: WaitCapabilities;
84
120
  emit(event: EmitEvent): void;
85
121
  readonly log: {
86
122
  info(...args: unknown[]): void;
@@ -91,6 +127,11 @@ export interface TaskContext {
91
127
  readonly run: {
92
128
  readonly id: string;
93
129
  };
130
+ readonly task: {
131
+ readonly id: string;
132
+ };
133
+ readonly source: TaskSource;
134
+ readonly workspace: TaskWorkspace;
94
135
  }
95
136
  export type EmitContent = {
96
137
  readonly type: "text";
@@ -116,17 +157,98 @@ export interface EmitEvent {
116
157
  readonly content: readonly EmitContent[];
117
158
  }
118
159
  export type MaybePromise<T> = T | Promise<T>;
119
- export interface TaskConfig<TPayload = unknown, TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>> {
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>> {
120
165
  readonly id: string;
121
166
  readonly sandbox: SandboxBuilder;
122
167
  readonly maxDuration?: number;
168
+ readonly queue?: TaskQueueConfig;
169
+ readonly ttl?: string;
123
170
  readonly secrets?: TSecrets;
124
- readonly run: (payload: TPayload, ctx: TaskContext) => MaybePromise<TOutput>;
125
171
  }
126
- export type Task<TPayload = unknown, TOutput = unknown, TSecrets extends SecretDecls = Record<never, never>> = TaskConfig<TPayload, TOutput, TSecrets>;
127
- export type AnyTask = Task<any, any, SecretDecls>;
128
- export type TaskPayload<TTask> = TTask extends Task<infer TPayload, any, any> ? TPayload : never;
129
- export type TaskOutput<TTask> = TTask extends Task<any, infer TOutput, any> ? Awaited<TOutput> : never;
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;
130
252
  export declare const taskBrand: unique symbol;
131
253
  export declare const taskOriginBrand: unique symbol;
132
254
  export declare const configBrand: unique symbol;
@@ -187,13 +309,20 @@ export interface SandboxResources {
187
309
  readonly cpu?: number;
188
310
  readonly memory?: string;
189
311
  }
190
- export declare function markTask<TPayload, TOutput, TSecrets extends SecretDecls>(config: TaskConfig<TPayload, TOutput, TSecrets>): Task<TPayload, Awaited<TOutput>, TSecrets>;
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>>;
191
320
  export declare function isTaskDefinition(value: unknown): value is AnyTask & {
192
321
  readonly [taskBrand]: true;
193
322
  };
194
323
  export declare function taskOriginFile(value: unknown): string | null;
195
324
  export interface HelmrConfig {
196
- readonly project?: string;
325
+ readonly project: string;
197
326
  readonly dirs: readonly string[];
198
327
  readonly ignorePatterns?: readonly string[];
199
328
  }
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
- // sdk/typescript/src/internal.ts
65
- var approvalTimeoutErrorBrand = Symbol.for("helmr.sdk.ApprovalTimeoutError");
66
- var messageTimeoutErrorBrand = Symbol.for("helmr.sdk.MessageTimeoutError");
67
- var concurrentWaitErrorBrand = Symbol.for("helmr.sdk.ConcurrentWaitError");
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 ApprovalTimeoutError extends Error {
70
- constructor(message) {
71
- super(message);
72
- this.name = "ApprovalTimeoutError";
73
- Object.defineProperty(this, approvalTimeoutErrorBrand, { value: true });
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
- static [Symbol.hasInstance](value) {
76
- return this === ApprovalTimeoutError && typeof value === "object" && value !== null && approvalTimeoutErrorBrand in value;
160
+ }
161
+ function validateQueueName(value) {
162
+ if (!isValidQueueName(value)) {
163
+ throw new TaskQueueNameError(value);
77
164
  }
78
165
  }
79
-
80
- class MessageTimeoutError extends Error {
81
- constructor(message) {
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
- static [Symbol.hasInstance](value) {
87
- return this === MessageTimeoutError && typeof value === "object" && value !== null && messageTimeoutErrorBrand in value;
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
  }
@@ -205,7 +439,7 @@ function stackFrameFile(line) {
205
439
  return match[1].startsWith("file://") ? decodeURIComponent(new URL(match[1]).pathname) : match[1];
206
440
  }
207
441
  function isSdkInternalFrame(file) {
208
- return file.includes("/sdk/typescript/src/internal.ts") || file.includes("/sdk/typescript/src/task.ts") || file.includes("/sdk/typescript/src/index.ts") || file.includes("/runtime/typescript/src/");
442
+ return file.includes("/sdk/typescript/src/internal") || file.includes("/sdk/typescript/src/task") || file.includes("/sdk/typescript/src/index") || file.includes("/runtime/typescript/src/");
209
443
  }
210
444
 
211
445
  class ImageBuilderImpl {
@@ -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
  };