chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:32:57 +08:00
commit cd420f9332
4811 changed files with 884702 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
{
"name": "@internal/compute",
"private": true,
"version": "0.0.1",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"type": "module",
"exports": {
".": {
"@triggerdotdev/source": "./src/index.ts",
"import": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"default": "./dist/src/index.js"
}
},
"dependencies": {
"zod": "3.25.76"
},
"devDependencies": {
"rimraf": "6.0.1"
},
"scripts": {
"clean": "rimraf dist",
"typecheck": "tsc --noEmit",
"build": "pnpm run clean && tsc -p tsconfig.build.json",
"dev": "tsc --watch -p tsconfig.build.json"
}
}
+173
View File
@@ -0,0 +1,173 @@
import type {
TemplateCreateRequest,
TemplateCreateResponse,
InstanceCreateRequest,
InstanceCreateResponse,
InstanceSnapshotRequest,
SnapshotRestoreRequest,
} from "./types.js";
export type ComputeClientOptions = {
gatewayUrl: string;
authToken?: string;
timeoutMs: number;
/**
* Called once per outbound request to collect cross-service correlation
* headers (e.g. `traceparent`, `x-request-id`) from the caller's current
* scope. The returned record is merged onto the outbound headers. Return
* `{}` (or omit the option) to skip propagation.
*/
getPropagationHeaders?: () => Record<string, string>;
};
export class ComputeClient {
readonly templates: TemplatesNamespace;
readonly instances: InstancesNamespace;
readonly snapshots: SnapshotsNamespace;
constructor(private opts: ComputeClientOptions) {
const http = new HttpTransport(opts);
this.templates = new TemplatesNamespace(http);
this.instances = new InstancesNamespace(http);
this.snapshots = new SnapshotsNamespace(http);
}
}
// ── HTTP transport (shared plumbing) ─────────────────────────────────────────
type RequestOptions = {
signal?: AbortSignal;
};
class HttpTransport {
constructor(private opts: ComputeClientOptions) {}
private get headers(): Record<string, string> {
const h: Record<string, string> = { "Content-Type": "application/json" };
if (this.opts.authToken) {
h["Authorization"] = `Bearer ${this.opts.authToken}`;
}
const propagation = this.opts.getPropagationHeaders?.();
if (propagation) {
for (const [key, value] of Object.entries(propagation)) {
if (value) {
h[key] = value;
}
}
}
return h;
}
private signal(options?: RequestOptions): AbortSignal {
return options?.signal ?? AbortSignal.timeout(this.opts.timeoutMs);
}
async post<T = unknown>(
path: string,
body: unknown,
options?: RequestOptions
): Promise<T | undefined> {
const url = `${this.opts.gatewayUrl}${path}`;
const response = await fetch(url, {
method: "POST",
headers: this.headers,
body: JSON.stringify(body),
signal: this.signal(options),
});
if (!response.ok) {
const errorBody = await response.text().catch(() => "unknown error");
throw new ComputeClientError(response.status, errorBody, url);
}
// 202 Accepted or 204 No Content - no body to parse
if (response.status === 202 || response.status === 204) {
return undefined;
}
return (await response.json()) as T;
}
async delete(path: string, options?: RequestOptions): Promise<void> {
const url = `${this.opts.gatewayUrl}${path}`;
const response = await fetch(url, {
method: "DELETE",
headers: this.headers,
signal: this.signal(options),
});
if (!response.ok) {
const errorBody = await response.text().catch(() => "unknown error");
throw new ComputeClientError(response.status, errorBody, url);
}
}
}
// ── Error ────────────────────────────────────────────────────────────────────
export class ComputeClientError extends Error {
constructor(
public readonly status: number,
public readonly body: string,
public readonly url: string
) {
super(`Compute gateway request failed (${status}): ${body}`);
this.name = "ComputeClientError";
}
}
// ── Namespaces ───────────────────────────────────────────────────────────────
class TemplatesNamespace {
constructor(private http: HttpTransport) {}
async create(
req: TemplateCreateRequest,
options?: RequestOptions
): Promise<TemplateCreateResponse | undefined> {
// Background mode returns 202 with no body; sync/callback mode returns
// the full result. Caller decides whether to inspect.
return this.http.post<TemplateCreateResponse>("/api/templates", req, options);
}
}
class InstancesNamespace {
constructor(private http: HttpTransport) {}
async create(
req: InstanceCreateRequest,
options?: RequestOptions
): Promise<InstanceCreateResponse> {
const result = await this.http.post<InstanceCreateResponse>("/api/instances", req, options);
if (!result) {
throw new Error("Compute gateway returned no instance body");
}
return result;
}
async delete(runnerId: string, options?: RequestOptions): Promise<void> {
return this.http.delete(`/api/instances/${runnerId}`, options);
}
async snapshot(
runnerId: string,
req: InstanceSnapshotRequest,
options?: RequestOptions
): Promise<void> {
await this.http.post(`/api/instances/${runnerId}/snapshot`, req, options);
}
}
class SnapshotsNamespace {
constructor(private http: HttpTransport) {}
async restore(
snapshotId: string,
req: SnapshotRestoreRequest,
options?: RequestOptions
): Promise<void> {
await this.http.post(`/api/snapshots/${snapshotId}/restore`, req, options);
}
}
+11
View File
@@ -0,0 +1,11 @@
/**
* Strip the digest suffix from a container image reference.
* Tags are immutable, so we resolve by tag rather than pinning to a digest.
*
* "ghcr.io/org/image:tag@sha256:abc..." -> "ghcr.io/org/image:tag"
* "ghcr.io/org/image@sha256:abc..." -> "ghcr.io/org/image"
* "ghcr.io/org/image:tag" -> "ghcr.io/org/image:tag" (unchanged)
*/
export function stripImageDigest(imageRef: string): string {
return imageRef.split("@")[0] ?? imageRef;
}
+25
View File
@@ -0,0 +1,25 @@
export { ComputeClient, ComputeClientError } from "./client.js";
export type { ComputeClientOptions } from "./client.js";
export { stripImageDigest } from "./imageRef.js";
export {
MachineConfigSchema,
TemplateCreateRequestSchema,
TemplateCreateResultEntrySchema,
TemplateCreateResponseSchema,
InstanceCreateRequestSchema,
InstanceCreateResponseSchema,
InstanceSnapshotRequestSchema,
SnapshotRestoreRequestSchema,
SnapshotCallbackPayloadSchema,
} from "./types.js";
export type {
MachineConfig,
TemplateCreateRequest,
TemplateCreateResultEntry,
TemplateCreateResponse,
InstanceCreateRequest,
InstanceCreateResponse,
InstanceSnapshotRequest,
SnapshotRestoreRequest,
SnapshotCallbackPayload,
} from "./types.js";
+96
View File
@@ -0,0 +1,96 @@
import { z } from "zod";
// ── Templates ────────────────────────────────────────────────────────────────
export const MachineConfigSchema = z.object({
cpu: z.number(),
memory_gb: z.number(),
});
export type MachineConfig = z.infer<typeof MachineConfigSchema>;
export const TemplateCreateRequestSchema = z.object({
image: z.string(),
machine_configs: z.array(MachineConfigSchema),
background: z.boolean().optional(),
callback: z
.object({
url: z.string(),
metadata: z.record(z.string()).optional(),
})
.optional(),
});
export type TemplateCreateRequest = z.infer<typeof TemplateCreateRequestSchema>;
export const TemplateCreateResultEntrySchema = z.object({
machine_config: MachineConfigSchema,
error: z.string().optional(),
});
export type TemplateCreateResultEntry = z.infer<typeof TemplateCreateResultEntrySchema>;
export const TemplateCreateResponseSchema = z.object({
results: z.array(TemplateCreateResultEntrySchema),
error: z.string().optional(),
});
export type TemplateCreateResponse = z.infer<typeof TemplateCreateResponseSchema>;
// ── Instances ────────────────────────────────────────────────────────────────
export const InstanceCreateRequestSchema = z.object({
name: z.string(),
image: z.string(),
env: z.record(z.string()),
cpu: z.number(),
memory_gb: z.number(),
metadata: z.record(z.unknown()).optional(),
// Per-instance identity labels; the provider promotes a configured subset
// to network-policy selection. Distinct from metadata, which is
// observability-only and never selected on.
labels: z.record(z.string()).optional(),
});
export type InstanceCreateRequest = z.infer<typeof InstanceCreateRequestSchema>;
export const InstanceCreateResponseSchema = z.object({
id: z.string(),
_timing: z.unknown().optional(),
});
export type InstanceCreateResponse = z.infer<typeof InstanceCreateResponseSchema>;
export const InstanceSnapshotRequestSchema = z.object({
callback: z.object({
url: z.string(),
metadata: z.record(z.string()),
}),
});
export type InstanceSnapshotRequest = z.infer<typeof InstanceSnapshotRequestSchema>;
// ── Snapshots ────────────────────────────────────────────────────────────────
export const SnapshotRestoreRequestSchema = z.object({
name: z.string(),
metadata: z.record(z.string()),
cpu: z.number(),
memory_gb: z.number(),
// Per-instance identity labels; the caller must resupply the same set as on
// create. The provider doesn't persist them across a snapshot, so omitting
// them drops the restored run's policy-based network selection.
labels: z.record(z.string()).optional(),
});
export type SnapshotRestoreRequest = z.infer<typeof SnapshotRestoreRequestSchema>;
export const SnapshotCallbackPayloadSchema = z.discriminatedUnion("status", [
z.object({
status: z.literal("completed"),
snapshot_id: z.string(),
instance_id: z.string(),
metadata: z.record(z.string()).optional(),
duration_ms: z.number().optional(),
}),
z.object({
status: z.literal("failed"),
instance_id: z.string(),
error: z.string().optional(),
metadata: z.record(z.string()).optional(),
duration_ms: z.number().optional(),
}),
]);
export type SnapshotCallbackPayload = z.infer<typeof SnapshotCallbackPayloadSchema>;
@@ -0,0 +1,21 @@
{
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts"],
"compilerOptions": {
"composite": true,
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable", "DOM.AsyncIterable"],
"outDir": "dist",
"module": "Node16",
"moduleResolution": "Node16",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"preserveWatchOutput": true,
"skipLibCheck": true,
"strict": true,
"declaration": true
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2019",
"lib": ["ES2019", "DOM", "DOM.Iterable", "DOM.AsyncIterable"],
"module": "Node16",
"moduleResolution": "Node16",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"preserveWatchOutput": true,
"skipLibCheck": true,
"noEmit": true,
"strict": true
},
"exclude": ["node_modules"]
}