@helmr/sdk 0.1.1-rc.7 → 0.1.1-rc.8
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 +5 -9
- package/dist/compile.js +41 -16
- package/dist/index.d.ts +4 -2
- package/dist/index.js +148 -26
- package/dist/internal.d.ts +53 -14
- package/dist/internal.js +87 -21
- package/dist/runtime/client.d.ts +9 -2
- package/dist/runtime/run.d.ts +29 -0
- package/dist/sandbox.d.ts +2 -2
- package/dist/schedules.d.ts +2 -4
- package/dist/task.d.ts +2 -2
- package/dist/version.d.ts +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,9 +13,9 @@ Keep this layer stable and ergonomic. Runtime adapter internals, VM details, and
|
|
|
13
13
|
|
|
14
14
|
Payload is audit data: Helmr persists it in plaintext in the `run.created` event, DB, and events stream. Do not put secret values (tokens, API keys, credentials, or PII) in payload; use `secrets:` instead. Use payload for business context such as PR numbers, repo names, ticket ids, and other identifiers.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
`
|
|
18
|
-
|
|
16
|
+
Task code declares required secret names and runtime placements. Store each value
|
|
17
|
+
with `helmr secret set NAME`; run creation does not accept secret values or
|
|
18
|
+
binding maps.
|
|
19
19
|
|
|
20
20
|
## TypeScript Runtime Client
|
|
21
21
|
|
|
@@ -69,10 +69,6 @@ await client.waitpoints.respond("waitpoint-456", {
|
|
|
69
69
|
|
|
70
70
|
### `POST /api/runs`
|
|
71
71
|
|
|
72
|
-
`payload` is a JSON field for tasks that accept payload. Payload is audit data: Helmr persists it in plaintext in the `run.created` event, DB, and events stream. Do not put secret values (tokens, API keys, credentials, or PII) in payload;
|
|
72
|
+
`payload` is a JSON field for tasks that accept payload. Payload is audit data: Helmr persists it in plaintext in the `run.created` event, DB, and events stream. Do not put secret values (tokens, API keys, credentials, or PII) in payload; declare task secrets instead. Use payload for business context such as PR numbers, repo names, ticket ids, and other identifiers.
|
|
73
73
|
|
|
74
|
-
`secrets`
|
|
75
|
-
|
|
76
|
-
```json
|
|
77
|
-
{ "OPENAI_API_KEY": "vault:openai-api-key" }
|
|
78
|
-
```
|
|
74
|
+
Runs do not accept a `secrets` field. Secret names and placements are part of the deployed task definition.
|
package/dist/compile.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
FromSchema,
|
|
15
15
|
ImageSpecSchema,
|
|
16
16
|
ImageStepSchema,
|
|
17
|
+
NetworkPolicySchema,
|
|
17
18
|
PlacementSchema as BundlePlacementSchema,
|
|
18
19
|
PlatformSchema,
|
|
19
20
|
QueueSpecSchema,
|
|
@@ -68,12 +69,8 @@ function validateSecretName(name, label = "secret name") {
|
|
|
68
69
|
if (name.length > 128) {
|
|
69
70
|
throw new Error(`${label} must be at most 128 characters`);
|
|
70
71
|
}
|
|
71
|
-
if (!/^[A-Za-
|
|
72
|
-
throw new Error(`${label} must match /^[A-Za-
|
|
73
|
-
}
|
|
74
|
-
const upper = name.toUpperCase();
|
|
75
|
-
if (upper === "CON" || upper === "PRN" || upper === "AUX" || upper === "NUL" || /^COM[1-9]$/.test(upper) || /^LPT[1-9]$/.test(upper)) {
|
|
76
|
-
throw new Error(`${label} is reserved`);
|
|
72
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(name)) {
|
|
73
|
+
throw new Error(`${label} must match /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/`);
|
|
77
74
|
}
|
|
78
75
|
}
|
|
79
76
|
var taskBrand = Symbol.for("helmr.sdk.Task");
|
|
@@ -116,6 +113,7 @@ function compile(opts) {
|
|
|
116
113
|
const subImages = compiler.compileSubImages(imageSpec);
|
|
117
114
|
const workspace = compiler.compileWorkspace(task.sandbox);
|
|
118
115
|
const resources = task.sandbox.resourceSpec;
|
|
116
|
+
const network = task.sandbox.networkSpec;
|
|
119
117
|
const maxDurationSeconds = readOptionalMaxDurationSeconds(task.maxDuration, `task "${task.id}" maxDuration`);
|
|
120
118
|
const sandboxSpec = create(SandboxSpecSchema, {
|
|
121
119
|
id: task.sandbox.id,
|
|
@@ -126,7 +124,8 @@ function compile(opts) {
|
|
|
126
124
|
...resources.memory === undefined ? {} : { memory: resources.memory },
|
|
127
125
|
...resources.disk === undefined ? {} : { disk: resources.disk }
|
|
128
126
|
})
|
|
129
|
-
} : {}
|
|
127
|
+
} : {},
|
|
128
|
+
...network ? { network: compileNetwork(network) } : {}
|
|
130
129
|
});
|
|
131
130
|
return create(BundleSchema, {
|
|
132
131
|
image: imageSpec,
|
|
@@ -143,6 +142,7 @@ function compile(opts) {
|
|
|
143
142
|
...task.queue?.concurrencyLimit === undefined || task.queue.concurrencyLimit === null ? {} : { concurrencyLimit: task.queue.concurrencyLimit }
|
|
144
143
|
}),
|
|
145
144
|
...task.ttl === undefined ? {} : { ttl: task.ttl },
|
|
145
|
+
retryPolicyJson: JSON.stringify(task.retry ?? false),
|
|
146
146
|
schedules: compileTaskSchedules(task.schedule),
|
|
147
147
|
secrets: Object.entries(readSecretDecls(task.secrets)).map(([name, placement]) => create(BundleSecretPlacementSchema, {
|
|
148
148
|
name,
|
|
@@ -151,6 +151,13 @@ function compile(opts) {
|
|
|
151
151
|
})
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
|
+
function compileNetwork(network) {
|
|
155
|
+
return create(NetworkPolicySchema, {
|
|
156
|
+
internet: network.internet,
|
|
157
|
+
allow: [...network.allow],
|
|
158
|
+
deny: [...network.deny]
|
|
159
|
+
});
|
|
160
|
+
}
|
|
154
161
|
function compileTaskSchedules(schedule) {
|
|
155
162
|
if (schedule === undefined) {
|
|
156
163
|
return [];
|
|
@@ -159,8 +166,7 @@ function compileTaskSchedules(schedule) {
|
|
|
159
166
|
create(TaskScheduleSpecSchema, {
|
|
160
167
|
id: "",
|
|
161
168
|
cron: schedule.cron,
|
|
162
|
-
timezone: schedule.timezone ?? "UTC"
|
|
163
|
-
secretBindings: { ...schedule.secretBindings }
|
|
169
|
+
timezone: schedule.timezone ?? "UTC"
|
|
164
170
|
})
|
|
165
171
|
];
|
|
166
172
|
}
|
|
@@ -426,14 +432,27 @@ function readSecretDecls(value) {
|
|
|
426
432
|
if (value === undefined) {
|
|
427
433
|
return {};
|
|
428
434
|
}
|
|
429
|
-
if (
|
|
430
|
-
throw new Error("task secrets must be an
|
|
435
|
+
if (!Array.isArray(value)) {
|
|
436
|
+
throw new Error("task secrets must be an array");
|
|
431
437
|
}
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
438
|
+
const output = {};
|
|
439
|
+
value.forEach((item, index) => {
|
|
440
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
441
|
+
throw new Error(`task secrets.${index} must be a secret object`);
|
|
442
|
+
}
|
|
443
|
+
const record = item;
|
|
444
|
+
const name = record["name"];
|
|
445
|
+
if (typeof name !== "string") {
|
|
446
|
+
throw new Error(`task secrets.${index}.name must be a string`);
|
|
447
|
+
}
|
|
448
|
+
validateSecretName(name, `task secrets.${index}.name`);
|
|
449
|
+
if (Object.hasOwn(output, name)) {
|
|
450
|
+
throw new Error(`task secrets contains duplicate secret ${JSON.stringify(name)}`);
|
|
451
|
+
}
|
|
452
|
+
const { name: _name, ...placement } = record;
|
|
453
|
+
output[name] = readPlacement(placement, `task secrets.${index}`);
|
|
454
|
+
});
|
|
455
|
+
return output;
|
|
437
456
|
}
|
|
438
457
|
function readPlacement(value, label) {
|
|
439
458
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -445,6 +464,7 @@ function readPlacement(value, label) {
|
|
|
445
464
|
if (Object.keys(record).length !== 1 || !isNonEmptyPlacementString(env)) {
|
|
446
465
|
throw new Error(`${label} must be { env: string }`);
|
|
447
466
|
}
|
|
467
|
+
validateEnvPlacementName(env, `${label}.env`);
|
|
448
468
|
return { env };
|
|
449
469
|
}
|
|
450
470
|
if ("file" in record) {
|
|
@@ -508,6 +528,11 @@ function validatePlacementMode(mode, label) {
|
|
|
508
528
|
throw new Error(`${label} must only contain permission bits`);
|
|
509
529
|
}
|
|
510
530
|
}
|
|
531
|
+
function validateEnvPlacementName(value, label) {
|
|
532
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
|
|
533
|
+
throw new Error(`${label} must match /^[A-Za-z_][A-Za-z0-9_]*$/`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
511
536
|
function validatePlacementPath(path, label) {
|
|
512
537
|
const normalized = path.trim().replaceAll("\\", "/");
|
|
513
538
|
if (path !== path.trim()) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConcurrentWaitError, validateSecretName, type CacheBuilder, type CacheMount, type CacheMountBinding, type EmitContent, type EmitEvent, type ImageBuilder, type ImageRunOptions, type NoPayload, type Placement, type SandboxBuilder, type SecretDecls, type SecretMountBinding, type SourceCapabilities, type SourceDirRef, type SourceDirectoryOptions, type SourceFileRef, type TaskContext, type TaskWorkspace, type TaskOutput, type TaskPayload, type TaskTriggerPayload, type WaitCapabilities, type WaitForInput, type WaitJson, type WaitOptions, type WaitResolution, type WaitHumanOptions, type WaitUntilInput } from "./internal.js";
|
|
1
|
+
import { ConcurrentWaitError, validateSecretName, type CacheBuilder, type CacheMount, type CacheMountBinding, type EmitContent, type EmitEvent, type ImageBuilder, type ImageRunOptions, type NoPayload, type Placement, type SandboxBuilder, type SandboxNetwork, type SecretDecls, type SecretMountBinding, type SourceCapabilities, type SourceDirRef, type SourceDirectoryOptions, type SourceFileRef, type TaskContext, type TaskWorkspace, type TaskOutput, type TaskPayload, type TaskTriggerPayload, type WaitCapabilities, type WaitForInput, type WaitJson, type WaitOptions, type WaitResolution, type WaitHumanOptions, type WaitUntilInput } from "./internal.js";
|
|
2
2
|
import { PayloadSchemaValidationError } from "./schema/payload.js";
|
|
3
3
|
import { idempotencyKeys } from "./idempotency.js";
|
|
4
4
|
export type { IdempotencyKey, IdempotencyKeyCreateOptions, IdempotencyKeyInput, IdempotencyKeyScope } from "./idempotency.js";
|
|
@@ -14,7 +14,7 @@ export { AuthError, RunNotFoundError, TimeoutError, UnsupportedTransportError }
|
|
|
14
14
|
export { ConcurrentWaitError, PayloadSchemaValidationError };
|
|
15
15
|
export { type LogSnapshot, type ListRunEventsOptions, type ListRunsOptions, type PendingDelayWaitpoint, type PendingHumanWaitpoint, type PendingWaitpoint, type RetrieveRunOptions, type RunEvent, type RunEventRecord, type RunEventRecordPage, type RunHandle, type RunSnapshot, type RunStatus, type RunSummary, type RunWaitOptions, type SubscribeRunEventsOptions, type WaitpointRespondOptions, type WaitpointRef, } from "./runtime/run.js";
|
|
16
16
|
export { defineConfig, idempotencyKeys, queue, sandbox, task, tasks };
|
|
17
|
-
export type { HelmrConfig, HelmrConfigInput, Placement, SecretDecls, Task, TaskConfig, TaskQueueConfig, TaskOutput, TaskPayload, TaskTriggerPayload, WaitCapabilities, WaitForInput, WaitJson, WaitOptions, WaitResolution, WaitHumanOptions, WaitUntilInput, PayloadSchema, StandardSchemaV1, NoPayload, };
|
|
17
|
+
export type { HelmrConfig, HelmrConfigInput, Placement, SandboxNetwork, SecretDecls, Task, TaskConfig, TaskQueueConfig, TaskOutput, TaskPayload, TaskTriggerPayload, WaitCapabilities, WaitForInput, WaitJson, WaitOptions, WaitResolution, WaitHumanOptions, WaitUntilInput, PayloadSchema, StandardSchemaV1, NoPayload, };
|
|
18
18
|
export declare const image: (id: string) => ImageBuilder;
|
|
19
19
|
export declare const cache: (id: string) => CacheBuilder;
|
|
20
20
|
export declare const source: SourceCapabilities;
|
|
@@ -23,6 +23,8 @@ export { validateSecretName };
|
|
|
23
23
|
export declare const runs: {
|
|
24
24
|
retrieve: <TOutput = unknown>(idOrHandle: string | import("./index.js").RunHandle<TOutput>, opts?: import("./index.js").RetrieveRunOptions) => Promise<import("./index.js").RunSnapshot<TOutput>>;
|
|
25
25
|
wait: <TOutput = unknown>(idOrHandle: string | import("./index.js").RunHandle<TOutput>, opts?: import("./index.js").RunWaitOptions) => Promise<import("./index.js").RunSnapshot<TOutput>>;
|
|
26
|
+
cancel: <TOutput = unknown>(idOrHandle: string | import("./index.js").RunHandle<TOutput>, opts?: import("./runtime/run.js").CancelRunOptions) => Promise<import("./index.js").RunSnapshot<TOutput>>;
|
|
27
|
+
replay: <TPayload = unknown, TOutput = unknown>(idOrHandle: string | import("./index.js").RunHandle<TOutput>, opts?: import("./runtime/run.js").ReplayRunOptions<TPayload>) => Promise<import("./index.js").RunHandle<TOutput>>;
|
|
26
28
|
list: (opts?: import("./index.js").ListRunsOptions) => Promise<import("./index.js").RunSummary[]>;
|
|
27
29
|
logs: {
|
|
28
30
|
retrieve: <TOutput = unknown>(idOrHandle: string | import("./index.js").RunHandle<TOutput>, opts?: {
|
package/dist/index.js
CHANGED
|
@@ -212,12 +212,8 @@ function validateSecretName(name, label = "secret name") {
|
|
|
212
212
|
if (name.length > 128) {
|
|
213
213
|
throw new Error(`${label} must be at most 128 characters`);
|
|
214
214
|
}
|
|
215
|
-
if (!/^[A-Za-
|
|
216
|
-
throw new Error(`${label} must match /^[A-Za-
|
|
217
|
-
}
|
|
218
|
-
const upper = name.toUpperCase();
|
|
219
|
-
if (upper === "CON" || upper === "PRN" || upper === "AUX" || upper === "NUL" || /^COM[1-9]$/.test(upper) || /^LPT[1-9]$/.test(upper)) {
|
|
220
|
-
throw new Error(`${label} is reserved`);
|
|
215
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(name)) {
|
|
216
|
+
throw new Error(`${label} must match /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/`);
|
|
221
217
|
}
|
|
222
218
|
}
|
|
223
219
|
function validateImageBuildSecretRef(name) {
|
|
@@ -283,6 +279,7 @@ function markTaskInternal(config, schedule) {
|
|
|
283
279
|
validateTaskQueue(config.id, config.queue);
|
|
284
280
|
validateTaskSchedule(config.id, schedule);
|
|
285
281
|
validateOptionalTTL(config.ttl, `task ${JSON.stringify(config.id)} ttl`);
|
|
282
|
+
validateRetryPolicy(config.retry, `task ${JSON.stringify(config.id)} retry`);
|
|
286
283
|
assertPayloadSchema(config.payload, `task ${JSON.stringify(config.id)} payload`);
|
|
287
284
|
if (schedule !== undefined) {
|
|
288
285
|
Object.defineProperty(config, "schedule", {
|
|
@@ -294,6 +291,54 @@ function markTaskInternal(config, schedule) {
|
|
|
294
291
|
Object.defineProperty(config, taskOriginBrand, { value: captureTaskOrigin() });
|
|
295
292
|
return config;
|
|
296
293
|
}
|
|
294
|
+
function validateRetryPolicy(value, label = "retry") {
|
|
295
|
+
if (value === undefined || value === false) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
299
|
+
throw new Error(`${label} must be false or a retry policy object`);
|
|
300
|
+
}
|
|
301
|
+
const record = value;
|
|
302
|
+
for (const key of Object.keys(record)) {
|
|
303
|
+
if (key !== "maxAttempts" && key !== "backoff") {
|
|
304
|
+
throw new Error(`${label}.${key} is not supported`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (typeof record["maxAttempts"] !== "number" || !Number.isInteger(record["maxAttempts"]) || record["maxAttempts"] < 1 || record["maxAttempts"] > 10) {
|
|
308
|
+
throw new Error(`${label}.maxAttempts must be an integer between 1 and 10`);
|
|
309
|
+
}
|
|
310
|
+
const backoff = record["backoff"];
|
|
311
|
+
if (backoff !== undefined) {
|
|
312
|
+
if (backoff === null || typeof backoff !== "object" || Array.isArray(backoff)) {
|
|
313
|
+
throw new Error(`${label}.backoff must be an object`);
|
|
314
|
+
}
|
|
315
|
+
const backoffRecord = backoff;
|
|
316
|
+
for (const key of Object.keys(backoffRecord)) {
|
|
317
|
+
if (key !== "minMs" && key !== "maxMs" && key !== "factor" && key !== "jitter") {
|
|
318
|
+
throw new Error(`${label}.backoff.${key} is not supported`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
validateOptionalPositiveInteger(backoffRecord["minMs"], `${label}.backoff.minMs`);
|
|
322
|
+
validateOptionalPositiveInteger(backoffRecord["maxMs"], `${label}.backoff.maxMs`);
|
|
323
|
+
const factor = backoffRecord["factor"];
|
|
324
|
+
if (factor !== undefined && (typeof factor !== "number" || !Number.isFinite(factor) || factor <= 0)) {
|
|
325
|
+
throw new Error(`${label}.backoff.factor must be a positive number`);
|
|
326
|
+
}
|
|
327
|
+
const jitter = backoffRecord["jitter"];
|
|
328
|
+
if (jitter !== undefined && jitter !== "none" && jitter !== "full") {
|
|
329
|
+
throw new Error(`${label}.backoff.jitter must be "none" or "full"`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function validateOptionalPositiveInteger(value, label) {
|
|
334
|
+
if (value === undefined) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value > 0) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
throw new Error(`${label} must be a positive integer`);
|
|
341
|
+
}
|
|
297
342
|
function validateTaskSchedule(taskId, value) {
|
|
298
343
|
if (value === undefined) {
|
|
299
344
|
return;
|
|
@@ -304,17 +349,6 @@ function validateTaskSchedule(taskId, value) {
|
|
|
304
349
|
if (value.timezone !== undefined && value.timezone.trim() === "") {
|
|
305
350
|
throw new Error(`task ${JSON.stringify(taskId)} schedule timezone must not be empty`);
|
|
306
351
|
}
|
|
307
|
-
if (value.secretBindings !== undefined) {
|
|
308
|
-
if (value.secretBindings === null || typeof value.secretBindings !== "object" || Array.isArray(value.secretBindings)) {
|
|
309
|
-
throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings must be an object`);
|
|
310
|
-
}
|
|
311
|
-
for (const [name, binding] of Object.entries(value.secretBindings)) {
|
|
312
|
-
validateSecretName(name, `task ${JSON.stringify(taskId)} schedule secretBindings.${name}`);
|
|
313
|
-
if (typeof binding !== "string" || binding.trim() === "") {
|
|
314
|
-
throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings.${name} must be a non-empty string`);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
352
|
}
|
|
319
353
|
function validateTaskQueue(taskId, value) {
|
|
320
354
|
if (value === undefined) {
|
|
@@ -479,21 +513,23 @@ class SandboxBuilderImpl {
|
|
|
479
513
|
imageBuilder;
|
|
480
514
|
workspaceBinding;
|
|
481
515
|
resourceSpec;
|
|
482
|
-
|
|
516
|
+
networkSpec;
|
|
517
|
+
constructor(id, imageBuilder, workspaceBinding, resourceSpec, networkSpec) {
|
|
483
518
|
Object.defineProperty(this, sandboxBuilderBrand, { value: true });
|
|
484
519
|
this.id = id;
|
|
485
520
|
this.imageBuilder = imageBuilder;
|
|
486
521
|
this.workspaceBinding = workspaceBinding;
|
|
487
522
|
this.resourceSpec = resourceSpec;
|
|
523
|
+
this.networkSpec = networkSpec;
|
|
488
524
|
}
|
|
489
525
|
image(img) {
|
|
490
526
|
if (!isImageBuilder(img)) {
|
|
491
527
|
throw new Error("sandbox.image() requires an ImageBuilder created by image()");
|
|
492
528
|
}
|
|
493
|
-
return new SandboxBuilderImpl(this.id, img, this.workspaceBinding, this.resourceSpec);
|
|
529
|
+
return new SandboxBuilderImpl(this.id, img, this.workspaceBinding, this.resourceSpec, this.networkSpec);
|
|
494
530
|
}
|
|
495
531
|
workspace(mountPath = "/workspace") {
|
|
496
|
-
return new SandboxBuilderImpl(this.id, this.imageBuilder, { mountPath: normalizeWorkspaceMountPath(mountPath) }, this.resourceSpec);
|
|
532
|
+
return new SandboxBuilderImpl(this.id, this.imageBuilder, { mountPath: normalizeWorkspaceMountPath(mountPath) }, this.resourceSpec, this.networkSpec);
|
|
497
533
|
}
|
|
498
534
|
resources(opts) {
|
|
499
535
|
const resourceSpec = {
|
|
@@ -501,9 +537,38 @@ class SandboxBuilderImpl {
|
|
|
501
537
|
...opts.memory === undefined ? {} : { memory: opts.memory },
|
|
502
538
|
...opts.disk === undefined ? {} : { disk: opts.disk }
|
|
503
539
|
};
|
|
504
|
-
return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, resourceSpec);
|
|
540
|
+
return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, resourceSpec, this.networkSpec);
|
|
541
|
+
}
|
|
542
|
+
network(opts) {
|
|
543
|
+
return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, this.resourceSpec, normalizeSandboxNetwork(opts));
|
|
505
544
|
}
|
|
506
545
|
}
|
|
546
|
+
function normalizeSandboxNetwork(opts) {
|
|
547
|
+
const value = opts.internet;
|
|
548
|
+
if (value === undefined || value === true) {
|
|
549
|
+
return { internet: true, allow: [], deny: [] };
|
|
550
|
+
}
|
|
551
|
+
if (value === false) {
|
|
552
|
+
return { internet: false, allow: [], deny: [] };
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
internet: true,
|
|
556
|
+
allow: normalizeNetworkEntries(value.allow, "network.internet.allow"),
|
|
557
|
+
deny: normalizeNetworkEntries(value.deny, "network.internet.deny")
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function normalizeNetworkEntries(entries, label) {
|
|
561
|
+
if (entries === undefined) {
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
return entries.map((entry, index) => {
|
|
565
|
+
const normalized = entry.trim();
|
|
566
|
+
if (normalized === "") {
|
|
567
|
+
throw new Error(`${label}[${index}] must not be empty`);
|
|
568
|
+
}
|
|
569
|
+
return normalized;
|
|
570
|
+
});
|
|
571
|
+
}
|
|
507
572
|
|
|
508
573
|
class SourceFileRefImpl {
|
|
509
574
|
path;
|
|
@@ -609,6 +674,13 @@ class UnsupportedTransportError extends Error {
|
|
|
609
674
|
}
|
|
610
675
|
}
|
|
611
676
|
|
|
677
|
+
// sdk/typescript/src/version.ts
|
|
678
|
+
var HELMR_API_VERSION = "2026-06-06";
|
|
679
|
+
var HELMR_API_VERSION_HEADER = "Helmr-API-Version";
|
|
680
|
+
var HELMR_SDK_VERSION_HEADER = "Helmr-SDK-Version";
|
|
681
|
+
var SOURCE_PACKAGE_VERSION = "0.1.0";
|
|
682
|
+
var HELMR_SDK_VERSION = "0.1.1-rc.8".trim() !== "" ? "0.1.1-rc.8" : SOURCE_PACKAGE_VERSION;
|
|
683
|
+
|
|
612
684
|
// sdk/typescript/src/runtime/run.ts
|
|
613
685
|
function runHandle(id, taskId) {
|
|
614
686
|
return { id, taskId };
|
|
@@ -623,6 +695,12 @@ function runSnapshot(snapshot) {
|
|
|
623
695
|
createdAt: snapshot.createdAt ?? null,
|
|
624
696
|
updatedAt: snapshot.updatedAt ?? null,
|
|
625
697
|
pendingWaitpoint: snapshot.pendingWaitpoint ?? null,
|
|
698
|
+
...snapshot.version === undefined && snapshot.deploymentVersion === undefined ? {} : { version: snapshot.version ?? snapshot.deploymentVersion ?? null },
|
|
699
|
+
...snapshot.deploymentVersion === undefined && snapshot.version === undefined ? {} : { deploymentVersion: snapshot.deploymentVersion ?? snapshot.version ?? null },
|
|
700
|
+
...snapshot.apiVersion === undefined ? {} : { apiVersion: snapshot.apiVersion },
|
|
701
|
+
...snapshot.sdkVersion === undefined ? {} : { sdkVersion: snapshot.sdkVersion },
|
|
702
|
+
...snapshot.cliVersion === undefined ? {} : { cliVersion: snapshot.cliVersion },
|
|
703
|
+
attemptNumber: snapshot.attemptNumber ?? null,
|
|
626
704
|
...runStateBooleans(status),
|
|
627
705
|
...snapshot.output === undefined ? {} : { output: snapshot.output }
|
|
628
706
|
};
|
|
@@ -733,6 +811,7 @@ class HelmrClient {
|
|
|
733
811
|
return await this.#triggerRun(task.id, payload, opts, readOptionalMaxDurationSeconds(task.maxDuration));
|
|
734
812
|
}
|
|
735
813
|
async#triggerRun(taskId, payload, opts, maxDurationSeconds) {
|
|
814
|
+
validateRetryPolicy(opts.retry, "retry");
|
|
736
815
|
const runOptions = {
|
|
737
816
|
...opts.deploymentId === undefined ? {} : { deployment_id: opts.deploymentId },
|
|
738
817
|
...opts.version === undefined ? {} : { version: opts.version },
|
|
@@ -740,6 +819,9 @@ class HelmrClient {
|
|
|
740
819
|
...opts.concurrencyKey === undefined ? {} : { concurrency_key: opts.concurrencyKey },
|
|
741
820
|
...opts.priority === undefined ? {} : { priority: opts.priority },
|
|
742
821
|
...opts.ttl === undefined ? {} : { ttl: opts.ttl },
|
|
822
|
+
...opts.retry === undefined ? {} : { retry: opts.retry },
|
|
823
|
+
...opts.metadata === undefined ? {} : { metadata: opts.metadata },
|
|
824
|
+
...opts.tags === undefined ? {} : { tags: opts.tags },
|
|
743
825
|
...maxDurationSeconds === undefined ? {} : { max_duration_seconds: maxDurationSeconds },
|
|
744
826
|
...runIdempotencyRequestFields(opts.idempotencyKey, opts.idempotencyKeyTTL)
|
|
745
827
|
};
|
|
@@ -747,7 +829,6 @@ class HelmrClient {
|
|
|
747
829
|
method: "POST",
|
|
748
830
|
body: JSON.stringify({
|
|
749
831
|
task_id: taskId,
|
|
750
|
-
secrets: opts.secrets ?? {},
|
|
751
832
|
...payload === undefined ? {} : { payload },
|
|
752
833
|
...Object.keys(runOptions).length === 0 ? {} : { options: runOptions }
|
|
753
834
|
}),
|
|
@@ -783,6 +864,24 @@ class HelmrClient {
|
|
|
783
864
|
wait.cleanup();
|
|
784
865
|
}
|
|
785
866
|
},
|
|
867
|
+
cancel: async (idOrHandle, opts = {}) => {
|
|
868
|
+
const response = await this.#json(`/api/runs/${encodeURIComponent(runId(idOrHandle))}/cancel`, {
|
|
869
|
+
method: "POST",
|
|
870
|
+
body: JSON.stringify(cancelRunBody(opts)),
|
|
871
|
+
headers: { "content-type": "application/json" },
|
|
872
|
+
...requestSignal(opts.signal)
|
|
873
|
+
});
|
|
874
|
+
return runResponseToSnapshot(response.run);
|
|
875
|
+
},
|
|
876
|
+
replay: async (idOrHandle, opts = {}) => {
|
|
877
|
+
const response = await this.#json(`/api/runs/${encodeURIComponent(runId(idOrHandle))}/replay`, {
|
|
878
|
+
method: "POST",
|
|
879
|
+
body: JSON.stringify(replayRunBody(opts)),
|
|
880
|
+
headers: { "content-type": "application/json" },
|
|
881
|
+
...requestSignal(opts.signal)
|
|
882
|
+
});
|
|
883
|
+
return runHandle(response.run.id, response.run.task_id);
|
|
884
|
+
},
|
|
786
885
|
list: async (opts = {}) => {
|
|
787
886
|
const query = new URLSearchParams;
|
|
788
887
|
if (opts.status !== undefined)
|
|
@@ -948,6 +1047,8 @@ class HelmrClient {
|
|
|
948
1047
|
}
|
|
949
1048
|
async#fetch(path, init = {}) {
|
|
950
1049
|
const headers = new Headers(init.headers);
|
|
1050
|
+
headers.set(HELMR_API_VERSION_HEADER, HELMR_API_VERSION);
|
|
1051
|
+
headers.set(HELMR_SDK_VERSION_HEADER, HELMR_SDK_VERSION);
|
|
951
1052
|
if (this.#apiKey !== undefined) {
|
|
952
1053
|
headers.set("authorization", `Bearer ${this.#apiKey}`);
|
|
953
1054
|
}
|
|
@@ -1001,6 +1102,12 @@ function runResponseToSnapshot(response) {
|
|
|
1001
1102
|
return runSnapshot({
|
|
1002
1103
|
id: response.id,
|
|
1003
1104
|
taskId: response.task_id,
|
|
1105
|
+
...response.version === undefined && response.deployment_version === undefined ? {} : { version: response.version ?? response.deployment_version ?? null },
|
|
1106
|
+
...response.deployment_version === undefined && response.version === undefined ? {} : { deploymentVersion: response.deployment_version ?? response.version ?? null },
|
|
1107
|
+
...response.api_version === undefined ? {} : { apiVersion: response.api_version },
|
|
1108
|
+
...response.sdk_version === undefined ? {} : { sdkVersion: response.sdk_version },
|
|
1109
|
+
...response.cli_version === undefined ? {} : { cliVersion: response.cli_version },
|
|
1110
|
+
attemptNumber: response.attempt_number ?? null,
|
|
1004
1111
|
status: response.status,
|
|
1005
1112
|
exitCode: response.exit_code ?? null,
|
|
1006
1113
|
...response.created_at === undefined ? {} : { createdAt: response.created_at },
|
|
@@ -1009,6 +1116,23 @@ function runResponseToSnapshot(response) {
|
|
|
1009
1116
|
..."output" in response ? { output: response.output } : {}
|
|
1010
1117
|
});
|
|
1011
1118
|
}
|
|
1119
|
+
function cancelRunBody(opts) {
|
|
1120
|
+
return {
|
|
1121
|
+
...opts.reason === undefined ? {} : { reason: opts.reason },
|
|
1122
|
+
...opts.force === undefined ? {} : { force: opts.force },
|
|
1123
|
+
...opts.idempotencyKey === undefined ? {} : { idempotency_key: opts.idempotencyKey }
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
function replayRunBody(opts) {
|
|
1127
|
+
return {
|
|
1128
|
+
...opts.version === undefined ? {} : { version: opts.version },
|
|
1129
|
+
...opts.payload === undefined ? {} : { payload: opts.payload },
|
|
1130
|
+
...opts.reason === undefined ? {} : { reason: opts.reason },
|
|
1131
|
+
...opts.idempotencyKey === undefined ? {} : { idempotency_key: opts.idempotencyKey },
|
|
1132
|
+
...opts.metadata === undefined ? {} : { metadata: opts.metadata },
|
|
1133
|
+
...opts.tags === undefined ? {} : { tags: opts.tags }
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1012
1136
|
function scheduleCreateBody(opts) {
|
|
1013
1137
|
return {
|
|
1014
1138
|
..."deduplicationKey" in opts && opts.deduplicationKey !== undefined ? { deduplication_key: opts.deduplicationKey } : {},
|
|
@@ -1019,7 +1143,6 @@ function scheduleCreateBody(opts) {
|
|
|
1019
1143
|
cron: opts.cron,
|
|
1020
1144
|
...opts.timezone === undefined ? {} : { timezone: opts.timezone },
|
|
1021
1145
|
...opts.active === undefined ? {} : { active: opts.active },
|
|
1022
|
-
...opts.secretBindings === undefined ? {} : { secret_bindings: opts.secretBindings },
|
|
1023
1146
|
...opts.options === undefined ? {} : { options: runOptionsBody(opts.options) }
|
|
1024
1147
|
};
|
|
1025
1148
|
}
|
|
@@ -1468,11 +1591,10 @@ function queue(config) {
|
|
|
1468
1591
|
|
|
1469
1592
|
// sdk/typescript/src/schedules.ts
|
|
1470
1593
|
function task2(config) {
|
|
1471
|
-
const { cron,
|
|
1594
|
+
const { cron, ...taskConfig } = config;
|
|
1472
1595
|
const schedule = cron === undefined ? undefined : {
|
|
1473
1596
|
cron: typeof cron === "string" ? cron : cron.pattern,
|
|
1474
|
-
...typeof cron === "string" || cron.timezone === undefined ? {} : { timezone: cron.timezone }
|
|
1475
|
-
...secretBindings === undefined ? {} : { secretBindings }
|
|
1597
|
+
...typeof cron === "string" || cron.timezone === undefined ? {} : { timezone: cron.timezone }
|
|
1476
1598
|
};
|
|
1477
1599
|
const marked = markScheduledTask({ ...taskConfig, payload: scheduledTaskPayloadSchema }, schedule);
|
|
1478
1600
|
Object.defineProperty(marked, "trigger", {
|
package/dist/internal.d.ts
CHANGED
|
@@ -80,7 +80,20 @@ export type Placement = {
|
|
|
80
80
|
readonly mode?: string;
|
|
81
81
|
readonly owner?: string;
|
|
82
82
|
};
|
|
83
|
-
export type
|
|
83
|
+
export type SecretDecl = ({
|
|
84
|
+
readonly env: string;
|
|
85
|
+
} | {
|
|
86
|
+
readonly file: string;
|
|
87
|
+
readonly mode?: string;
|
|
88
|
+
readonly owner?: string;
|
|
89
|
+
} | {
|
|
90
|
+
readonly dir: string;
|
|
91
|
+
readonly mode?: string;
|
|
92
|
+
readonly owner?: string;
|
|
93
|
+
}) & {
|
|
94
|
+
readonly name: string;
|
|
95
|
+
};
|
|
96
|
+
export type SecretDecls = readonly SecretDecl[];
|
|
84
97
|
export declare function validateSecretName(name: string, label?: string): void;
|
|
85
98
|
export interface TaskContext {
|
|
86
99
|
readonly wait: WaitCapabilities;
|
|
@@ -93,6 +106,11 @@ export interface TaskContext {
|
|
|
93
106
|
readonly signal: AbortSignal;
|
|
94
107
|
readonly run: {
|
|
95
108
|
readonly id: string;
|
|
109
|
+
readonly attemptId?: string;
|
|
110
|
+
readonly attemptNumber?: number;
|
|
111
|
+
readonly sessionId?: string;
|
|
112
|
+
readonly snapshotVersion?: number;
|
|
113
|
+
readonly replayedFromRunId?: string;
|
|
96
114
|
};
|
|
97
115
|
readonly task: {
|
|
98
116
|
readonly id: string;
|
|
@@ -127,12 +145,13 @@ declare const noPayloadBrand: unique symbol;
|
|
|
127
145
|
export interface NoPayload {
|
|
128
146
|
readonly [noPayloadBrand]: "NoPayload";
|
|
129
147
|
}
|
|
130
|
-
export interface TaskConfigBase<TSecrets extends SecretDecls =
|
|
148
|
+
export interface TaskConfigBase<TSecrets extends SecretDecls = readonly []> {
|
|
131
149
|
readonly id: string;
|
|
132
150
|
readonly sandbox: SandboxBuilder;
|
|
133
151
|
readonly maxDuration?: number;
|
|
134
152
|
readonly queue?: TaskQueueConfig;
|
|
135
153
|
readonly ttl?: string;
|
|
154
|
+
readonly retry?: RetryPolicy;
|
|
136
155
|
readonly secrets?: TSecrets;
|
|
137
156
|
}
|
|
138
157
|
export interface TaskQueueConfig {
|
|
@@ -142,7 +161,6 @@ export interface TaskQueueConfig {
|
|
|
142
161
|
export interface InternalTaskScheduleConfig {
|
|
143
162
|
readonly cron: string;
|
|
144
163
|
readonly timezone?: string;
|
|
145
|
-
readonly secretBindings?: Record<string, string>;
|
|
146
164
|
}
|
|
147
165
|
export type TaskRunOptions<TSecrets extends SecretDecls> = {
|
|
148
166
|
readonly deploymentId?: string;
|
|
@@ -151,26 +169,32 @@ export type TaskRunOptions<TSecrets extends SecretDecls> = {
|
|
|
151
169
|
readonly concurrencyKey?: string;
|
|
152
170
|
readonly priority?: number;
|
|
153
171
|
readonly ttl?: string;
|
|
172
|
+
readonly retry?: RetryPolicy;
|
|
173
|
+
readonly metadata?: Record<string, unknown>;
|
|
174
|
+
readonly tags?: readonly string[];
|
|
154
175
|
readonly idempotencyKey?: IdempotencyKeyInput;
|
|
155
176
|
readonly idempotencyKeyTTL?: string;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
readonly
|
|
160
|
-
readonly
|
|
177
|
+
};
|
|
178
|
+
export type RetryPolicy = false | {
|
|
179
|
+
readonly maxAttempts: number;
|
|
180
|
+
readonly backoff?: {
|
|
181
|
+
readonly minMs?: number;
|
|
182
|
+
readonly maxMs?: number;
|
|
183
|
+
readonly factor?: number;
|
|
184
|
+
readonly jitter?: "none" | "full";
|
|
161
185
|
};
|
|
162
|
-
}
|
|
186
|
+
};
|
|
163
187
|
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>>>;
|
|
164
|
-
export type TaskConfigWithPayload<TPayloadSchema extends PayloadSchema<any, any>, TOutput = unknown, TSecrets extends SecretDecls =
|
|
188
|
+
export type TaskConfigWithPayload<TPayloadSchema extends PayloadSchema<any, any>, TOutput = unknown, TSecrets extends SecretDecls = readonly []> = TaskConfigBase<TSecrets> & {
|
|
165
189
|
readonly payload: TPayloadSchema;
|
|
166
190
|
readonly run: (payload: PayloadSchemaOutput<TPayloadSchema>, ctx: TaskContext) => MaybePromise<TOutput>;
|
|
167
191
|
};
|
|
168
|
-
export type TaskConfigWithoutPayload<TOutput = unknown, TSecrets extends SecretDecls =
|
|
192
|
+
export type TaskConfigWithoutPayload<TOutput = unknown, TSecrets extends SecretDecls = readonly []> = TaskConfigBase<TSecrets> & {
|
|
169
193
|
readonly payload?: undefined;
|
|
170
194
|
readonly run: (ctx: TaskContext) => MaybePromise<TOutput>;
|
|
171
195
|
};
|
|
172
|
-
export type TaskConfig<TPayload = NoPayload, TOutput = unknown, TSecrets extends SecretDecls =
|
|
173
|
-
export type Task<TPayload = NoPayload, TOutput = unknown, TSecrets extends SecretDecls =
|
|
196
|
+
export type TaskConfig<TPayload = NoPayload, TOutput = unknown, TSecrets extends SecretDecls = readonly [], TPayloadSchema extends PayloadSchema<any, any> | undefined = undefined> = TPayloadSchema extends PayloadSchema<any, any> ? TaskConfigWithPayload<TPayloadSchema, TOutput, TSecrets> : TaskConfigWithoutPayload<TOutput, TSecrets>;
|
|
197
|
+
export type Task<TPayload = NoPayload, TOutput = unknown, TSecrets extends SecretDecls = readonly [], TPayloadInput = TPayload> = TaskConfigBase<TSecrets> & {
|
|
174
198
|
readonly "~types"?: {
|
|
175
199
|
readonly payloadInput: TPayloadInput;
|
|
176
200
|
readonly payload: TPayload;
|
|
@@ -238,6 +262,7 @@ export interface SandboxBuilder {
|
|
|
238
262
|
readonly memory?: string;
|
|
239
263
|
readonly disk?: string;
|
|
240
264
|
}): SandboxBuilder;
|
|
265
|
+
network(opts: SandboxNetwork): SandboxBuilder;
|
|
241
266
|
}
|
|
242
267
|
export type ImageBuildStep = {
|
|
243
268
|
readonly kind: "from";
|
|
@@ -275,9 +300,21 @@ export interface SandboxResources {
|
|
|
275
300
|
readonly memory?: string;
|
|
276
301
|
readonly disk?: string;
|
|
277
302
|
}
|
|
303
|
+
export type SandboxNetwork = {
|
|
304
|
+
readonly internet?: boolean | {
|
|
305
|
+
readonly allow?: readonly string[];
|
|
306
|
+
readonly deny?: readonly string[];
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
export interface SandboxNetworkSpec {
|
|
310
|
+
readonly internet: boolean;
|
|
311
|
+
readonly allow: readonly string[];
|
|
312
|
+
readonly deny: readonly string[];
|
|
313
|
+
}
|
|
278
314
|
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>;
|
|
279
315
|
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>;
|
|
280
316
|
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>;
|
|
317
|
+
export declare function validateRetryPolicy(value: unknown, label?: string): void;
|
|
281
318
|
export declare function validateTaskSchedule(taskId: string, value: InternalTaskScheduleConfig | undefined): void;
|
|
282
319
|
export declare function validateTaskQueue(taskId: string, value: TaskQueueConfig | undefined): void;
|
|
283
320
|
export declare function defaultTaskQueueName(taskId: string): string;
|
|
@@ -313,7 +350,8 @@ export declare class SandboxBuilderImpl implements SandboxBuilder {
|
|
|
313
350
|
readonly imageBuilder: ImageBuilderImpl | undefined;
|
|
314
351
|
readonly workspaceBinding: SandboxWorkspace | undefined;
|
|
315
352
|
readonly resourceSpec: SandboxResources | undefined;
|
|
316
|
-
|
|
353
|
+
readonly networkSpec: SandboxNetworkSpec | undefined;
|
|
354
|
+
constructor(id: string, imageBuilder?: ImageBuilderImpl, workspaceBinding?: SandboxWorkspace, resourceSpec?: SandboxResources, networkSpec?: SandboxNetworkSpec);
|
|
317
355
|
image(img: ImageBuilder): SandboxBuilder;
|
|
318
356
|
workspace(mountPath?: string): SandboxBuilder;
|
|
319
357
|
resources(opts: {
|
|
@@ -321,6 +359,7 @@ export declare class SandboxBuilderImpl implements SandboxBuilder {
|
|
|
321
359
|
readonly memory?: string;
|
|
322
360
|
readonly disk?: string;
|
|
323
361
|
}): SandboxBuilder;
|
|
362
|
+
network(opts: SandboxNetwork): SandboxBuilder;
|
|
324
363
|
}
|
|
325
364
|
export declare class SourceFileRefImpl implements SourceFileRef {
|
|
326
365
|
readonly path: string;
|
package/dist/internal.js
CHANGED
|
@@ -212,12 +212,8 @@ function validateSecretName(name, label = "secret name") {
|
|
|
212
212
|
if (name.length > 128) {
|
|
213
213
|
throw new Error(`${label} must be at most 128 characters`);
|
|
214
214
|
}
|
|
215
|
-
if (!/^[A-Za-
|
|
216
|
-
throw new Error(`${label} must match /^[A-Za-
|
|
217
|
-
}
|
|
218
|
-
const upper = name.toUpperCase();
|
|
219
|
-
if (upper === "CON" || upper === "PRN" || upper === "AUX" || upper === "NUL" || /^COM[1-9]$/.test(upper) || /^LPT[1-9]$/.test(upper)) {
|
|
220
|
-
throw new Error(`${label} is reserved`);
|
|
215
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(name)) {
|
|
216
|
+
throw new Error(`${label} must match /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/`);
|
|
221
217
|
}
|
|
222
218
|
}
|
|
223
219
|
function validateImageBuildSecretRef(name) {
|
|
@@ -283,6 +279,7 @@ function markTaskInternal(config, schedule) {
|
|
|
283
279
|
validateTaskQueue(config.id, config.queue);
|
|
284
280
|
validateTaskSchedule(config.id, schedule);
|
|
285
281
|
validateOptionalTTL(config.ttl, `task ${JSON.stringify(config.id)} ttl`);
|
|
282
|
+
validateRetryPolicy(config.retry, `task ${JSON.stringify(config.id)} retry`);
|
|
286
283
|
assertPayloadSchema(config.payload, `task ${JSON.stringify(config.id)} payload`);
|
|
287
284
|
if (schedule !== undefined) {
|
|
288
285
|
Object.defineProperty(config, "schedule", {
|
|
@@ -294,6 +291,54 @@ function markTaskInternal(config, schedule) {
|
|
|
294
291
|
Object.defineProperty(config, taskOriginBrand, { value: captureTaskOrigin() });
|
|
295
292
|
return config;
|
|
296
293
|
}
|
|
294
|
+
function validateRetryPolicy(value, label = "retry") {
|
|
295
|
+
if (value === undefined || value === false) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
299
|
+
throw new Error(`${label} must be false or a retry policy object`);
|
|
300
|
+
}
|
|
301
|
+
const record = value;
|
|
302
|
+
for (const key of Object.keys(record)) {
|
|
303
|
+
if (key !== "maxAttempts" && key !== "backoff") {
|
|
304
|
+
throw new Error(`${label}.${key} is not supported`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (typeof record["maxAttempts"] !== "number" || !Number.isInteger(record["maxAttempts"]) || record["maxAttempts"] < 1 || record["maxAttempts"] > 10) {
|
|
308
|
+
throw new Error(`${label}.maxAttempts must be an integer between 1 and 10`);
|
|
309
|
+
}
|
|
310
|
+
const backoff = record["backoff"];
|
|
311
|
+
if (backoff !== undefined) {
|
|
312
|
+
if (backoff === null || typeof backoff !== "object" || Array.isArray(backoff)) {
|
|
313
|
+
throw new Error(`${label}.backoff must be an object`);
|
|
314
|
+
}
|
|
315
|
+
const backoffRecord = backoff;
|
|
316
|
+
for (const key of Object.keys(backoffRecord)) {
|
|
317
|
+
if (key !== "minMs" && key !== "maxMs" && key !== "factor" && key !== "jitter") {
|
|
318
|
+
throw new Error(`${label}.backoff.${key} is not supported`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
validateOptionalPositiveInteger(backoffRecord["minMs"], `${label}.backoff.minMs`);
|
|
322
|
+
validateOptionalPositiveInteger(backoffRecord["maxMs"], `${label}.backoff.maxMs`);
|
|
323
|
+
const factor = backoffRecord["factor"];
|
|
324
|
+
if (factor !== undefined && (typeof factor !== "number" || !Number.isFinite(factor) || factor <= 0)) {
|
|
325
|
+
throw new Error(`${label}.backoff.factor must be a positive number`);
|
|
326
|
+
}
|
|
327
|
+
const jitter = backoffRecord["jitter"];
|
|
328
|
+
if (jitter !== undefined && jitter !== "none" && jitter !== "full") {
|
|
329
|
+
throw new Error(`${label}.backoff.jitter must be "none" or "full"`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function validateOptionalPositiveInteger(value, label) {
|
|
334
|
+
if (value === undefined) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value > 0) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
throw new Error(`${label} must be a positive integer`);
|
|
341
|
+
}
|
|
297
342
|
function validateTaskSchedule(taskId, value) {
|
|
298
343
|
if (value === undefined) {
|
|
299
344
|
return;
|
|
@@ -304,17 +349,6 @@ function validateTaskSchedule(taskId, value) {
|
|
|
304
349
|
if (value.timezone !== undefined && value.timezone.trim() === "") {
|
|
305
350
|
throw new Error(`task ${JSON.stringify(taskId)} schedule timezone must not be empty`);
|
|
306
351
|
}
|
|
307
|
-
if (value.secretBindings !== undefined) {
|
|
308
|
-
if (value.secretBindings === null || typeof value.secretBindings !== "object" || Array.isArray(value.secretBindings)) {
|
|
309
|
-
throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings must be an object`);
|
|
310
|
-
}
|
|
311
|
-
for (const [name, binding] of Object.entries(value.secretBindings)) {
|
|
312
|
-
validateSecretName(name, `task ${JSON.stringify(taskId)} schedule secretBindings.${name}`);
|
|
313
|
-
if (typeof binding !== "string" || binding.trim() === "") {
|
|
314
|
-
throw new Error(`task ${JSON.stringify(taskId)} schedule secretBindings.${name} must be a non-empty string`);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
352
|
}
|
|
319
353
|
function validateTaskQueue(taskId, value) {
|
|
320
354
|
if (value === undefined) {
|
|
@@ -495,21 +529,23 @@ class SandboxBuilderImpl {
|
|
|
495
529
|
imageBuilder;
|
|
496
530
|
workspaceBinding;
|
|
497
531
|
resourceSpec;
|
|
498
|
-
|
|
532
|
+
networkSpec;
|
|
533
|
+
constructor(id, imageBuilder, workspaceBinding, resourceSpec, networkSpec) {
|
|
499
534
|
Object.defineProperty(this, sandboxBuilderBrand, { value: true });
|
|
500
535
|
this.id = id;
|
|
501
536
|
this.imageBuilder = imageBuilder;
|
|
502
537
|
this.workspaceBinding = workspaceBinding;
|
|
503
538
|
this.resourceSpec = resourceSpec;
|
|
539
|
+
this.networkSpec = networkSpec;
|
|
504
540
|
}
|
|
505
541
|
image(img) {
|
|
506
542
|
if (!isImageBuilder(img)) {
|
|
507
543
|
throw new Error("sandbox.image() requires an ImageBuilder created by image()");
|
|
508
544
|
}
|
|
509
|
-
return new SandboxBuilderImpl(this.id, img, this.workspaceBinding, this.resourceSpec);
|
|
545
|
+
return new SandboxBuilderImpl(this.id, img, this.workspaceBinding, this.resourceSpec, this.networkSpec);
|
|
510
546
|
}
|
|
511
547
|
workspace(mountPath = "/workspace") {
|
|
512
|
-
return new SandboxBuilderImpl(this.id, this.imageBuilder, { mountPath: normalizeWorkspaceMountPath(mountPath) }, this.resourceSpec);
|
|
548
|
+
return new SandboxBuilderImpl(this.id, this.imageBuilder, { mountPath: normalizeWorkspaceMountPath(mountPath) }, this.resourceSpec, this.networkSpec);
|
|
513
549
|
}
|
|
514
550
|
resources(opts) {
|
|
515
551
|
const resourceSpec = {
|
|
@@ -517,8 +553,37 @@ class SandboxBuilderImpl {
|
|
|
517
553
|
...opts.memory === undefined ? {} : { memory: opts.memory },
|
|
518
554
|
...opts.disk === undefined ? {} : { disk: opts.disk }
|
|
519
555
|
};
|
|
520
|
-
return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, resourceSpec);
|
|
556
|
+
return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, resourceSpec, this.networkSpec);
|
|
557
|
+
}
|
|
558
|
+
network(opts) {
|
|
559
|
+
return new SandboxBuilderImpl(this.id, this.imageBuilder, this.workspaceBinding, this.resourceSpec, normalizeSandboxNetwork(opts));
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function normalizeSandboxNetwork(opts) {
|
|
563
|
+
const value = opts.internet;
|
|
564
|
+
if (value === undefined || value === true) {
|
|
565
|
+
return { internet: true, allow: [], deny: [] };
|
|
521
566
|
}
|
|
567
|
+
if (value === false) {
|
|
568
|
+
return { internet: false, allow: [], deny: [] };
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
internet: true,
|
|
572
|
+
allow: normalizeNetworkEntries(value.allow, "network.internet.allow"),
|
|
573
|
+
deny: normalizeNetworkEntries(value.deny, "network.internet.deny")
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function normalizeNetworkEntries(entries, label) {
|
|
577
|
+
if (entries === undefined) {
|
|
578
|
+
return [];
|
|
579
|
+
}
|
|
580
|
+
return entries.map((entry, index) => {
|
|
581
|
+
const normalized = entry.trim();
|
|
582
|
+
if (normalized === "") {
|
|
583
|
+
throw new Error(`${label}[${index}] must not be empty`);
|
|
584
|
+
}
|
|
585
|
+
return normalized;
|
|
586
|
+
});
|
|
522
587
|
}
|
|
523
588
|
|
|
524
589
|
class SourceFileRefImpl {
|
|
@@ -557,6 +622,7 @@ export {
|
|
|
557
622
|
validateTaskSchedule,
|
|
558
623
|
validateTaskQueue,
|
|
559
624
|
validateSecretName,
|
|
625
|
+
validateRetryPolicy,
|
|
560
626
|
validateOptionalTTL,
|
|
561
627
|
taskOriginFile,
|
|
562
628
|
taskOriginBrand,
|
package/dist/runtime/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AnyTask, type NoPayload, type SecretDecls, type TaskOutput, type TaskRunOptions, type TaskSecrets, type TaskTriggerPayload } from "../internal.js";
|
|
2
|
-
import { type LogSnapshot, type ListRunEventsOptions, type ListRunsOptions, type PendingHumanWaitpoint, type PendingWaitpointResponse, type RetrieveRunOptions, type RunHandle, type RunEvent, type RunSnapshot, type RunSummary, type RunWaitOptions, type SubscribeRunEventsOptions, type WaitpointRespondOptions, type WaitpointRef } from "./run.js";
|
|
2
|
+
import { type LogSnapshot, type CancelRunOptions, type ListRunEventsOptions, type ListRunsOptions, type PendingHumanWaitpoint, type PendingWaitpointResponse, type RetrieveRunOptions, type RunHandle, type RunEvent, type RunSnapshot, type RunSummary, type RunWaitOptions, type ReplayRunOptions, type SubscribeRunEventsOptions, type WaitpointRespondOptions, type WaitpointRef } from "./run.js";
|
|
3
3
|
export interface HelmrClientOptions {
|
|
4
4
|
readonly url?: string;
|
|
5
5
|
readonly apiKey?: string;
|
|
@@ -47,7 +47,6 @@ export interface ScheduleCreateOptions {
|
|
|
47
47
|
readonly cron: string;
|
|
48
48
|
readonly timezone?: string;
|
|
49
49
|
readonly active?: boolean;
|
|
50
|
-
readonly secretBindings?: Record<string, string>;
|
|
51
50
|
readonly options?: ScheduleRunOptions;
|
|
52
51
|
}
|
|
53
52
|
export type ScheduleUpdateOptions = Omit<ScheduleCreateOptions, "deduplicationKey"> & {
|
|
@@ -139,6 +138,8 @@ export declare class HelmrClient {
|
|
|
139
138
|
readonly runs: {
|
|
140
139
|
retrieve: <TOutput = unknown>(idOrHandle: string | RunHandle<TOutput>, opts?: RetrieveRunOptions) => Promise<RunSnapshot<TOutput>>;
|
|
141
140
|
wait: <TOutput = unknown>(idOrHandle: string | RunHandle<TOutput>, opts?: RunWaitOptions) => Promise<RunSnapshot<TOutput>>;
|
|
141
|
+
cancel: <TOutput = unknown>(idOrHandle: string | RunHandle<TOutput>, opts?: CancelRunOptions) => Promise<RunSnapshot<TOutput>>;
|
|
142
|
+
replay: <TPayload = unknown, TOutput = unknown>(idOrHandle: string | RunHandle<TOutput>, opts?: ReplayRunOptions<TPayload>) => Promise<RunHandle<TOutput>>;
|
|
142
143
|
list: (opts?: ListRunsOptions) => Promise<RunSummary[]>;
|
|
143
144
|
logs: {
|
|
144
145
|
retrieve: <TOutput = unknown>(idOrHandle: string | RunHandle<TOutput>, opts?: {
|
|
@@ -157,6 +158,12 @@ export interface RunResponse {
|
|
|
157
158
|
readonly id: string;
|
|
158
159
|
readonly project_id?: string;
|
|
159
160
|
readonly environment_id?: string;
|
|
161
|
+
readonly version?: string;
|
|
162
|
+
readonly deployment_version?: string;
|
|
163
|
+
readonly api_version?: string;
|
|
164
|
+
readonly sdk_version?: string;
|
|
165
|
+
readonly cli_version?: string;
|
|
166
|
+
readonly attempt_number?: number | null;
|
|
160
167
|
readonly task_id: string;
|
|
161
168
|
readonly status: string;
|
|
162
169
|
readonly exit_code?: number | null;
|
package/dist/runtime/run.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ export interface RunStateBooleans {
|
|
|
20
20
|
export interface RunSnapshot<TOutput = unknown> extends RunStateBooleans {
|
|
21
21
|
readonly id: string;
|
|
22
22
|
readonly taskId: string;
|
|
23
|
+
readonly version?: string | null;
|
|
24
|
+
readonly deploymentVersion?: string | null;
|
|
25
|
+
readonly apiVersion?: string | null;
|
|
26
|
+
readonly sdkVersion?: string | null;
|
|
27
|
+
readonly cliVersion?: string | null;
|
|
28
|
+
readonly attemptNumber: number | null;
|
|
23
29
|
readonly status: RunStatus;
|
|
24
30
|
readonly exitCode: number | null;
|
|
25
31
|
readonly createdAt: string | null;
|
|
@@ -57,6 +63,21 @@ export interface RunWaitOptions {
|
|
|
57
63
|
readonly intervalMs?: number;
|
|
58
64
|
readonly signal?: AbortSignal;
|
|
59
65
|
}
|
|
66
|
+
export interface CancelRunOptions {
|
|
67
|
+
readonly reason?: string;
|
|
68
|
+
readonly force?: boolean;
|
|
69
|
+
readonly idempotencyKey?: string;
|
|
70
|
+
readonly signal?: AbortSignal;
|
|
71
|
+
}
|
|
72
|
+
export interface ReplayRunOptions<TPayload = unknown> {
|
|
73
|
+
readonly version?: "original" | "latest" | string;
|
|
74
|
+
readonly payload?: TPayload;
|
|
75
|
+
readonly reason?: string;
|
|
76
|
+
readonly idempotencyKey?: string;
|
|
77
|
+
readonly metadata?: Record<string, unknown>;
|
|
78
|
+
readonly tags?: readonly string[];
|
|
79
|
+
readonly signal?: AbortSignal;
|
|
80
|
+
}
|
|
60
81
|
export interface RetrieveRunOptions {
|
|
61
82
|
readonly signal?: AbortSignal;
|
|
62
83
|
}
|
|
@@ -132,6 +153,8 @@ export type RunEvent = {
|
|
|
132
153
|
export interface RunEventRecord {
|
|
133
154
|
readonly id: string;
|
|
134
155
|
readonly run_id?: string | null;
|
|
156
|
+
readonly session_id?: string | null;
|
|
157
|
+
readonly attempt_number?: number | null;
|
|
135
158
|
readonly kind: string;
|
|
136
159
|
readonly message: string;
|
|
137
160
|
readonly at: string;
|
|
@@ -165,6 +188,12 @@ export declare function runHandle<TOutput = unknown>(id: string, taskId: string)
|
|
|
165
188
|
export declare function runSnapshot<TOutput = unknown>(snapshot: {
|
|
166
189
|
readonly id: string;
|
|
167
190
|
readonly taskId: string;
|
|
191
|
+
readonly version?: string | null;
|
|
192
|
+
readonly deploymentVersion?: string | null;
|
|
193
|
+
readonly apiVersion?: string | null;
|
|
194
|
+
readonly sdkVersion?: string | null;
|
|
195
|
+
readonly cliVersion?: string | null;
|
|
196
|
+
readonly attemptNumber?: number | null;
|
|
168
197
|
readonly status: string;
|
|
169
198
|
readonly exitCode?: number | null;
|
|
170
199
|
readonly createdAt?: string | null;
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { type SandboxBuilder } from "./internal.js";
|
|
1
|
+
import { type SandboxBuilder, type SandboxNetwork } from "./internal.js";
|
|
2
2
|
export declare const sandbox: (id: string) => SandboxBuilder;
|
|
3
|
-
export type { SandboxBuilder };
|
|
3
|
+
export type { SandboxBuilder, SandboxNetwork };
|
package/dist/schedules.d.ts
CHANGED
|
@@ -14,15 +14,13 @@ export type ScheduleCron = string | {
|
|
|
14
14
|
};
|
|
15
15
|
type DeclarativeScheduleFields = {
|
|
16
16
|
readonly cron: ScheduleCron;
|
|
17
|
-
readonly secretBindings?: Record<string, string>;
|
|
18
17
|
} | {
|
|
19
18
|
readonly cron?: undefined;
|
|
20
|
-
readonly secretBindings?: undefined;
|
|
21
19
|
};
|
|
22
|
-
export type ScheduledTaskConfig<TOutput = unknown, TSecrets extends SecretDecls =
|
|
20
|
+
export type ScheduledTaskConfig<TOutput = unknown, TSecrets extends SecretDecls = readonly []> = Omit<TaskConfigBase<TSecrets>, "payload"> & DeclarativeScheduleFields & {
|
|
23
21
|
readonly payload?: never;
|
|
24
22
|
readonly run: (payload: ScheduledTaskPayload, ctx: TaskContext) => MaybePromise<TOutput>;
|
|
25
23
|
};
|
|
26
|
-
export declare function task<TOutput = unknown, TSecrets extends SecretDecls =
|
|
24
|
+
export declare function task<TOutput = unknown, TSecrets extends SecretDecls = readonly []>(config: ScheduledTaskConfig<TOutput, TSecrets>): MarkedTask<ScheduledTaskPayload, Awaited<TOutput>, TSecrets, typeof scheduledTaskPayloadSchema>;
|
|
27
25
|
declare const scheduledTaskPayloadSchema: PayloadSchema<unknown, ScheduledTaskPayload>;
|
|
28
26
|
export {};
|
package/dist/task.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type NoPayload, type SecretDecls, type Task, type TaskConfig, type TaskQueueConfig, type TaskConfigWithPayload, type TaskConfigWithoutPayload, type TaskRunOptions, type TaskOutput, type TaskPayload, type TaskTriggerPayload } from "./internal.js";
|
|
2
2
|
import type { PayloadSchema, PayloadSchemaInput, PayloadSchemaOutput } from "./schema/payload.js";
|
|
3
|
-
export declare function task<TPayloadSchema extends PayloadSchema<any, any>, TOutput, TSecrets extends SecretDecls =
|
|
4
|
-
export declare function task<TOutput = unknown, TSecrets extends SecretDecls =
|
|
3
|
+
export declare function task<TPayloadSchema extends PayloadSchema<any, any>, TOutput, TSecrets extends SecretDecls = readonly []>(config: TaskConfigWithPayload<TPayloadSchema, TOutput, TSecrets>): Task<PayloadSchemaOutput<TPayloadSchema>, Awaited<TOutput>, TSecrets, PayloadSchemaInput<TPayloadSchema>>;
|
|
4
|
+
export declare function task<TOutput = unknown, TSecrets extends SecretDecls = readonly []>(config: TaskConfigWithoutPayload<TOutput, TSecrets>): Task<NoPayload, Awaited<TOutput>, TSecrets, NoPayload>;
|
|
5
5
|
export declare function queue(config: TaskQueueConfig): TaskQueueConfig;
|
|
6
6
|
export type { NoPayload, Task, TaskConfig, TaskOutput, TaskPayload, TaskQueueConfig, TaskRunOptions, TaskTriggerPayload };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helmr/sdk",
|
|
3
|
-
"version": "0.1.1-rc.
|
|
3
|
+
"version": "0.1.1-rc.8",
|
|
4
4
|
"description": "TypeScript SDK for authoring and triggering Helmr tasks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -43,6 +43,6 @@
|
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@bufbuild/protobuf": "^2.11.0",
|
|
46
|
-
"@helmr/proto": "0.1.1-rc.
|
|
46
|
+
"@helmr/proto": "0.1.1-rc.8"
|
|
47
47
|
}
|
|
48
48
|
}
|